##// END OF EJS Templates
test: remove aliases forcing date, use default-date...
Boris Feld -
r32414:7b7f55d5 default
parent child Browse files
Show More
@@ -1,2735 +1,2731 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import difflib
48 import difflib
49 import distutils.version as version
49 import distutils.version as version
50 import errno
50 import errno
51 import json
51 import json
52 import optparse
52 import optparse
53 import os
53 import os
54 import random
54 import random
55 import re
55 import re
56 import shutil
56 import shutil
57 import signal
57 import signal
58 import socket
58 import socket
59 import subprocess
59 import subprocess
60 import sys
60 import sys
61 import sysconfig
61 import sysconfig
62 import tempfile
62 import tempfile
63 import threading
63 import threading
64 import time
64 import time
65 import unittest
65 import unittest
66 import xml.dom.minidom as minidom
66 import xml.dom.minidom as minidom
67
67
68 try:
68 try:
69 import Queue as queue
69 import Queue as queue
70 except ImportError:
70 except ImportError:
71 import queue
71 import queue
72
72
73 if os.environ.get('RTUNICODEPEDANTRY', False):
73 if os.environ.get('RTUNICODEPEDANTRY', False):
74 try:
74 try:
75 reload(sys)
75 reload(sys)
76 sys.setdefaultencoding("undefined")
76 sys.setdefaultencoding("undefined")
77 except NameError:
77 except NameError:
78 pass
78 pass
79
79
80 osenvironb = getattr(os, 'environb', os.environ)
80 osenvironb = getattr(os, 'environb', os.environ)
81 processlock = threading.Lock()
81 processlock = threading.Lock()
82
82
83 if sys.version_info > (3, 5, 0):
83 if sys.version_info > (3, 5, 0):
84 PYTHON3 = True
84 PYTHON3 = True
85 xrange = range # we use xrange in one place, and we'd rather not use range
85 xrange = range # we use xrange in one place, and we'd rather not use range
86 def _bytespath(p):
86 def _bytespath(p):
87 return p.encode('utf-8')
87 return p.encode('utf-8')
88
88
89 def _strpath(p):
89 def _strpath(p):
90 return p.decode('utf-8')
90 return p.decode('utf-8')
91
91
92 elif sys.version_info >= (3, 0, 0):
92 elif sys.version_info >= (3, 0, 0):
93 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
93 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
94 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
94 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
95 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
95 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
96 else:
96 else:
97 PYTHON3 = False
97 PYTHON3 = False
98
98
99 # In python 2.x, path operations are generally done using
99 # In python 2.x, path operations are generally done using
100 # bytestrings by default, so we don't have to do any extra
100 # bytestrings by default, so we don't have to do any extra
101 # fiddling there. We define the wrapper functions anyway just to
101 # fiddling there. We define the wrapper functions anyway just to
102 # help keep code consistent between platforms.
102 # help keep code consistent between platforms.
103 def _bytespath(p):
103 def _bytespath(p):
104 return p
104 return p
105
105
106 _strpath = _bytespath
106 _strpath = _bytespath
107
107
108 # For Windows support
108 # For Windows support
109 wifexited = getattr(os, "WIFEXITED", lambda x: False)
109 wifexited = getattr(os, "WIFEXITED", lambda x: False)
110
110
111 # Whether to use IPv6
111 # Whether to use IPv6
112 def checksocketfamily(name, port=20058):
112 def checksocketfamily(name, port=20058):
113 """return true if we can listen on localhost using family=name
113 """return true if we can listen on localhost using family=name
114
114
115 name should be either 'AF_INET', or 'AF_INET6'.
115 name should be either 'AF_INET', or 'AF_INET6'.
116 port being used is okay - EADDRINUSE is considered as successful.
116 port being used is okay - EADDRINUSE is considered as successful.
117 """
117 """
118 family = getattr(socket, name, None)
118 family = getattr(socket, name, None)
119 if family is None:
119 if family is None:
120 return False
120 return False
121 try:
121 try:
122 s = socket.socket(family, socket.SOCK_STREAM)
122 s = socket.socket(family, socket.SOCK_STREAM)
123 s.bind(('localhost', port))
123 s.bind(('localhost', port))
124 s.close()
124 s.close()
125 return True
125 return True
126 except socket.error as exc:
126 except socket.error as exc:
127 if exc.errno == errno.EADDRINUSE:
127 if exc.errno == errno.EADDRINUSE:
128 return True
128 return True
129 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
129 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
130 return False
130 return False
131 else:
131 else:
132 raise
132 raise
133 else:
133 else:
134 return False
134 return False
135
135
136 # useipv6 will be set by parseargs
136 # useipv6 will be set by parseargs
137 useipv6 = None
137 useipv6 = None
138
138
139 def checkportisavailable(port):
139 def checkportisavailable(port):
140 """return true if a port seems free to bind on localhost"""
140 """return true if a port seems free to bind on localhost"""
141 if useipv6:
141 if useipv6:
142 family = socket.AF_INET6
142 family = socket.AF_INET6
143 else:
143 else:
144 family = socket.AF_INET
144 family = socket.AF_INET
145 try:
145 try:
146 s = socket.socket(family, socket.SOCK_STREAM)
146 s = socket.socket(family, socket.SOCK_STREAM)
147 s.bind(('localhost', port))
147 s.bind(('localhost', port))
148 s.close()
148 s.close()
149 return True
149 return True
150 except socket.error as exc:
150 except socket.error as exc:
151 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
151 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
152 errno.EPROTONOSUPPORT):
152 errno.EPROTONOSUPPORT):
153 raise
153 raise
154 return False
154 return False
155
155
156 closefds = os.name == 'posix'
156 closefds = os.name == 'posix'
157 def Popen4(cmd, wd, timeout, env=None):
157 def Popen4(cmd, wd, timeout, env=None):
158 processlock.acquire()
158 processlock.acquire()
159 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
159 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
160 close_fds=closefds,
160 close_fds=closefds,
161 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
161 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
162 stderr=subprocess.STDOUT)
162 stderr=subprocess.STDOUT)
163 processlock.release()
163 processlock.release()
164
164
165 p.fromchild = p.stdout
165 p.fromchild = p.stdout
166 p.tochild = p.stdin
166 p.tochild = p.stdin
167 p.childerr = p.stderr
167 p.childerr = p.stderr
168
168
169 p.timeout = False
169 p.timeout = False
170 if timeout:
170 if timeout:
171 def t():
171 def t():
172 start = time.time()
172 start = time.time()
173 while time.time() - start < timeout and p.returncode is None:
173 while time.time() - start < timeout and p.returncode is None:
174 time.sleep(.1)
174 time.sleep(.1)
175 p.timeout = True
175 p.timeout = True
176 if p.returncode is None:
176 if p.returncode is None:
177 terminate(p)
177 terminate(p)
178 threading.Thread(target=t).start()
178 threading.Thread(target=t).start()
179
179
180 return p
180 return p
181
181
182 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
182 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
183 IMPL_PATH = b'PYTHONPATH'
183 IMPL_PATH = b'PYTHONPATH'
184 if 'java' in sys.platform:
184 if 'java' in sys.platform:
185 IMPL_PATH = b'JYTHONPATH'
185 IMPL_PATH = b'JYTHONPATH'
186
186
187 defaults = {
187 defaults = {
188 'jobs': ('HGTEST_JOBS', 1),
188 'jobs': ('HGTEST_JOBS', 1),
189 'timeout': ('HGTEST_TIMEOUT', 180),
189 'timeout': ('HGTEST_TIMEOUT', 180),
190 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
190 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
191 'port': ('HGTEST_PORT', 20059),
191 'port': ('HGTEST_PORT', 20059),
192 'shell': ('HGTEST_SHELL', 'sh'),
192 'shell': ('HGTEST_SHELL', 'sh'),
193 }
193 }
194
194
195 def canonpath(path):
195 def canonpath(path):
196 return os.path.realpath(os.path.expanduser(path))
196 return os.path.realpath(os.path.expanduser(path))
197
197
198 def parselistfiles(files, listtype, warn=True):
198 def parselistfiles(files, listtype, warn=True):
199 entries = dict()
199 entries = dict()
200 for filename in files:
200 for filename in files:
201 try:
201 try:
202 path = os.path.expanduser(os.path.expandvars(filename))
202 path = os.path.expanduser(os.path.expandvars(filename))
203 f = open(path, "rb")
203 f = open(path, "rb")
204 except IOError as err:
204 except IOError as err:
205 if err.errno != errno.ENOENT:
205 if err.errno != errno.ENOENT:
206 raise
206 raise
207 if warn:
207 if warn:
208 print("warning: no such %s file: %s" % (listtype, filename))
208 print("warning: no such %s file: %s" % (listtype, filename))
209 continue
209 continue
210
210
211 for line in f.readlines():
211 for line in f.readlines():
212 line = line.split(b'#', 1)[0].strip()
212 line = line.split(b'#', 1)[0].strip()
213 if line:
213 if line:
214 entries[line] = filename
214 entries[line] = filename
215
215
216 f.close()
216 f.close()
217 return entries
217 return entries
218
218
219 def parsettestcases(path):
219 def parsettestcases(path):
220 """read a .t test file, return a set of test case names
220 """read a .t test file, return a set of test case names
221
221
222 If path does not exist, return an empty set.
222 If path does not exist, return an empty set.
223 """
223 """
224 cases = set()
224 cases = set()
225 try:
225 try:
226 with open(path, 'rb') as f:
226 with open(path, 'rb') as f:
227 for l in f:
227 for l in f:
228 if l.startswith(b'#testcases '):
228 if l.startswith(b'#testcases '):
229 cases.update(l[11:].split())
229 cases.update(l[11:].split())
230 except IOError as ex:
230 except IOError as ex:
231 if ex.errno != errno.ENOENT:
231 if ex.errno != errno.ENOENT:
232 raise
232 raise
233 return cases
233 return cases
234
234
235 def getparser():
235 def getparser():
236 """Obtain the OptionParser used by the CLI."""
236 """Obtain the OptionParser used by the CLI."""
237 parser = optparse.OptionParser("%prog [options] [tests]")
237 parser = optparse.OptionParser("%prog [options] [tests]")
238
238
239 # keep these sorted
239 # keep these sorted
240 parser.add_option("--blacklist", action="append",
240 parser.add_option("--blacklist", action="append",
241 help="skip tests listed in the specified blacklist file")
241 help="skip tests listed in the specified blacklist file")
242 parser.add_option("--whitelist", action="append",
242 parser.add_option("--whitelist", action="append",
243 help="always run tests listed in the specified whitelist file")
243 help="always run tests listed in the specified whitelist file")
244 parser.add_option("--changed", type="string",
244 parser.add_option("--changed", type="string",
245 help="run tests that are changed in parent rev or working directory")
245 help="run tests that are changed in parent rev or working directory")
246 parser.add_option("-C", "--annotate", action="store_true",
246 parser.add_option("-C", "--annotate", action="store_true",
247 help="output files annotated with coverage")
247 help="output files annotated with coverage")
248 parser.add_option("-c", "--cover", action="store_true",
248 parser.add_option("-c", "--cover", action="store_true",
249 help="print a test coverage report")
249 help="print a test coverage report")
250 parser.add_option("-d", "--debug", action="store_true",
250 parser.add_option("-d", "--debug", action="store_true",
251 help="debug mode: write output of test scripts to console"
251 help="debug mode: write output of test scripts to console"
252 " rather than capturing and diffing it (disables timeout)")
252 " rather than capturing and diffing it (disables timeout)")
253 parser.add_option("-f", "--first", action="store_true",
253 parser.add_option("-f", "--first", action="store_true",
254 help="exit on the first test failure")
254 help="exit on the first test failure")
255 parser.add_option("-H", "--htmlcov", action="store_true",
255 parser.add_option("-H", "--htmlcov", action="store_true",
256 help="create an HTML report of the coverage of the files")
256 help="create an HTML report of the coverage of the files")
257 parser.add_option("-i", "--interactive", action="store_true",
257 parser.add_option("-i", "--interactive", action="store_true",
258 help="prompt to accept changed output")
258 help="prompt to accept changed output")
259 parser.add_option("-j", "--jobs", type="int",
259 parser.add_option("-j", "--jobs", type="int",
260 help="number of jobs to run in parallel"
260 help="number of jobs to run in parallel"
261 " (default: $%s or %d)" % defaults['jobs'])
261 " (default: $%s or %d)" % defaults['jobs'])
262 parser.add_option("--keep-tmpdir", action="store_true",
262 parser.add_option("--keep-tmpdir", action="store_true",
263 help="keep temporary directory after running tests")
263 help="keep temporary directory after running tests")
264 parser.add_option("-k", "--keywords",
264 parser.add_option("-k", "--keywords",
265 help="run tests matching keywords")
265 help="run tests matching keywords")
266 parser.add_option("-l", "--local", action="store_true",
266 parser.add_option("-l", "--local", action="store_true",
267 help="shortcut for --with-hg=<testdir>/../hg, "
267 help="shortcut for --with-hg=<testdir>/../hg, "
268 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
268 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
269 parser.add_option("--loop", action="store_true",
269 parser.add_option("--loop", action="store_true",
270 help="loop tests repeatedly")
270 help="loop tests repeatedly")
271 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
271 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
272 help="run each test N times (default=1)", default=1)
272 help="run each test N times (default=1)", default=1)
273 parser.add_option("-n", "--nodiff", action="store_true",
273 parser.add_option("-n", "--nodiff", action="store_true",
274 help="skip showing test changes")
274 help="skip showing test changes")
275 parser.add_option("-p", "--port", type="int",
275 parser.add_option("-p", "--port", type="int",
276 help="port on which servers should listen"
276 help="port on which servers should listen"
277 " (default: $%s or %d)" % defaults['port'])
277 " (default: $%s or %d)" % defaults['port'])
278 parser.add_option("--compiler", type="string",
278 parser.add_option("--compiler", type="string",
279 help="compiler to build with")
279 help="compiler to build with")
280 parser.add_option("--pure", action="store_true",
280 parser.add_option("--pure", action="store_true",
281 help="use pure Python code instead of C extensions")
281 help="use pure Python code instead of C extensions")
282 parser.add_option("-R", "--restart", action="store_true",
282 parser.add_option("-R", "--restart", action="store_true",
283 help="restart at last error")
283 help="restart at last error")
284 parser.add_option("-r", "--retest", action="store_true",
284 parser.add_option("-r", "--retest", action="store_true",
285 help="retest failed tests")
285 help="retest failed tests")
286 parser.add_option("-S", "--noskips", action="store_true",
286 parser.add_option("-S", "--noskips", action="store_true",
287 help="don't report skip tests verbosely")
287 help="don't report skip tests verbosely")
288 parser.add_option("--shell", type="string",
288 parser.add_option("--shell", type="string",
289 help="shell to use (default: $%s or %s)" % defaults['shell'])
289 help="shell to use (default: $%s or %s)" % defaults['shell'])
290 parser.add_option("-t", "--timeout", type="int",
290 parser.add_option("-t", "--timeout", type="int",
291 help="kill errant tests after TIMEOUT seconds"
291 help="kill errant tests after TIMEOUT seconds"
292 " (default: $%s or %d)" % defaults['timeout'])
292 " (default: $%s or %d)" % defaults['timeout'])
293 parser.add_option("--slowtimeout", type="int",
293 parser.add_option("--slowtimeout", type="int",
294 help="kill errant slow tests after SLOWTIMEOUT seconds"
294 help="kill errant slow tests after SLOWTIMEOUT seconds"
295 " (default: $%s or %d)" % defaults['slowtimeout'])
295 " (default: $%s or %d)" % defaults['slowtimeout'])
296 parser.add_option("--time", action="store_true",
296 parser.add_option("--time", action="store_true",
297 help="time how long each test takes")
297 help="time how long each test takes")
298 parser.add_option("--json", action="store_true",
298 parser.add_option("--json", action="store_true",
299 help="store test result data in 'report.json' file")
299 help="store test result data in 'report.json' file")
300 parser.add_option("--tmpdir", type="string",
300 parser.add_option("--tmpdir", type="string",
301 help="run tests in the given temporary directory"
301 help="run tests in the given temporary directory"
302 " (implies --keep-tmpdir)")
302 " (implies --keep-tmpdir)")
303 parser.add_option("-v", "--verbose", action="store_true",
303 parser.add_option("-v", "--verbose", action="store_true",
304 help="output verbose messages")
304 help="output verbose messages")
305 parser.add_option("--xunit", type="string",
305 parser.add_option("--xunit", type="string",
306 help="record xunit results at specified path")
306 help="record xunit results at specified path")
307 parser.add_option("--view", type="string",
307 parser.add_option("--view", type="string",
308 help="external diff viewer")
308 help="external diff viewer")
309 parser.add_option("--with-hg", type="string",
309 parser.add_option("--with-hg", type="string",
310 metavar="HG",
310 metavar="HG",
311 help="test using specified hg script rather than a "
311 help="test using specified hg script rather than a "
312 "temporary installation")
312 "temporary installation")
313 parser.add_option("--chg", action="store_true",
313 parser.add_option("--chg", action="store_true",
314 help="install and use chg wrapper in place of hg")
314 help="install and use chg wrapper in place of hg")
315 parser.add_option("--with-chg", metavar="CHG",
315 parser.add_option("--with-chg", metavar="CHG",
316 help="use specified chg wrapper in place of hg")
316 help="use specified chg wrapper in place of hg")
317 parser.add_option("--ipv6", action="store_true",
317 parser.add_option("--ipv6", action="store_true",
318 help="prefer IPv6 to IPv4 for network related tests")
318 help="prefer IPv6 to IPv4 for network related tests")
319 parser.add_option("-3", "--py3k-warnings", action="store_true",
319 parser.add_option("-3", "--py3k-warnings", action="store_true",
320 help="enable Py3k warnings on Python 2.7+")
320 help="enable Py3k warnings on Python 2.7+")
321 # This option should be deleted once test-check-py3-compat.t and other
321 # This option should be deleted once test-check-py3-compat.t and other
322 # Python 3 tests run with Python 3.
322 # Python 3 tests run with Python 3.
323 parser.add_option("--with-python3", metavar="PYTHON3",
323 parser.add_option("--with-python3", metavar="PYTHON3",
324 help="Python 3 interpreter (if running under Python 2)"
324 help="Python 3 interpreter (if running under Python 2)"
325 " (TEMPORARY)")
325 " (TEMPORARY)")
326 parser.add_option('--extra-config-opt', action="append",
326 parser.add_option('--extra-config-opt', action="append",
327 help='set the given config opt in the test hgrc')
327 help='set the given config opt in the test hgrc')
328 parser.add_option('--random', action="store_true",
328 parser.add_option('--random', action="store_true",
329 help='run tests in random order')
329 help='run tests in random order')
330 parser.add_option('--profile-runner', action='store_true',
330 parser.add_option('--profile-runner', action='store_true',
331 help='run statprof on run-tests')
331 help='run statprof on run-tests')
332 parser.add_option('--allow-slow-tests', action='store_true',
332 parser.add_option('--allow-slow-tests', action='store_true',
333 help='allow extremely slow tests')
333 help='allow extremely slow tests')
334 parser.add_option('--showchannels', action='store_true',
334 parser.add_option('--showchannels', action='store_true',
335 help='show scheduling channels')
335 help='show scheduling channels')
336 parser.add_option('--known-good-rev', type="string",
336 parser.add_option('--known-good-rev', type="string",
337 metavar="known_good_rev",
337 metavar="known_good_rev",
338 help=("Automatically bisect any failures using this "
338 help=("Automatically bisect any failures using this "
339 "revision as a known-good revision."))
339 "revision as a known-good revision."))
340
340
341 for option, (envvar, default) in defaults.items():
341 for option, (envvar, default) in defaults.items():
342 defaults[option] = type(default)(os.environ.get(envvar, default))
342 defaults[option] = type(default)(os.environ.get(envvar, default))
343 parser.set_defaults(**defaults)
343 parser.set_defaults(**defaults)
344
344
345 return parser
345 return parser
346
346
347 def parseargs(args, parser):
347 def parseargs(args, parser):
348 """Parse arguments with our OptionParser and validate results."""
348 """Parse arguments with our OptionParser and validate results."""
349 (options, args) = parser.parse_args(args)
349 (options, args) = parser.parse_args(args)
350
350
351 # jython is always pure
351 # jython is always pure
352 if 'java' in sys.platform or '__pypy__' in sys.modules:
352 if 'java' in sys.platform or '__pypy__' in sys.modules:
353 options.pure = True
353 options.pure = True
354
354
355 if options.with_hg:
355 if options.with_hg:
356 options.with_hg = canonpath(_bytespath(options.with_hg))
356 options.with_hg = canonpath(_bytespath(options.with_hg))
357 if not (os.path.isfile(options.with_hg) and
357 if not (os.path.isfile(options.with_hg) and
358 os.access(options.with_hg, os.X_OK)):
358 os.access(options.with_hg, os.X_OK)):
359 parser.error('--with-hg must specify an executable hg script')
359 parser.error('--with-hg must specify an executable hg script')
360 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
360 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
361 sys.stderr.write('warning: --with-hg should specify an hg script\n')
361 sys.stderr.write('warning: --with-hg should specify an hg script\n')
362 if options.local:
362 if options.local:
363 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
363 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
364 reporootdir = os.path.dirname(testdir)
364 reporootdir = os.path.dirname(testdir)
365 pathandattrs = [(b'hg', 'with_hg')]
365 pathandattrs = [(b'hg', 'with_hg')]
366 if options.chg:
366 if options.chg:
367 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
367 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
368 for relpath, attr in pathandattrs:
368 for relpath, attr in pathandattrs:
369 binpath = os.path.join(reporootdir, relpath)
369 binpath = os.path.join(reporootdir, relpath)
370 if os.name != 'nt' and not os.access(binpath, os.X_OK):
370 if os.name != 'nt' and not os.access(binpath, os.X_OK):
371 parser.error('--local specified, but %r not found or '
371 parser.error('--local specified, but %r not found or '
372 'not executable' % binpath)
372 'not executable' % binpath)
373 setattr(options, attr, binpath)
373 setattr(options, attr, binpath)
374
374
375 if (options.chg or options.with_chg) and os.name == 'nt':
375 if (options.chg or options.with_chg) and os.name == 'nt':
376 parser.error('chg does not work on %s' % os.name)
376 parser.error('chg does not work on %s' % os.name)
377 if options.with_chg:
377 if options.with_chg:
378 options.chg = False # no installation to temporary location
378 options.chg = False # no installation to temporary location
379 options.with_chg = canonpath(_bytespath(options.with_chg))
379 options.with_chg = canonpath(_bytespath(options.with_chg))
380 if not (os.path.isfile(options.with_chg) and
380 if not (os.path.isfile(options.with_chg) and
381 os.access(options.with_chg, os.X_OK)):
381 os.access(options.with_chg, os.X_OK)):
382 parser.error('--with-chg must specify a chg executable')
382 parser.error('--with-chg must specify a chg executable')
383 if options.chg and options.with_hg:
383 if options.chg and options.with_hg:
384 # chg shares installation location with hg
384 # chg shares installation location with hg
385 parser.error('--chg does not work when --with-hg is specified '
385 parser.error('--chg does not work when --with-hg is specified '
386 '(use --with-chg instead)')
386 '(use --with-chg instead)')
387
387
388 global useipv6
388 global useipv6
389 if options.ipv6:
389 if options.ipv6:
390 useipv6 = checksocketfamily('AF_INET6')
390 useipv6 = checksocketfamily('AF_INET6')
391 else:
391 else:
392 # only use IPv6 if IPv4 is unavailable and IPv6 is available
392 # only use IPv6 if IPv4 is unavailable and IPv6 is available
393 useipv6 = ((not checksocketfamily('AF_INET'))
393 useipv6 = ((not checksocketfamily('AF_INET'))
394 and checksocketfamily('AF_INET6'))
394 and checksocketfamily('AF_INET6'))
395
395
396 options.anycoverage = options.cover or options.annotate or options.htmlcov
396 options.anycoverage = options.cover or options.annotate or options.htmlcov
397 if options.anycoverage:
397 if options.anycoverage:
398 try:
398 try:
399 import coverage
399 import coverage
400 covver = version.StrictVersion(coverage.__version__).version
400 covver = version.StrictVersion(coverage.__version__).version
401 if covver < (3, 3):
401 if covver < (3, 3):
402 parser.error('coverage options require coverage 3.3 or later')
402 parser.error('coverage options require coverage 3.3 or later')
403 except ImportError:
403 except ImportError:
404 parser.error('coverage options now require the coverage package')
404 parser.error('coverage options now require the coverage package')
405
405
406 if options.anycoverage and options.local:
406 if options.anycoverage and options.local:
407 # this needs some path mangling somewhere, I guess
407 # this needs some path mangling somewhere, I guess
408 parser.error("sorry, coverage options do not work when --local "
408 parser.error("sorry, coverage options do not work when --local "
409 "is specified")
409 "is specified")
410
410
411 if options.anycoverage and options.with_hg:
411 if options.anycoverage and options.with_hg:
412 parser.error("sorry, coverage options do not work when --with-hg "
412 parser.error("sorry, coverage options do not work when --with-hg "
413 "is specified")
413 "is specified")
414
414
415 global verbose
415 global verbose
416 if options.verbose:
416 if options.verbose:
417 verbose = ''
417 verbose = ''
418
418
419 if options.tmpdir:
419 if options.tmpdir:
420 options.tmpdir = canonpath(options.tmpdir)
420 options.tmpdir = canonpath(options.tmpdir)
421
421
422 if options.jobs < 1:
422 if options.jobs < 1:
423 parser.error('--jobs must be positive')
423 parser.error('--jobs must be positive')
424 if options.interactive and options.debug:
424 if options.interactive and options.debug:
425 parser.error("-i/--interactive and -d/--debug are incompatible")
425 parser.error("-i/--interactive and -d/--debug are incompatible")
426 if options.debug:
426 if options.debug:
427 if options.timeout != defaults['timeout']:
427 if options.timeout != defaults['timeout']:
428 sys.stderr.write(
428 sys.stderr.write(
429 'warning: --timeout option ignored with --debug\n')
429 'warning: --timeout option ignored with --debug\n')
430 if options.slowtimeout != defaults['slowtimeout']:
430 if options.slowtimeout != defaults['slowtimeout']:
431 sys.stderr.write(
431 sys.stderr.write(
432 'warning: --slowtimeout option ignored with --debug\n')
432 'warning: --slowtimeout option ignored with --debug\n')
433 options.timeout = 0
433 options.timeout = 0
434 options.slowtimeout = 0
434 options.slowtimeout = 0
435 if options.py3k_warnings:
435 if options.py3k_warnings:
436 if PYTHON3:
436 if PYTHON3:
437 parser.error(
437 parser.error(
438 '--py3k-warnings can only be used on Python 2.7')
438 '--py3k-warnings can only be used on Python 2.7')
439 if options.with_python3:
439 if options.with_python3:
440 if PYTHON3:
440 if PYTHON3:
441 parser.error('--with-python3 cannot be used when executing with '
441 parser.error('--with-python3 cannot be used when executing with '
442 'Python 3')
442 'Python 3')
443
443
444 options.with_python3 = canonpath(options.with_python3)
444 options.with_python3 = canonpath(options.with_python3)
445 # Verify Python3 executable is acceptable.
445 # Verify Python3 executable is acceptable.
446 proc = subprocess.Popen([options.with_python3, b'--version'],
446 proc = subprocess.Popen([options.with_python3, b'--version'],
447 stdout=subprocess.PIPE,
447 stdout=subprocess.PIPE,
448 stderr=subprocess.STDOUT)
448 stderr=subprocess.STDOUT)
449 out, _err = proc.communicate()
449 out, _err = proc.communicate()
450 ret = proc.wait()
450 ret = proc.wait()
451 if ret != 0:
451 if ret != 0:
452 parser.error('could not determine version of python 3')
452 parser.error('could not determine version of python 3')
453 if not out.startswith('Python '):
453 if not out.startswith('Python '):
454 parser.error('unexpected output from python3 --version: %s' %
454 parser.error('unexpected output from python3 --version: %s' %
455 out)
455 out)
456 vers = version.LooseVersion(out[len('Python '):])
456 vers = version.LooseVersion(out[len('Python '):])
457 if vers < version.LooseVersion('3.5.0'):
457 if vers < version.LooseVersion('3.5.0'):
458 parser.error('--with-python3 version must be 3.5.0 or greater; '
458 parser.error('--with-python3 version must be 3.5.0 or greater; '
459 'got %s' % out)
459 'got %s' % out)
460
460
461 if options.blacklist:
461 if options.blacklist:
462 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
462 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
463 if options.whitelist:
463 if options.whitelist:
464 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
464 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
465 else:
465 else:
466 options.whitelisted = {}
466 options.whitelisted = {}
467
467
468 if options.showchannels:
468 if options.showchannels:
469 options.nodiff = True
469 options.nodiff = True
470
470
471 return (options, args)
471 return (options, args)
472
472
473 def rename(src, dst):
473 def rename(src, dst):
474 """Like os.rename(), trade atomicity and opened files friendliness
474 """Like os.rename(), trade atomicity and opened files friendliness
475 for existing destination support.
475 for existing destination support.
476 """
476 """
477 shutil.copy(src, dst)
477 shutil.copy(src, dst)
478 os.remove(src)
478 os.remove(src)
479
479
480 _unified_diff = difflib.unified_diff
480 _unified_diff = difflib.unified_diff
481 if PYTHON3:
481 if PYTHON3:
482 import functools
482 import functools
483 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
483 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
484
484
485 def getdiff(expected, output, ref, err):
485 def getdiff(expected, output, ref, err):
486 servefail = False
486 servefail = False
487 lines = []
487 lines = []
488 for line in _unified_diff(expected, output, ref, err):
488 for line in _unified_diff(expected, output, ref, err):
489 if line.startswith(b'+++') or line.startswith(b'---'):
489 if line.startswith(b'+++') or line.startswith(b'---'):
490 line = line.replace(b'\\', b'/')
490 line = line.replace(b'\\', b'/')
491 if line.endswith(b' \n'):
491 if line.endswith(b' \n'):
492 line = line[:-2] + b'\n'
492 line = line[:-2] + b'\n'
493 lines.append(line)
493 lines.append(line)
494 if not servefail and line.startswith(
494 if not servefail and line.startswith(
495 b'+ abort: child process failed to start'):
495 b'+ abort: child process failed to start'):
496 servefail = True
496 servefail = True
497
497
498 return servefail, lines
498 return servefail, lines
499
499
500 verbose = False
500 verbose = False
501 def vlog(*msg):
501 def vlog(*msg):
502 """Log only when in verbose mode."""
502 """Log only when in verbose mode."""
503 if verbose is False:
503 if verbose is False:
504 return
504 return
505
505
506 return log(*msg)
506 return log(*msg)
507
507
508 # Bytes that break XML even in a CDATA block: control characters 0-31
508 # Bytes that break XML even in a CDATA block: control characters 0-31
509 # sans \t, \n and \r
509 # sans \t, \n and \r
510 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
510 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
511
511
512 # Match feature conditionalized output lines in the form, capturing the feature
512 # Match feature conditionalized output lines in the form, capturing the feature
513 # list in group 2, and the preceeding line output in group 1:
513 # list in group 2, and the preceeding line output in group 1:
514 #
514 #
515 # output..output (feature !)\n
515 # output..output (feature !)\n
516 optline = re.compile(b'(.+) \((.+?) !\)\n$')
516 optline = re.compile(b'(.+) \((.+?) !\)\n$')
517
517
518 def cdatasafe(data):
518 def cdatasafe(data):
519 """Make a string safe to include in a CDATA block.
519 """Make a string safe to include in a CDATA block.
520
520
521 Certain control characters are illegal in a CDATA block, and
521 Certain control characters are illegal in a CDATA block, and
522 there's no way to include a ]]> in a CDATA either. This function
522 there's no way to include a ]]> in a CDATA either. This function
523 replaces illegal bytes with ? and adds a space between the ]] so
523 replaces illegal bytes with ? and adds a space between the ]] so
524 that it won't break the CDATA block.
524 that it won't break the CDATA block.
525 """
525 """
526 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
526 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
527
527
528 def log(*msg):
528 def log(*msg):
529 """Log something to stdout.
529 """Log something to stdout.
530
530
531 Arguments are strings to print.
531 Arguments are strings to print.
532 """
532 """
533 with iolock:
533 with iolock:
534 if verbose:
534 if verbose:
535 print(verbose, end=' ')
535 print(verbose, end=' ')
536 for m in msg:
536 for m in msg:
537 print(m, end=' ')
537 print(m, end=' ')
538 print()
538 print()
539 sys.stdout.flush()
539 sys.stdout.flush()
540
540
541 def terminate(proc):
541 def terminate(proc):
542 """Terminate subprocess"""
542 """Terminate subprocess"""
543 vlog('# Terminating process %d' % proc.pid)
543 vlog('# Terminating process %d' % proc.pid)
544 try:
544 try:
545 proc.terminate()
545 proc.terminate()
546 except OSError:
546 except OSError:
547 pass
547 pass
548
548
549 def killdaemons(pidfile):
549 def killdaemons(pidfile):
550 import killdaemons as killmod
550 import killdaemons as killmod
551 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
551 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
552 logfn=vlog)
552 logfn=vlog)
553
553
554 class Test(unittest.TestCase):
554 class Test(unittest.TestCase):
555 """Encapsulates a single, runnable test.
555 """Encapsulates a single, runnable test.
556
556
557 While this class conforms to the unittest.TestCase API, it differs in that
557 While this class conforms to the unittest.TestCase API, it differs in that
558 instances need to be instantiated manually. (Typically, unittest.TestCase
558 instances need to be instantiated manually. (Typically, unittest.TestCase
559 classes are instantiated automatically by scanning modules.)
559 classes are instantiated automatically by scanning modules.)
560 """
560 """
561
561
562 # Status code reserved for skipped tests (used by hghave).
562 # Status code reserved for skipped tests (used by hghave).
563 SKIPPED_STATUS = 80
563 SKIPPED_STATUS = 80
564
564
565 def __init__(self, path, tmpdir, keeptmpdir=False,
565 def __init__(self, path, tmpdir, keeptmpdir=False,
566 debug=False,
566 debug=False,
567 timeout=defaults['timeout'],
567 timeout=defaults['timeout'],
568 startport=defaults['port'], extraconfigopts=None,
568 startport=defaults['port'], extraconfigopts=None,
569 py3kwarnings=False, shell=None, hgcommand=None,
569 py3kwarnings=False, shell=None, hgcommand=None,
570 slowtimeout=defaults['slowtimeout'], usechg=False,
570 slowtimeout=defaults['slowtimeout'], usechg=False,
571 useipv6=False):
571 useipv6=False):
572 """Create a test from parameters.
572 """Create a test from parameters.
573
573
574 path is the full path to the file defining the test.
574 path is the full path to the file defining the test.
575
575
576 tmpdir is the main temporary directory to use for this test.
576 tmpdir is the main temporary directory to use for this test.
577
577
578 keeptmpdir determines whether to keep the test's temporary directory
578 keeptmpdir determines whether to keep the test's temporary directory
579 after execution. It defaults to removal (False).
579 after execution. It defaults to removal (False).
580
580
581 debug mode will make the test execute verbosely, with unfiltered
581 debug mode will make the test execute verbosely, with unfiltered
582 output.
582 output.
583
583
584 timeout controls the maximum run time of the test. It is ignored when
584 timeout controls the maximum run time of the test. It is ignored when
585 debug is True. See slowtimeout for tests with #require slow.
585 debug is True. See slowtimeout for tests with #require slow.
586
586
587 slowtimeout overrides timeout if the test has #require slow.
587 slowtimeout overrides timeout if the test has #require slow.
588
588
589 startport controls the starting port number to use for this test. Each
589 startport controls the starting port number to use for this test. Each
590 test will reserve 3 port numbers for execution. It is the caller's
590 test will reserve 3 port numbers for execution. It is the caller's
591 responsibility to allocate a non-overlapping port range to Test
591 responsibility to allocate a non-overlapping port range to Test
592 instances.
592 instances.
593
593
594 extraconfigopts is an iterable of extra hgrc config options. Values
594 extraconfigopts is an iterable of extra hgrc config options. Values
595 must have the form "key=value" (something understood by hgrc). Values
595 must have the form "key=value" (something understood by hgrc). Values
596 of the form "foo.key=value" will result in "[foo] key=value".
596 of the form "foo.key=value" will result in "[foo] key=value".
597
597
598 py3kwarnings enables Py3k warnings.
598 py3kwarnings enables Py3k warnings.
599
599
600 shell is the shell to execute tests in.
600 shell is the shell to execute tests in.
601 """
601 """
602 self.path = path
602 self.path = path
603 self.bname = os.path.basename(path)
603 self.bname = os.path.basename(path)
604 self.name = _strpath(self.bname)
604 self.name = _strpath(self.bname)
605 self._testdir = os.path.dirname(path)
605 self._testdir = os.path.dirname(path)
606 self._tmpname = os.path.basename(path)
606 self._tmpname = os.path.basename(path)
607 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
607 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
608
608
609 self._threadtmp = tmpdir
609 self._threadtmp = tmpdir
610 self._keeptmpdir = keeptmpdir
610 self._keeptmpdir = keeptmpdir
611 self._debug = debug
611 self._debug = debug
612 self._timeout = timeout
612 self._timeout = timeout
613 self._slowtimeout = slowtimeout
613 self._slowtimeout = slowtimeout
614 self._startport = startport
614 self._startport = startport
615 self._extraconfigopts = extraconfigopts or []
615 self._extraconfigopts = extraconfigopts or []
616 self._py3kwarnings = py3kwarnings
616 self._py3kwarnings = py3kwarnings
617 self._shell = _bytespath(shell)
617 self._shell = _bytespath(shell)
618 self._hgcommand = hgcommand or b'hg'
618 self._hgcommand = hgcommand or b'hg'
619 self._usechg = usechg
619 self._usechg = usechg
620 self._useipv6 = useipv6
620 self._useipv6 = useipv6
621
621
622 self._aborted = False
622 self._aborted = False
623 self._daemonpids = []
623 self._daemonpids = []
624 self._finished = None
624 self._finished = None
625 self._ret = None
625 self._ret = None
626 self._out = None
626 self._out = None
627 self._skipped = None
627 self._skipped = None
628 self._testtmp = None
628 self._testtmp = None
629 self._chgsockdir = None
629 self._chgsockdir = None
630
630
631 # If we're not in --debug mode and reference output file exists,
631 # If we're not in --debug mode and reference output file exists,
632 # check test output against it.
632 # check test output against it.
633 if debug:
633 if debug:
634 self._refout = None # to match "out is None"
634 self._refout = None # to match "out is None"
635 elif os.path.exists(self.refpath):
635 elif os.path.exists(self.refpath):
636 f = open(self.refpath, 'rb')
636 f = open(self.refpath, 'rb')
637 self._refout = f.read().splitlines(True)
637 self._refout = f.read().splitlines(True)
638 f.close()
638 f.close()
639 else:
639 else:
640 self._refout = []
640 self._refout = []
641
641
642 # needed to get base class __repr__ running
642 # needed to get base class __repr__ running
643 @property
643 @property
644 def _testMethodName(self):
644 def _testMethodName(self):
645 return self.name
645 return self.name
646
646
647 def __str__(self):
647 def __str__(self):
648 return self.name
648 return self.name
649
649
650 def shortDescription(self):
650 def shortDescription(self):
651 return self.name
651 return self.name
652
652
653 def setUp(self):
653 def setUp(self):
654 """Tasks to perform before run()."""
654 """Tasks to perform before run()."""
655 self._finished = False
655 self._finished = False
656 self._ret = None
656 self._ret = None
657 self._out = None
657 self._out = None
658 self._skipped = None
658 self._skipped = None
659
659
660 try:
660 try:
661 os.mkdir(self._threadtmp)
661 os.mkdir(self._threadtmp)
662 except OSError as e:
662 except OSError as e:
663 if e.errno != errno.EEXIST:
663 if e.errno != errno.EEXIST:
664 raise
664 raise
665
665
666 name = self._tmpname
666 name = self._tmpname
667 self._testtmp = os.path.join(self._threadtmp, name)
667 self._testtmp = os.path.join(self._threadtmp, name)
668 os.mkdir(self._testtmp)
668 os.mkdir(self._testtmp)
669
669
670 # Remove any previous output files.
670 # Remove any previous output files.
671 if os.path.exists(self.errpath):
671 if os.path.exists(self.errpath):
672 try:
672 try:
673 os.remove(self.errpath)
673 os.remove(self.errpath)
674 except OSError as e:
674 except OSError as e:
675 # We might have raced another test to clean up a .err
675 # We might have raced another test to clean up a .err
676 # file, so ignore ENOENT when removing a previous .err
676 # file, so ignore ENOENT when removing a previous .err
677 # file.
677 # file.
678 if e.errno != errno.ENOENT:
678 if e.errno != errno.ENOENT:
679 raise
679 raise
680
680
681 if self._usechg:
681 if self._usechg:
682 self._chgsockdir = os.path.join(self._threadtmp,
682 self._chgsockdir = os.path.join(self._threadtmp,
683 b'%s.chgsock' % name)
683 b'%s.chgsock' % name)
684 os.mkdir(self._chgsockdir)
684 os.mkdir(self._chgsockdir)
685
685
686 def run(self, result):
686 def run(self, result):
687 """Run this test and report results against a TestResult instance."""
687 """Run this test and report results against a TestResult instance."""
688 # This function is extremely similar to unittest.TestCase.run(). Once
688 # This function is extremely similar to unittest.TestCase.run(). Once
689 # we require Python 2.7 (or at least its version of unittest), this
689 # we require Python 2.7 (or at least its version of unittest), this
690 # function can largely go away.
690 # function can largely go away.
691 self._result = result
691 self._result = result
692 result.startTest(self)
692 result.startTest(self)
693 try:
693 try:
694 try:
694 try:
695 self.setUp()
695 self.setUp()
696 except (KeyboardInterrupt, SystemExit):
696 except (KeyboardInterrupt, SystemExit):
697 self._aborted = True
697 self._aborted = True
698 raise
698 raise
699 except Exception:
699 except Exception:
700 result.addError(self, sys.exc_info())
700 result.addError(self, sys.exc_info())
701 return
701 return
702
702
703 success = False
703 success = False
704 try:
704 try:
705 self.runTest()
705 self.runTest()
706 except KeyboardInterrupt:
706 except KeyboardInterrupt:
707 self._aborted = True
707 self._aborted = True
708 raise
708 raise
709 except SkipTest as e:
709 except SkipTest as e:
710 result.addSkip(self, str(e))
710 result.addSkip(self, str(e))
711 # The base class will have already counted this as a
711 # The base class will have already counted this as a
712 # test we "ran", but we want to exclude skipped tests
712 # test we "ran", but we want to exclude skipped tests
713 # from those we count towards those run.
713 # from those we count towards those run.
714 result.testsRun -= 1
714 result.testsRun -= 1
715 except IgnoreTest as e:
715 except IgnoreTest as e:
716 result.addIgnore(self, str(e))
716 result.addIgnore(self, str(e))
717 # As with skips, ignores also should be excluded from
717 # As with skips, ignores also should be excluded from
718 # the number of tests executed.
718 # the number of tests executed.
719 result.testsRun -= 1
719 result.testsRun -= 1
720 except WarnTest as e:
720 except WarnTest as e:
721 result.addWarn(self, str(e))
721 result.addWarn(self, str(e))
722 except ReportedTest as e:
722 except ReportedTest as e:
723 pass
723 pass
724 except self.failureException as e:
724 except self.failureException as e:
725 # This differs from unittest in that we don't capture
725 # This differs from unittest in that we don't capture
726 # the stack trace. This is for historical reasons and
726 # the stack trace. This is for historical reasons and
727 # this decision could be revisited in the future,
727 # this decision could be revisited in the future,
728 # especially for PythonTest instances.
728 # especially for PythonTest instances.
729 if result.addFailure(self, str(e)):
729 if result.addFailure(self, str(e)):
730 success = True
730 success = True
731 except Exception:
731 except Exception:
732 result.addError(self, sys.exc_info())
732 result.addError(self, sys.exc_info())
733 else:
733 else:
734 success = True
734 success = True
735
735
736 try:
736 try:
737 self.tearDown()
737 self.tearDown()
738 except (KeyboardInterrupt, SystemExit):
738 except (KeyboardInterrupt, SystemExit):
739 self._aborted = True
739 self._aborted = True
740 raise
740 raise
741 except Exception:
741 except Exception:
742 result.addError(self, sys.exc_info())
742 result.addError(self, sys.exc_info())
743 success = False
743 success = False
744
744
745 if success:
745 if success:
746 result.addSuccess(self)
746 result.addSuccess(self)
747 finally:
747 finally:
748 result.stopTest(self, interrupted=self._aborted)
748 result.stopTest(self, interrupted=self._aborted)
749
749
750 def runTest(self):
750 def runTest(self):
751 """Run this test instance.
751 """Run this test instance.
752
752
753 This will return a tuple describing the result of the test.
753 This will return a tuple describing the result of the test.
754 """
754 """
755 env = self._getenv()
755 env = self._getenv()
756 self._daemonpids.append(env['DAEMON_PIDS'])
756 self._daemonpids.append(env['DAEMON_PIDS'])
757 self._createhgrc(env['HGRCPATH'])
757 self._createhgrc(env['HGRCPATH'])
758
758
759 vlog('# Test', self.name)
759 vlog('# Test', self.name)
760
760
761 ret, out = self._run(env)
761 ret, out = self._run(env)
762 self._finished = True
762 self._finished = True
763 self._ret = ret
763 self._ret = ret
764 self._out = out
764 self._out = out
765
765
766 def describe(ret):
766 def describe(ret):
767 if ret < 0:
767 if ret < 0:
768 return 'killed by signal: %d' % -ret
768 return 'killed by signal: %d' % -ret
769 return 'returned error code %d' % ret
769 return 'returned error code %d' % ret
770
770
771 self._skipped = False
771 self._skipped = False
772
772
773 if ret == self.SKIPPED_STATUS:
773 if ret == self.SKIPPED_STATUS:
774 if out is None: # Debug mode, nothing to parse.
774 if out is None: # Debug mode, nothing to parse.
775 missing = ['unknown']
775 missing = ['unknown']
776 failed = None
776 failed = None
777 else:
777 else:
778 missing, failed = TTest.parsehghaveoutput(out)
778 missing, failed = TTest.parsehghaveoutput(out)
779
779
780 if not missing:
780 if not missing:
781 missing = ['skipped']
781 missing = ['skipped']
782
782
783 if failed:
783 if failed:
784 self.fail('hg have failed checking for %s' % failed[-1])
784 self.fail('hg have failed checking for %s' % failed[-1])
785 else:
785 else:
786 self._skipped = True
786 self._skipped = True
787 raise SkipTest(missing[-1])
787 raise SkipTest(missing[-1])
788 elif ret == 'timeout':
788 elif ret == 'timeout':
789 self.fail('timed out')
789 self.fail('timed out')
790 elif ret is False:
790 elif ret is False:
791 raise WarnTest('no result code from test')
791 raise WarnTest('no result code from test')
792 elif out != self._refout:
792 elif out != self._refout:
793 # Diff generation may rely on written .err file.
793 # Diff generation may rely on written .err file.
794 if (ret != 0 or out != self._refout) and not self._skipped \
794 if (ret != 0 or out != self._refout) and not self._skipped \
795 and not self._debug:
795 and not self._debug:
796 f = open(self.errpath, 'wb')
796 f = open(self.errpath, 'wb')
797 for line in out:
797 for line in out:
798 f.write(line)
798 f.write(line)
799 f.close()
799 f.close()
800
800
801 # The result object handles diff calculation for us.
801 # The result object handles diff calculation for us.
802 if self._result.addOutputMismatch(self, ret, out, self._refout):
802 if self._result.addOutputMismatch(self, ret, out, self._refout):
803 # change was accepted, skip failing
803 # change was accepted, skip failing
804 return
804 return
805
805
806 if ret:
806 if ret:
807 msg = 'output changed and ' + describe(ret)
807 msg = 'output changed and ' + describe(ret)
808 else:
808 else:
809 msg = 'output changed'
809 msg = 'output changed'
810
810
811 self.fail(msg)
811 self.fail(msg)
812 elif ret:
812 elif ret:
813 self.fail(describe(ret))
813 self.fail(describe(ret))
814
814
815 def tearDown(self):
815 def tearDown(self):
816 """Tasks to perform after run()."""
816 """Tasks to perform after run()."""
817 for entry in self._daemonpids:
817 for entry in self._daemonpids:
818 killdaemons(entry)
818 killdaemons(entry)
819 self._daemonpids = []
819 self._daemonpids = []
820
820
821 if self._keeptmpdir:
821 if self._keeptmpdir:
822 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
822 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
823 (self._testtmp.decode('utf-8'),
823 (self._testtmp.decode('utf-8'),
824 self._threadtmp.decode('utf-8')))
824 self._threadtmp.decode('utf-8')))
825 else:
825 else:
826 shutil.rmtree(self._testtmp, True)
826 shutil.rmtree(self._testtmp, True)
827 shutil.rmtree(self._threadtmp, True)
827 shutil.rmtree(self._threadtmp, True)
828
828
829 if self._usechg:
829 if self._usechg:
830 # chgservers will stop automatically after they find the socket
830 # chgservers will stop automatically after they find the socket
831 # files are deleted
831 # files are deleted
832 shutil.rmtree(self._chgsockdir, True)
832 shutil.rmtree(self._chgsockdir, True)
833
833
834 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
834 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
835 and not self._debug and self._out:
835 and not self._debug and self._out:
836 f = open(self.errpath, 'wb')
836 f = open(self.errpath, 'wb')
837 for line in self._out:
837 for line in self._out:
838 f.write(line)
838 f.write(line)
839 f.close()
839 f.close()
840
840
841 vlog("# Ret was:", self._ret, '(%s)' % self.name)
841 vlog("# Ret was:", self._ret, '(%s)' % self.name)
842
842
843 def _run(self, env):
843 def _run(self, env):
844 # This should be implemented in child classes to run tests.
844 # This should be implemented in child classes to run tests.
845 raise SkipTest('unknown test type')
845 raise SkipTest('unknown test type')
846
846
847 def abort(self):
847 def abort(self):
848 """Terminate execution of this test."""
848 """Terminate execution of this test."""
849 self._aborted = True
849 self._aborted = True
850
850
851 def _portmap(self, i):
851 def _portmap(self, i):
852 offset = b'' if i == 0 else b'%d' % i
852 offset = b'' if i == 0 else b'%d' % i
853 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
853 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
854
854
855 def _getreplacements(self):
855 def _getreplacements(self):
856 """Obtain a mapping of text replacements to apply to test output.
856 """Obtain a mapping of text replacements to apply to test output.
857
857
858 Test output needs to be normalized so it can be compared to expected
858 Test output needs to be normalized so it can be compared to expected
859 output. This function defines how some of that normalization will
859 output. This function defines how some of that normalization will
860 occur.
860 occur.
861 """
861 """
862 r = [
862 r = [
863 # This list should be parallel to defineport in _getenv
863 # This list should be parallel to defineport in _getenv
864 self._portmap(0),
864 self._portmap(0),
865 self._portmap(1),
865 self._portmap(1),
866 self._portmap(2),
866 self._portmap(2),
867 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
867 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
868 br'\1 (glob)'),
868 br'\1 (glob)'),
869 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
869 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
870 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
870 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
871 ]
871 ]
872 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
872 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
873
873
874 return r
874 return r
875
875
876 def _escapepath(self, p):
876 def _escapepath(self, p):
877 if os.name == 'nt':
877 if os.name == 'nt':
878 return (
878 return (
879 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
879 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
880 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
880 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
881 for c in p))
881 for c in p))
882 )
882 )
883 else:
883 else:
884 return re.escape(p)
884 return re.escape(p)
885
885
886 def _localip(self):
886 def _localip(self):
887 if self._useipv6:
887 if self._useipv6:
888 return b'::1'
888 return b'::1'
889 else:
889 else:
890 return b'127.0.0.1'
890 return b'127.0.0.1'
891
891
892 def _getenv(self):
892 def _getenv(self):
893 """Obtain environment variables to use during test execution."""
893 """Obtain environment variables to use during test execution."""
894 def defineport(i):
894 def defineport(i):
895 offset = '' if i == 0 else '%s' % i
895 offset = '' if i == 0 else '%s' % i
896 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
896 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
897 env = os.environ.copy()
897 env = os.environ.copy()
898 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
898 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
899 env['HGEMITWARNINGS'] = '1'
899 env['HGEMITWARNINGS'] = '1'
900 env['TESTTMP'] = self._testtmp
900 env['TESTTMP'] = self._testtmp
901 env['HOME'] = self._testtmp
901 env['HOME'] = self._testtmp
902 # This number should match portneeded in _getport
902 # This number should match portneeded in _getport
903 for port in xrange(3):
903 for port in xrange(3):
904 # This list should be parallel to _portmap in _getreplacements
904 # This list should be parallel to _portmap in _getreplacements
905 defineport(port)
905 defineport(port)
906 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
906 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
907 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
907 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
908 env["HGEDITOR"] = ('"' + sys.executable + '"'
908 env["HGEDITOR"] = ('"' + sys.executable + '"'
909 + ' -c "import sys; sys.exit(0)"')
909 + ' -c "import sys; sys.exit(0)"')
910 env["HGMERGE"] = "internal:merge"
910 env["HGMERGE"] = "internal:merge"
911 env["HGUSER"] = "test"
911 env["HGUSER"] = "test"
912 env["HGENCODING"] = "ascii"
912 env["HGENCODING"] = "ascii"
913 env["HGENCODINGMODE"] = "strict"
913 env["HGENCODINGMODE"] = "strict"
914 env['HGIPV6'] = str(int(self._useipv6))
914 env['HGIPV6'] = str(int(self._useipv6))
915
915
916 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
916 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
917 # IP addresses.
917 # IP addresses.
918 env['LOCALIP'] = self._localip()
918 env['LOCALIP'] = self._localip()
919
919
920 # Reset some environment variables to well-known values so that
920 # Reset some environment variables to well-known values so that
921 # the tests produce repeatable output.
921 # the tests produce repeatable output.
922 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
922 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
923 env['TZ'] = 'GMT'
923 env['TZ'] = 'GMT'
924 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
924 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
925 env['COLUMNS'] = '80'
925 env['COLUMNS'] = '80'
926 env['TERM'] = 'xterm'
926 env['TERM'] = 'xterm'
927
927
928 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
928 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
929 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
929 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
930 'NO_PROXY CHGDEBUG').split():
930 'NO_PROXY CHGDEBUG').split():
931 if k in env:
931 if k in env:
932 del env[k]
932 del env[k]
933
933
934 # unset env related to hooks
934 # unset env related to hooks
935 for k in env.keys():
935 for k in env.keys():
936 if k.startswith('HG_'):
936 if k.startswith('HG_'):
937 del env[k]
937 del env[k]
938
938
939 if self._usechg:
939 if self._usechg:
940 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
940 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
941
941
942 return env
942 return env
943
943
944 def _createhgrc(self, path):
944 def _createhgrc(self, path):
945 """Create an hgrc file for this test."""
945 """Create an hgrc file for this test."""
946 hgrc = open(path, 'wb')
946 hgrc = open(path, 'wb')
947 hgrc.write(b'[ui]\n')
947 hgrc.write(b'[ui]\n')
948 hgrc.write(b'slash = True\n')
948 hgrc.write(b'slash = True\n')
949 hgrc.write(b'interactive = False\n')
949 hgrc.write(b'interactive = False\n')
950 hgrc.write(b'mergemarkers = detailed\n')
950 hgrc.write(b'mergemarkers = detailed\n')
951 hgrc.write(b'promptecho = True\n')
951 hgrc.write(b'promptecho = True\n')
952 hgrc.write(b'[defaults]\n')
952 hgrc.write(b'[defaults]\n')
953 hgrc.write(b'backout = -d "0 0"\n')
954 hgrc.write(b'commit = -d "0 0"\n')
955 hgrc.write(b'shelve = --date "0 0"\n')
956 hgrc.write(b'tag = -d "0 0"\n')
957 hgrc.write(b'[devel]\n')
953 hgrc.write(b'[devel]\n')
958 hgrc.write(b'all-warnings = true\n')
954 hgrc.write(b'all-warnings = true\n')
959 hgrc.write(b'default-date = 0 0\n')
955 hgrc.write(b'default-date = 0 0\n')
960 hgrc.write(b'[largefiles]\n')
956 hgrc.write(b'[largefiles]\n')
961 hgrc.write(b'usercache = %s\n' %
957 hgrc.write(b'usercache = %s\n' %
962 (os.path.join(self._testtmp, b'.cache/largefiles')))
958 (os.path.join(self._testtmp, b'.cache/largefiles')))
963 hgrc.write(b'[web]\n')
959 hgrc.write(b'[web]\n')
964 hgrc.write(b'address = localhost\n')
960 hgrc.write(b'address = localhost\n')
965 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
961 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
966
962
967 for opt in self._extraconfigopts:
963 for opt in self._extraconfigopts:
968 section, key = opt.split('.', 1)
964 section, key = opt.split('.', 1)
969 assert '=' in key, ('extra config opt %s must '
965 assert '=' in key, ('extra config opt %s must '
970 'have an = for assignment' % opt)
966 'have an = for assignment' % opt)
971 hgrc.write(b'[%s]\n%s\n' % (section, key))
967 hgrc.write(b'[%s]\n%s\n' % (section, key))
972 hgrc.close()
968 hgrc.close()
973
969
974 def fail(self, msg):
970 def fail(self, msg):
975 # unittest differentiates between errored and failed.
971 # unittest differentiates between errored and failed.
976 # Failed is denoted by AssertionError (by default at least).
972 # Failed is denoted by AssertionError (by default at least).
977 raise AssertionError(msg)
973 raise AssertionError(msg)
978
974
979 def _runcommand(self, cmd, env, normalizenewlines=False):
975 def _runcommand(self, cmd, env, normalizenewlines=False):
980 """Run command in a sub-process, capturing the output (stdout and
976 """Run command in a sub-process, capturing the output (stdout and
981 stderr).
977 stderr).
982
978
983 Return a tuple (exitcode, output). output is None in debug mode.
979 Return a tuple (exitcode, output). output is None in debug mode.
984 """
980 """
985 if self._debug:
981 if self._debug:
986 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
982 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
987 env=env)
983 env=env)
988 ret = proc.wait()
984 ret = proc.wait()
989 return (ret, None)
985 return (ret, None)
990
986
991 proc = Popen4(cmd, self._testtmp, self._timeout, env)
987 proc = Popen4(cmd, self._testtmp, self._timeout, env)
992 def cleanup():
988 def cleanup():
993 terminate(proc)
989 terminate(proc)
994 ret = proc.wait()
990 ret = proc.wait()
995 if ret == 0:
991 if ret == 0:
996 ret = signal.SIGTERM << 8
992 ret = signal.SIGTERM << 8
997 killdaemons(env['DAEMON_PIDS'])
993 killdaemons(env['DAEMON_PIDS'])
998 return ret
994 return ret
999
995
1000 output = ''
996 output = ''
1001 proc.tochild.close()
997 proc.tochild.close()
1002
998
1003 try:
999 try:
1004 output = proc.fromchild.read()
1000 output = proc.fromchild.read()
1005 except KeyboardInterrupt:
1001 except KeyboardInterrupt:
1006 vlog('# Handling keyboard interrupt')
1002 vlog('# Handling keyboard interrupt')
1007 cleanup()
1003 cleanup()
1008 raise
1004 raise
1009
1005
1010 ret = proc.wait()
1006 ret = proc.wait()
1011 if wifexited(ret):
1007 if wifexited(ret):
1012 ret = os.WEXITSTATUS(ret)
1008 ret = os.WEXITSTATUS(ret)
1013
1009
1014 if proc.timeout:
1010 if proc.timeout:
1015 ret = 'timeout'
1011 ret = 'timeout'
1016
1012
1017 if ret:
1013 if ret:
1018 killdaemons(env['DAEMON_PIDS'])
1014 killdaemons(env['DAEMON_PIDS'])
1019
1015
1020 for s, r in self._getreplacements():
1016 for s, r in self._getreplacements():
1021 output = re.sub(s, r, output)
1017 output = re.sub(s, r, output)
1022
1018
1023 if normalizenewlines:
1019 if normalizenewlines:
1024 output = output.replace('\r\n', '\n')
1020 output = output.replace('\r\n', '\n')
1025
1021
1026 return ret, output.splitlines(True)
1022 return ret, output.splitlines(True)
1027
1023
1028 class PythonTest(Test):
1024 class PythonTest(Test):
1029 """A Python-based test."""
1025 """A Python-based test."""
1030
1026
1031 @property
1027 @property
1032 def refpath(self):
1028 def refpath(self):
1033 return os.path.join(self._testdir, b'%s.out' % self.bname)
1029 return os.path.join(self._testdir, b'%s.out' % self.bname)
1034
1030
1035 def _run(self, env):
1031 def _run(self, env):
1036 py3kswitch = self._py3kwarnings and b' -3' or b''
1032 py3kswitch = self._py3kwarnings and b' -3' or b''
1037 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1033 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1038 vlog("# Running", cmd)
1034 vlog("# Running", cmd)
1039 normalizenewlines = os.name == 'nt'
1035 normalizenewlines = os.name == 'nt'
1040 result = self._runcommand(cmd, env,
1036 result = self._runcommand(cmd, env,
1041 normalizenewlines=normalizenewlines)
1037 normalizenewlines=normalizenewlines)
1042 if self._aborted:
1038 if self._aborted:
1043 raise KeyboardInterrupt()
1039 raise KeyboardInterrupt()
1044
1040
1045 return result
1041 return result
1046
1042
1047 # Some glob patterns apply only in some circumstances, so the script
1043 # Some glob patterns apply only in some circumstances, so the script
1048 # might want to remove (glob) annotations that otherwise should be
1044 # might want to remove (glob) annotations that otherwise should be
1049 # retained.
1045 # retained.
1050 checkcodeglobpats = [
1046 checkcodeglobpats = [
1051 # On Windows it looks like \ doesn't require a (glob), but we know
1047 # On Windows it looks like \ doesn't require a (glob), but we know
1052 # better.
1048 # better.
1053 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1049 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1054 re.compile(br'^moving \S+/.*[^)]$'),
1050 re.compile(br'^moving \S+/.*[^)]$'),
1055 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1051 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1056 # Not all platforms have 127.0.0.1 as loopback (though most do),
1052 # Not all platforms have 127.0.0.1 as loopback (though most do),
1057 # so we always glob that too.
1053 # so we always glob that too.
1058 re.compile(br'.*\$LOCALIP.*$'),
1054 re.compile(br'.*\$LOCALIP.*$'),
1059 ]
1055 ]
1060
1056
1061 bchr = chr
1057 bchr = chr
1062 if PYTHON3:
1058 if PYTHON3:
1063 bchr = lambda x: bytes([x])
1059 bchr = lambda x: bytes([x])
1064
1060
1065 class TTest(Test):
1061 class TTest(Test):
1066 """A "t test" is a test backed by a .t file."""
1062 """A "t test" is a test backed by a .t file."""
1067
1063
1068 SKIPPED_PREFIX = b'skipped: '
1064 SKIPPED_PREFIX = b'skipped: '
1069 FAILED_PREFIX = b'hghave check failed: '
1065 FAILED_PREFIX = b'hghave check failed: '
1070 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1066 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1071
1067
1072 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1068 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1073 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1069 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1074 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1070 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1075
1071
1076 def __init__(self, path, *args, **kwds):
1072 def __init__(self, path, *args, **kwds):
1077 # accept an extra "case" parameter
1073 # accept an extra "case" parameter
1078 case = None
1074 case = None
1079 if 'case' in kwds:
1075 if 'case' in kwds:
1080 case = kwds.pop('case')
1076 case = kwds.pop('case')
1081 self._case = case
1077 self._case = case
1082 self._allcases = parsettestcases(path)
1078 self._allcases = parsettestcases(path)
1083 super(TTest, self).__init__(path, *args, **kwds)
1079 super(TTest, self).__init__(path, *args, **kwds)
1084 if case:
1080 if case:
1085 self.name = '%s (case %s)' % (self.name, _strpath(case))
1081 self.name = '%s (case %s)' % (self.name, _strpath(case))
1086 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1082 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1087 self._tmpname += b'-%s' % case
1083 self._tmpname += b'-%s' % case
1088
1084
1089 @property
1085 @property
1090 def refpath(self):
1086 def refpath(self):
1091 return os.path.join(self._testdir, self.bname)
1087 return os.path.join(self._testdir, self.bname)
1092
1088
1093 def _run(self, env):
1089 def _run(self, env):
1094 f = open(self.path, 'rb')
1090 f = open(self.path, 'rb')
1095 lines = f.readlines()
1091 lines = f.readlines()
1096 f.close()
1092 f.close()
1097
1093
1098 salt, script, after, expected = self._parsetest(lines)
1094 salt, script, after, expected = self._parsetest(lines)
1099
1095
1100 # Write out the generated script.
1096 # Write out the generated script.
1101 fname = b'%s.sh' % self._testtmp
1097 fname = b'%s.sh' % self._testtmp
1102 f = open(fname, 'wb')
1098 f = open(fname, 'wb')
1103 for l in script:
1099 for l in script:
1104 f.write(l)
1100 f.write(l)
1105 f.close()
1101 f.close()
1106
1102
1107 cmd = b'%s "%s"' % (self._shell, fname)
1103 cmd = b'%s "%s"' % (self._shell, fname)
1108 vlog("# Running", cmd)
1104 vlog("# Running", cmd)
1109
1105
1110 exitcode, output = self._runcommand(cmd, env)
1106 exitcode, output = self._runcommand(cmd, env)
1111
1107
1112 if self._aborted:
1108 if self._aborted:
1113 raise KeyboardInterrupt()
1109 raise KeyboardInterrupt()
1114
1110
1115 # Do not merge output if skipped. Return hghave message instead.
1111 # Do not merge output if skipped. Return hghave message instead.
1116 # Similarly, with --debug, output is None.
1112 # Similarly, with --debug, output is None.
1117 if exitcode == self.SKIPPED_STATUS or output is None:
1113 if exitcode == self.SKIPPED_STATUS or output is None:
1118 return exitcode, output
1114 return exitcode, output
1119
1115
1120 return self._processoutput(exitcode, output, salt, after, expected)
1116 return self._processoutput(exitcode, output, salt, after, expected)
1121
1117
1122 def _hghave(self, reqs):
1118 def _hghave(self, reqs):
1123 # TODO do something smarter when all other uses of hghave are gone.
1119 # TODO do something smarter when all other uses of hghave are gone.
1124 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1120 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1125 tdir = runtestdir.replace(b'\\', b'/')
1121 tdir = runtestdir.replace(b'\\', b'/')
1126 proc = Popen4(b'%s -c "%s/hghave %s"' %
1122 proc = Popen4(b'%s -c "%s/hghave %s"' %
1127 (self._shell, tdir, b' '.join(reqs)),
1123 (self._shell, tdir, b' '.join(reqs)),
1128 self._testtmp, 0, self._getenv())
1124 self._testtmp, 0, self._getenv())
1129 stdout, stderr = proc.communicate()
1125 stdout, stderr = proc.communicate()
1130 ret = proc.wait()
1126 ret = proc.wait()
1131 if wifexited(ret):
1127 if wifexited(ret):
1132 ret = os.WEXITSTATUS(ret)
1128 ret = os.WEXITSTATUS(ret)
1133 if ret == 2:
1129 if ret == 2:
1134 print(stdout.decode('utf-8'))
1130 print(stdout.decode('utf-8'))
1135 sys.exit(1)
1131 sys.exit(1)
1136
1132
1137 if ret != 0:
1133 if ret != 0:
1138 return False, stdout
1134 return False, stdout
1139
1135
1140 if 'slow' in reqs:
1136 if 'slow' in reqs:
1141 self._timeout = self._slowtimeout
1137 self._timeout = self._slowtimeout
1142 return True, None
1138 return True, None
1143
1139
1144 def _iftest(self, args):
1140 def _iftest(self, args):
1145 # implements "#if"
1141 # implements "#if"
1146 reqs = []
1142 reqs = []
1147 for arg in args:
1143 for arg in args:
1148 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1144 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1149 if arg[3:] == self._case:
1145 if arg[3:] == self._case:
1150 return False
1146 return False
1151 elif arg in self._allcases:
1147 elif arg in self._allcases:
1152 if arg != self._case:
1148 if arg != self._case:
1153 return False
1149 return False
1154 else:
1150 else:
1155 reqs.append(arg)
1151 reqs.append(arg)
1156 return self._hghave(reqs)[0]
1152 return self._hghave(reqs)[0]
1157
1153
1158 def _parsetest(self, lines):
1154 def _parsetest(self, lines):
1159 # We generate a shell script which outputs unique markers to line
1155 # We generate a shell script which outputs unique markers to line
1160 # up script results with our source. These markers include input
1156 # up script results with our source. These markers include input
1161 # line number and the last return code.
1157 # line number and the last return code.
1162 salt = b"SALT%d" % time.time()
1158 salt = b"SALT%d" % time.time()
1163 def addsalt(line, inpython):
1159 def addsalt(line, inpython):
1164 if inpython:
1160 if inpython:
1165 script.append(b'%s %d 0\n' % (salt, line))
1161 script.append(b'%s %d 0\n' % (salt, line))
1166 else:
1162 else:
1167 script.append(b'echo %s %d $?\n' % (salt, line))
1163 script.append(b'echo %s %d $?\n' % (salt, line))
1168
1164
1169 script = []
1165 script = []
1170
1166
1171 # After we run the shell script, we re-unify the script output
1167 # After we run the shell script, we re-unify the script output
1172 # with non-active parts of the source, with synchronization by our
1168 # with non-active parts of the source, with synchronization by our
1173 # SALT line number markers. The after table contains the non-active
1169 # SALT line number markers. The after table contains the non-active
1174 # components, ordered by line number.
1170 # components, ordered by line number.
1175 after = {}
1171 after = {}
1176
1172
1177 # Expected shell script output.
1173 # Expected shell script output.
1178 expected = {}
1174 expected = {}
1179
1175
1180 pos = prepos = -1
1176 pos = prepos = -1
1181
1177
1182 # True or False when in a true or false conditional section
1178 # True or False when in a true or false conditional section
1183 skipping = None
1179 skipping = None
1184
1180
1185 # We keep track of whether or not we're in a Python block so we
1181 # We keep track of whether or not we're in a Python block so we
1186 # can generate the surrounding doctest magic.
1182 # can generate the surrounding doctest magic.
1187 inpython = False
1183 inpython = False
1188
1184
1189 if self._debug:
1185 if self._debug:
1190 script.append(b'set -x\n')
1186 script.append(b'set -x\n')
1191 if self._hgcommand != b'hg':
1187 if self._hgcommand != b'hg':
1192 script.append(b'alias hg="%s"\n' % self._hgcommand)
1188 script.append(b'alias hg="%s"\n' % self._hgcommand)
1193 if os.getenv('MSYSTEM'):
1189 if os.getenv('MSYSTEM'):
1194 script.append(b'alias pwd="pwd -W"\n')
1190 script.append(b'alias pwd="pwd -W"\n')
1195
1191
1196 n = 0
1192 n = 0
1197 for n, l in enumerate(lines):
1193 for n, l in enumerate(lines):
1198 if not l.endswith(b'\n'):
1194 if not l.endswith(b'\n'):
1199 l += b'\n'
1195 l += b'\n'
1200 if l.startswith(b'#require'):
1196 if l.startswith(b'#require'):
1201 lsplit = l.split()
1197 lsplit = l.split()
1202 if len(lsplit) < 2 or lsplit[0] != b'#require':
1198 if len(lsplit) < 2 or lsplit[0] != b'#require':
1203 after.setdefault(pos, []).append(' !!! invalid #require\n')
1199 after.setdefault(pos, []).append(' !!! invalid #require\n')
1204 haveresult, message = self._hghave(lsplit[1:])
1200 haveresult, message = self._hghave(lsplit[1:])
1205 if not haveresult:
1201 if not haveresult:
1206 script = [b'echo "%s"\nexit 80\n' % message]
1202 script = [b'echo "%s"\nexit 80\n' % message]
1207 break
1203 break
1208 after.setdefault(pos, []).append(l)
1204 after.setdefault(pos, []).append(l)
1209 elif l.startswith(b'#if'):
1205 elif l.startswith(b'#if'):
1210 lsplit = l.split()
1206 lsplit = l.split()
1211 if len(lsplit) < 2 or lsplit[0] != b'#if':
1207 if len(lsplit) < 2 or lsplit[0] != b'#if':
1212 after.setdefault(pos, []).append(' !!! invalid #if\n')
1208 after.setdefault(pos, []).append(' !!! invalid #if\n')
1213 if skipping is not None:
1209 if skipping is not None:
1214 after.setdefault(pos, []).append(' !!! nested #if\n')
1210 after.setdefault(pos, []).append(' !!! nested #if\n')
1215 skipping = not self._iftest(lsplit[1:])
1211 skipping = not self._iftest(lsplit[1:])
1216 after.setdefault(pos, []).append(l)
1212 after.setdefault(pos, []).append(l)
1217 elif l.startswith(b'#else'):
1213 elif l.startswith(b'#else'):
1218 if skipping is None:
1214 if skipping is None:
1219 after.setdefault(pos, []).append(' !!! missing #if\n')
1215 after.setdefault(pos, []).append(' !!! missing #if\n')
1220 skipping = not skipping
1216 skipping = not skipping
1221 after.setdefault(pos, []).append(l)
1217 after.setdefault(pos, []).append(l)
1222 elif l.startswith(b'#endif'):
1218 elif l.startswith(b'#endif'):
1223 if skipping is None:
1219 if skipping is None:
1224 after.setdefault(pos, []).append(' !!! missing #if\n')
1220 after.setdefault(pos, []).append(' !!! missing #if\n')
1225 skipping = None
1221 skipping = None
1226 after.setdefault(pos, []).append(l)
1222 after.setdefault(pos, []).append(l)
1227 elif skipping:
1223 elif skipping:
1228 after.setdefault(pos, []).append(l)
1224 after.setdefault(pos, []).append(l)
1229 elif l.startswith(b' >>> '): # python inlines
1225 elif l.startswith(b' >>> '): # python inlines
1230 after.setdefault(pos, []).append(l)
1226 after.setdefault(pos, []).append(l)
1231 prepos = pos
1227 prepos = pos
1232 pos = n
1228 pos = n
1233 if not inpython:
1229 if not inpython:
1234 # We've just entered a Python block. Add the header.
1230 # We've just entered a Python block. Add the header.
1235 inpython = True
1231 inpython = True
1236 addsalt(prepos, False) # Make sure we report the exit code.
1232 addsalt(prepos, False) # Make sure we report the exit code.
1237 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1233 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1238 addsalt(n, True)
1234 addsalt(n, True)
1239 script.append(l[2:])
1235 script.append(l[2:])
1240 elif l.startswith(b' ... '): # python inlines
1236 elif l.startswith(b' ... '): # python inlines
1241 after.setdefault(prepos, []).append(l)
1237 after.setdefault(prepos, []).append(l)
1242 script.append(l[2:])
1238 script.append(l[2:])
1243 elif l.startswith(b' $ '): # commands
1239 elif l.startswith(b' $ '): # commands
1244 if inpython:
1240 if inpython:
1245 script.append(b'EOF\n')
1241 script.append(b'EOF\n')
1246 inpython = False
1242 inpython = False
1247 after.setdefault(pos, []).append(l)
1243 after.setdefault(pos, []).append(l)
1248 prepos = pos
1244 prepos = pos
1249 pos = n
1245 pos = n
1250 addsalt(n, False)
1246 addsalt(n, False)
1251 cmd = l[4:].split()
1247 cmd = l[4:].split()
1252 if len(cmd) == 2 and cmd[0] == b'cd':
1248 if len(cmd) == 2 and cmd[0] == b'cd':
1253 l = b' $ cd %s || exit 1\n' % cmd[1]
1249 l = b' $ cd %s || exit 1\n' % cmd[1]
1254 script.append(l[4:])
1250 script.append(l[4:])
1255 elif l.startswith(b' > '): # continuations
1251 elif l.startswith(b' > '): # continuations
1256 after.setdefault(prepos, []).append(l)
1252 after.setdefault(prepos, []).append(l)
1257 script.append(l[4:])
1253 script.append(l[4:])
1258 elif l.startswith(b' '): # results
1254 elif l.startswith(b' '): # results
1259 # Queue up a list of expected results.
1255 # Queue up a list of expected results.
1260 expected.setdefault(pos, []).append(l[2:])
1256 expected.setdefault(pos, []).append(l[2:])
1261 else:
1257 else:
1262 if inpython:
1258 if inpython:
1263 script.append(b'EOF\n')
1259 script.append(b'EOF\n')
1264 inpython = False
1260 inpython = False
1265 # Non-command/result. Queue up for merged output.
1261 # Non-command/result. Queue up for merged output.
1266 after.setdefault(pos, []).append(l)
1262 after.setdefault(pos, []).append(l)
1267
1263
1268 if inpython:
1264 if inpython:
1269 script.append(b'EOF\n')
1265 script.append(b'EOF\n')
1270 if skipping is not None:
1266 if skipping is not None:
1271 after.setdefault(pos, []).append(' !!! missing #endif\n')
1267 after.setdefault(pos, []).append(' !!! missing #endif\n')
1272 addsalt(n + 1, False)
1268 addsalt(n + 1, False)
1273
1269
1274 return salt, script, after, expected
1270 return salt, script, after, expected
1275
1271
1276 def _processoutput(self, exitcode, output, salt, after, expected):
1272 def _processoutput(self, exitcode, output, salt, after, expected):
1277 # Merge the script output back into a unified test.
1273 # Merge the script output back into a unified test.
1278 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1274 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1279 if exitcode != 0:
1275 if exitcode != 0:
1280 warnonly = 3
1276 warnonly = 3
1281
1277
1282 pos = -1
1278 pos = -1
1283 postout = []
1279 postout = []
1284 for l in output:
1280 for l in output:
1285 lout, lcmd = l, None
1281 lout, lcmd = l, None
1286 if salt in l:
1282 if salt in l:
1287 lout, lcmd = l.split(salt, 1)
1283 lout, lcmd = l.split(salt, 1)
1288
1284
1289 while lout:
1285 while lout:
1290 if not lout.endswith(b'\n'):
1286 if not lout.endswith(b'\n'):
1291 lout += b' (no-eol)\n'
1287 lout += b' (no-eol)\n'
1292
1288
1293 # Find the expected output at the current position.
1289 # Find the expected output at the current position.
1294 els = [None]
1290 els = [None]
1295 if expected.get(pos, None):
1291 if expected.get(pos, None):
1296 els = expected[pos]
1292 els = expected[pos]
1297
1293
1298 i = 0
1294 i = 0
1299 optional = []
1295 optional = []
1300 while i < len(els):
1296 while i < len(els):
1301 el = els[i]
1297 el = els[i]
1302
1298
1303 r = TTest.linematch(el, lout)
1299 r = TTest.linematch(el, lout)
1304 if isinstance(r, str):
1300 if isinstance(r, str):
1305 if r == '+glob':
1301 if r == '+glob':
1306 lout = el[:-1] + ' (glob)\n'
1302 lout = el[:-1] + ' (glob)\n'
1307 r = '' # Warn only this line.
1303 r = '' # Warn only this line.
1308 elif r == '-glob':
1304 elif r == '-glob':
1309 lout = ''.join(el.rsplit(' (glob)', 1))
1305 lout = ''.join(el.rsplit(' (glob)', 1))
1310 r = '' # Warn only this line.
1306 r = '' # Warn only this line.
1311 elif r == "retry":
1307 elif r == "retry":
1312 postout.append(b' ' + el)
1308 postout.append(b' ' + el)
1313 els.pop(i)
1309 els.pop(i)
1314 break
1310 break
1315 else:
1311 else:
1316 log('\ninfo, unknown linematch result: %r\n' % r)
1312 log('\ninfo, unknown linematch result: %r\n' % r)
1317 r = False
1313 r = False
1318 if r:
1314 if r:
1319 els.pop(i)
1315 els.pop(i)
1320 break
1316 break
1321 if el:
1317 if el:
1322 if el.endswith(b" (?)\n"):
1318 if el.endswith(b" (?)\n"):
1323 optional.append(i)
1319 optional.append(i)
1324 else:
1320 else:
1325 m = optline.match(el)
1321 m = optline.match(el)
1326 if m:
1322 if m:
1327 conditions = [c for c in m.group(2).split(' ')]
1323 conditions = [c for c in m.group(2).split(' ')]
1328
1324
1329 if self._hghave(conditions)[0]:
1325 if self._hghave(conditions)[0]:
1330 lout = el
1326 lout = el
1331 else:
1327 else:
1332 optional.append(i)
1328 optional.append(i)
1333
1329
1334 i += 1
1330 i += 1
1335
1331
1336 if r:
1332 if r:
1337 if r == "retry":
1333 if r == "retry":
1338 continue
1334 continue
1339 # clean up any optional leftovers
1335 # clean up any optional leftovers
1340 for i in optional:
1336 for i in optional:
1341 postout.append(b' ' + els[i])
1337 postout.append(b' ' + els[i])
1342 for i in reversed(optional):
1338 for i in reversed(optional):
1343 del els[i]
1339 del els[i]
1344 postout.append(b' ' + el)
1340 postout.append(b' ' + el)
1345 else:
1341 else:
1346 if self.NEEDESCAPE(lout):
1342 if self.NEEDESCAPE(lout):
1347 lout = TTest._stringescape(b'%s (esc)\n' %
1343 lout = TTest._stringescape(b'%s (esc)\n' %
1348 lout.rstrip(b'\n'))
1344 lout.rstrip(b'\n'))
1349 postout.append(b' ' + lout) # Let diff deal with it.
1345 postout.append(b' ' + lout) # Let diff deal with it.
1350 if r != '': # If line failed.
1346 if r != '': # If line failed.
1351 warnonly = 3 # for sure not
1347 warnonly = 3 # for sure not
1352 elif warnonly == 1: # Is "not yet" and line is warn only.
1348 elif warnonly == 1: # Is "not yet" and line is warn only.
1353 warnonly = 2 # Yes do warn.
1349 warnonly = 2 # Yes do warn.
1354 break
1350 break
1355 else:
1351 else:
1356 # clean up any optional leftovers
1352 # clean up any optional leftovers
1357 while expected.get(pos, None):
1353 while expected.get(pos, None):
1358 el = expected[pos].pop(0)
1354 el = expected[pos].pop(0)
1359 if el:
1355 if el:
1360 if (not optline.match(el)
1356 if (not optline.match(el)
1361 and not el.endswith(b" (?)\n")):
1357 and not el.endswith(b" (?)\n")):
1362 break
1358 break
1363 postout.append(b' ' + el)
1359 postout.append(b' ' + el)
1364
1360
1365 if lcmd:
1361 if lcmd:
1366 # Add on last return code.
1362 # Add on last return code.
1367 ret = int(lcmd.split()[1])
1363 ret = int(lcmd.split()[1])
1368 if ret != 0:
1364 if ret != 0:
1369 postout.append(b' [%d]\n' % ret)
1365 postout.append(b' [%d]\n' % ret)
1370 if pos in after:
1366 if pos in after:
1371 # Merge in non-active test bits.
1367 # Merge in non-active test bits.
1372 postout += after.pop(pos)
1368 postout += after.pop(pos)
1373 pos = int(lcmd.split()[0])
1369 pos = int(lcmd.split()[0])
1374
1370
1375 if pos in after:
1371 if pos in after:
1376 postout += after.pop(pos)
1372 postout += after.pop(pos)
1377
1373
1378 if warnonly == 2:
1374 if warnonly == 2:
1379 exitcode = False # Set exitcode to warned.
1375 exitcode = False # Set exitcode to warned.
1380
1376
1381 return exitcode, postout
1377 return exitcode, postout
1382
1378
1383 @staticmethod
1379 @staticmethod
1384 def rematch(el, l):
1380 def rematch(el, l):
1385 try:
1381 try:
1386 # use \Z to ensure that the regex matches to the end of the string
1382 # use \Z to ensure that the regex matches to the end of the string
1387 if os.name == 'nt':
1383 if os.name == 'nt':
1388 return re.match(el + br'\r?\n\Z', l)
1384 return re.match(el + br'\r?\n\Z', l)
1389 return re.match(el + br'\n\Z', l)
1385 return re.match(el + br'\n\Z', l)
1390 except re.error:
1386 except re.error:
1391 # el is an invalid regex
1387 # el is an invalid regex
1392 return False
1388 return False
1393
1389
1394 @staticmethod
1390 @staticmethod
1395 def globmatch(el, l):
1391 def globmatch(el, l):
1396 # The only supported special characters are * and ? plus / which also
1392 # The only supported special characters are * and ? plus / which also
1397 # matches \ on windows. Escaping of these characters is supported.
1393 # matches \ on windows. Escaping of these characters is supported.
1398 if el + b'\n' == l:
1394 if el + b'\n' == l:
1399 if os.altsep:
1395 if os.altsep:
1400 # matching on "/" is not needed for this line
1396 # matching on "/" is not needed for this line
1401 for pat in checkcodeglobpats:
1397 for pat in checkcodeglobpats:
1402 if pat.match(el):
1398 if pat.match(el):
1403 return True
1399 return True
1404 return b'-glob'
1400 return b'-glob'
1405 return True
1401 return True
1406 el = el.replace(b'$LOCALIP', b'*')
1402 el = el.replace(b'$LOCALIP', b'*')
1407 i, n = 0, len(el)
1403 i, n = 0, len(el)
1408 res = b''
1404 res = b''
1409 while i < n:
1405 while i < n:
1410 c = el[i:i + 1]
1406 c = el[i:i + 1]
1411 i += 1
1407 i += 1
1412 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1408 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1413 res += el[i - 1:i + 1]
1409 res += el[i - 1:i + 1]
1414 i += 1
1410 i += 1
1415 elif c == b'*':
1411 elif c == b'*':
1416 res += b'.*'
1412 res += b'.*'
1417 elif c == b'?':
1413 elif c == b'?':
1418 res += b'.'
1414 res += b'.'
1419 elif c == b'/' and os.altsep:
1415 elif c == b'/' and os.altsep:
1420 res += b'[/\\\\]'
1416 res += b'[/\\\\]'
1421 else:
1417 else:
1422 res += re.escape(c)
1418 res += re.escape(c)
1423 return TTest.rematch(res, l)
1419 return TTest.rematch(res, l)
1424
1420
1425 @staticmethod
1421 @staticmethod
1426 def linematch(el, l):
1422 def linematch(el, l):
1427 retry = False
1423 retry = False
1428 if el == l: # perfect match (fast)
1424 if el == l: # perfect match (fast)
1429 return True
1425 return True
1430 if el:
1426 if el:
1431 if el.endswith(b" (?)\n"):
1427 if el.endswith(b" (?)\n"):
1432 retry = "retry"
1428 retry = "retry"
1433 el = el[:-5] + b"\n"
1429 el = el[:-5] + b"\n"
1434 else:
1430 else:
1435 m = optline.match(el)
1431 m = optline.match(el)
1436 if m:
1432 if m:
1437 el = m.group(1) + b"\n"
1433 el = m.group(1) + b"\n"
1438 retry = "retry"
1434 retry = "retry"
1439
1435
1440 if el.endswith(b" (esc)\n"):
1436 if el.endswith(b" (esc)\n"):
1441 if PYTHON3:
1437 if PYTHON3:
1442 el = el[:-7].decode('unicode_escape') + '\n'
1438 el = el[:-7].decode('unicode_escape') + '\n'
1443 el = el.encode('utf-8')
1439 el = el.encode('utf-8')
1444 else:
1440 else:
1445 el = el[:-7].decode('string-escape') + '\n'
1441 el = el[:-7].decode('string-escape') + '\n'
1446 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1442 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1447 return True
1443 return True
1448 if el.endswith(b" (re)\n"):
1444 if el.endswith(b" (re)\n"):
1449 return TTest.rematch(el[:-6], l) or retry
1445 return TTest.rematch(el[:-6], l) or retry
1450 if el.endswith(b" (glob)\n"):
1446 if el.endswith(b" (glob)\n"):
1451 # ignore '(glob)' added to l by 'replacements'
1447 # ignore '(glob)' added to l by 'replacements'
1452 if l.endswith(b" (glob)\n"):
1448 if l.endswith(b" (glob)\n"):
1453 l = l[:-8] + b"\n"
1449 l = l[:-8] + b"\n"
1454 return TTest.globmatch(el[:-8], l) or retry
1450 return TTest.globmatch(el[:-8], l) or retry
1455 if os.altsep and l.replace(b'\\', b'/') == el:
1451 if os.altsep and l.replace(b'\\', b'/') == el:
1456 return b'+glob'
1452 return b'+glob'
1457 return retry
1453 return retry
1458
1454
1459 @staticmethod
1455 @staticmethod
1460 def parsehghaveoutput(lines):
1456 def parsehghaveoutput(lines):
1461 '''Parse hghave log lines.
1457 '''Parse hghave log lines.
1462
1458
1463 Return tuple of lists (missing, failed):
1459 Return tuple of lists (missing, failed):
1464 * the missing/unknown features
1460 * the missing/unknown features
1465 * the features for which existence check failed'''
1461 * the features for which existence check failed'''
1466 missing = []
1462 missing = []
1467 failed = []
1463 failed = []
1468 for line in lines:
1464 for line in lines:
1469 if line.startswith(TTest.SKIPPED_PREFIX):
1465 if line.startswith(TTest.SKIPPED_PREFIX):
1470 line = line.splitlines()[0]
1466 line = line.splitlines()[0]
1471 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1467 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1472 elif line.startswith(TTest.FAILED_PREFIX):
1468 elif line.startswith(TTest.FAILED_PREFIX):
1473 line = line.splitlines()[0]
1469 line = line.splitlines()[0]
1474 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1470 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1475
1471
1476 return missing, failed
1472 return missing, failed
1477
1473
1478 @staticmethod
1474 @staticmethod
1479 def _escapef(m):
1475 def _escapef(m):
1480 return TTest.ESCAPEMAP[m.group(0)]
1476 return TTest.ESCAPEMAP[m.group(0)]
1481
1477
1482 @staticmethod
1478 @staticmethod
1483 def _stringescape(s):
1479 def _stringescape(s):
1484 return TTest.ESCAPESUB(TTest._escapef, s)
1480 return TTest.ESCAPESUB(TTest._escapef, s)
1485
1481
1486 iolock = threading.RLock()
1482 iolock = threading.RLock()
1487
1483
1488 class SkipTest(Exception):
1484 class SkipTest(Exception):
1489 """Raised to indicate that a test is to be skipped."""
1485 """Raised to indicate that a test is to be skipped."""
1490
1486
1491 class IgnoreTest(Exception):
1487 class IgnoreTest(Exception):
1492 """Raised to indicate that a test is to be ignored."""
1488 """Raised to indicate that a test is to be ignored."""
1493
1489
1494 class WarnTest(Exception):
1490 class WarnTest(Exception):
1495 """Raised to indicate that a test warned."""
1491 """Raised to indicate that a test warned."""
1496
1492
1497 class ReportedTest(Exception):
1493 class ReportedTest(Exception):
1498 """Raised to indicate that a test already reported."""
1494 """Raised to indicate that a test already reported."""
1499
1495
1500 class TestResult(unittest._TextTestResult):
1496 class TestResult(unittest._TextTestResult):
1501 """Holds results when executing via unittest."""
1497 """Holds results when executing via unittest."""
1502 # Don't worry too much about accessing the non-public _TextTestResult.
1498 # Don't worry too much about accessing the non-public _TextTestResult.
1503 # It is relatively common in Python testing tools.
1499 # It is relatively common in Python testing tools.
1504 def __init__(self, options, *args, **kwargs):
1500 def __init__(self, options, *args, **kwargs):
1505 super(TestResult, self).__init__(*args, **kwargs)
1501 super(TestResult, self).__init__(*args, **kwargs)
1506
1502
1507 self._options = options
1503 self._options = options
1508
1504
1509 # unittest.TestResult didn't have skipped until 2.7. We need to
1505 # unittest.TestResult didn't have skipped until 2.7. We need to
1510 # polyfill it.
1506 # polyfill it.
1511 self.skipped = []
1507 self.skipped = []
1512
1508
1513 # We have a custom "ignored" result that isn't present in any Python
1509 # We have a custom "ignored" result that isn't present in any Python
1514 # unittest implementation. It is very similar to skipped. It may make
1510 # unittest implementation. It is very similar to skipped. It may make
1515 # sense to map it into skip some day.
1511 # sense to map it into skip some day.
1516 self.ignored = []
1512 self.ignored = []
1517
1513
1518 # We have a custom "warned" result that isn't present in any Python
1514 # We have a custom "warned" result that isn't present in any Python
1519 # unittest implementation. It is very similar to failed. It may make
1515 # unittest implementation. It is very similar to failed. It may make
1520 # sense to map it into fail some day.
1516 # sense to map it into fail some day.
1521 self.warned = []
1517 self.warned = []
1522
1518
1523 self.times = []
1519 self.times = []
1524 self._firststarttime = None
1520 self._firststarttime = None
1525 # Data stored for the benefit of generating xunit reports.
1521 # Data stored for the benefit of generating xunit reports.
1526 self.successes = []
1522 self.successes = []
1527 self.faildata = {}
1523 self.faildata = {}
1528
1524
1529 def addFailure(self, test, reason):
1525 def addFailure(self, test, reason):
1530 self.failures.append((test, reason))
1526 self.failures.append((test, reason))
1531
1527
1532 if self._options.first:
1528 if self._options.first:
1533 self.stop()
1529 self.stop()
1534 else:
1530 else:
1535 with iolock:
1531 with iolock:
1536 if reason == "timed out":
1532 if reason == "timed out":
1537 self.stream.write('t')
1533 self.stream.write('t')
1538 else:
1534 else:
1539 if not self._options.nodiff:
1535 if not self._options.nodiff:
1540 self.stream.write('\nERROR: %s output changed\n' % test)
1536 self.stream.write('\nERROR: %s output changed\n' % test)
1541 self.stream.write('!')
1537 self.stream.write('!')
1542
1538
1543 self.stream.flush()
1539 self.stream.flush()
1544
1540
1545 def addSuccess(self, test):
1541 def addSuccess(self, test):
1546 with iolock:
1542 with iolock:
1547 super(TestResult, self).addSuccess(test)
1543 super(TestResult, self).addSuccess(test)
1548 self.successes.append(test)
1544 self.successes.append(test)
1549
1545
1550 def addError(self, test, err):
1546 def addError(self, test, err):
1551 super(TestResult, self).addError(test, err)
1547 super(TestResult, self).addError(test, err)
1552 if self._options.first:
1548 if self._options.first:
1553 self.stop()
1549 self.stop()
1554
1550
1555 # Polyfill.
1551 # Polyfill.
1556 def addSkip(self, test, reason):
1552 def addSkip(self, test, reason):
1557 self.skipped.append((test, reason))
1553 self.skipped.append((test, reason))
1558 with iolock:
1554 with iolock:
1559 if self.showAll:
1555 if self.showAll:
1560 self.stream.writeln('skipped %s' % reason)
1556 self.stream.writeln('skipped %s' % reason)
1561 else:
1557 else:
1562 self.stream.write('s')
1558 self.stream.write('s')
1563 self.stream.flush()
1559 self.stream.flush()
1564
1560
1565 def addIgnore(self, test, reason):
1561 def addIgnore(self, test, reason):
1566 self.ignored.append((test, reason))
1562 self.ignored.append((test, reason))
1567 with iolock:
1563 with iolock:
1568 if self.showAll:
1564 if self.showAll:
1569 self.stream.writeln('ignored %s' % reason)
1565 self.stream.writeln('ignored %s' % reason)
1570 else:
1566 else:
1571 if reason not in ('not retesting', "doesn't match keyword"):
1567 if reason not in ('not retesting', "doesn't match keyword"):
1572 self.stream.write('i')
1568 self.stream.write('i')
1573 else:
1569 else:
1574 self.testsRun += 1
1570 self.testsRun += 1
1575 self.stream.flush()
1571 self.stream.flush()
1576
1572
1577 def addWarn(self, test, reason):
1573 def addWarn(self, test, reason):
1578 self.warned.append((test, reason))
1574 self.warned.append((test, reason))
1579
1575
1580 if self._options.first:
1576 if self._options.first:
1581 self.stop()
1577 self.stop()
1582
1578
1583 with iolock:
1579 with iolock:
1584 if self.showAll:
1580 if self.showAll:
1585 self.stream.writeln('warned %s' % reason)
1581 self.stream.writeln('warned %s' % reason)
1586 else:
1582 else:
1587 self.stream.write('~')
1583 self.stream.write('~')
1588 self.stream.flush()
1584 self.stream.flush()
1589
1585
1590 def addOutputMismatch(self, test, ret, got, expected):
1586 def addOutputMismatch(self, test, ret, got, expected):
1591 """Record a mismatch in test output for a particular test."""
1587 """Record a mismatch in test output for a particular test."""
1592 if self.shouldStop:
1588 if self.shouldStop:
1593 # don't print, some other test case already failed and
1589 # don't print, some other test case already failed and
1594 # printed, we're just stale and probably failed due to our
1590 # printed, we're just stale and probably failed due to our
1595 # temp dir getting cleaned up.
1591 # temp dir getting cleaned up.
1596 return
1592 return
1597
1593
1598 accepted = False
1594 accepted = False
1599 lines = []
1595 lines = []
1600
1596
1601 with iolock:
1597 with iolock:
1602 if self._options.nodiff:
1598 if self._options.nodiff:
1603 pass
1599 pass
1604 elif self._options.view:
1600 elif self._options.view:
1605 v = self._options.view
1601 v = self._options.view
1606 if PYTHON3:
1602 if PYTHON3:
1607 v = _bytespath(v)
1603 v = _bytespath(v)
1608 os.system(b"%s %s %s" %
1604 os.system(b"%s %s %s" %
1609 (v, test.refpath, test.errpath))
1605 (v, test.refpath, test.errpath))
1610 else:
1606 else:
1611 servefail, lines = getdiff(expected, got,
1607 servefail, lines = getdiff(expected, got,
1612 test.refpath, test.errpath)
1608 test.refpath, test.errpath)
1613 if servefail:
1609 if servefail:
1614 self.addFailure(
1610 self.addFailure(
1615 test,
1611 test,
1616 'server failed to start (HGPORT=%s)' % test._startport)
1612 'server failed to start (HGPORT=%s)' % test._startport)
1617 raise ReportedTest('server failed to start')
1613 raise ReportedTest('server failed to start')
1618 else:
1614 else:
1619 self.stream.write('\n')
1615 self.stream.write('\n')
1620 for line in lines:
1616 for line in lines:
1621 if PYTHON3:
1617 if PYTHON3:
1622 self.stream.flush()
1618 self.stream.flush()
1623 self.stream.buffer.write(line)
1619 self.stream.buffer.write(line)
1624 self.stream.buffer.flush()
1620 self.stream.buffer.flush()
1625 else:
1621 else:
1626 self.stream.write(line)
1622 self.stream.write(line)
1627 self.stream.flush()
1623 self.stream.flush()
1628
1624
1629 # handle interactive prompt without releasing iolock
1625 # handle interactive prompt without releasing iolock
1630 if self._options.interactive:
1626 if self._options.interactive:
1631 self.stream.write('Accept this change? [n] ')
1627 self.stream.write('Accept this change? [n] ')
1632 answer = sys.stdin.readline().strip()
1628 answer = sys.stdin.readline().strip()
1633 if answer.lower() in ('y', 'yes'):
1629 if answer.lower() in ('y', 'yes'):
1634 if test.name.endswith('.t'):
1630 if test.name.endswith('.t'):
1635 rename(test.errpath, test.path)
1631 rename(test.errpath, test.path)
1636 else:
1632 else:
1637 rename(test.errpath, '%s.out' % test.path)
1633 rename(test.errpath, '%s.out' % test.path)
1638 accepted = True
1634 accepted = True
1639 if not accepted:
1635 if not accepted:
1640 self.faildata[test.name] = b''.join(lines)
1636 self.faildata[test.name] = b''.join(lines)
1641
1637
1642 return accepted
1638 return accepted
1643
1639
1644 def startTest(self, test):
1640 def startTest(self, test):
1645 super(TestResult, self).startTest(test)
1641 super(TestResult, self).startTest(test)
1646
1642
1647 # os.times module computes the user time and system time spent by
1643 # os.times module computes the user time and system time spent by
1648 # child's processes along with real elapsed time taken by a process.
1644 # child's processes along with real elapsed time taken by a process.
1649 # This module has one limitation. It can only work for Linux user
1645 # This module has one limitation. It can only work for Linux user
1650 # and not for Windows.
1646 # and not for Windows.
1651 test.started = os.times()
1647 test.started = os.times()
1652 if self._firststarttime is None: # thread racy but irrelevant
1648 if self._firststarttime is None: # thread racy but irrelevant
1653 self._firststarttime = test.started[4]
1649 self._firststarttime = test.started[4]
1654
1650
1655 def stopTest(self, test, interrupted=False):
1651 def stopTest(self, test, interrupted=False):
1656 super(TestResult, self).stopTest(test)
1652 super(TestResult, self).stopTest(test)
1657
1653
1658 test.stopped = os.times()
1654 test.stopped = os.times()
1659
1655
1660 starttime = test.started
1656 starttime = test.started
1661 endtime = test.stopped
1657 endtime = test.stopped
1662 origin = self._firststarttime
1658 origin = self._firststarttime
1663 self.times.append((test.name,
1659 self.times.append((test.name,
1664 endtime[2] - starttime[2], # user space CPU time
1660 endtime[2] - starttime[2], # user space CPU time
1665 endtime[3] - starttime[3], # sys space CPU time
1661 endtime[3] - starttime[3], # sys space CPU time
1666 endtime[4] - starttime[4], # real time
1662 endtime[4] - starttime[4], # real time
1667 starttime[4] - origin, # start date in run context
1663 starttime[4] - origin, # start date in run context
1668 endtime[4] - origin, # end date in run context
1664 endtime[4] - origin, # end date in run context
1669 ))
1665 ))
1670
1666
1671 if interrupted:
1667 if interrupted:
1672 with iolock:
1668 with iolock:
1673 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1669 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1674 test.name, self.times[-1][3]))
1670 test.name, self.times[-1][3]))
1675
1671
1676 class TestSuite(unittest.TestSuite):
1672 class TestSuite(unittest.TestSuite):
1677 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1673 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1678
1674
1679 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1675 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1680 retest=False, keywords=None, loop=False, runs_per_test=1,
1676 retest=False, keywords=None, loop=False, runs_per_test=1,
1681 loadtest=None, showchannels=False,
1677 loadtest=None, showchannels=False,
1682 *args, **kwargs):
1678 *args, **kwargs):
1683 """Create a new instance that can run tests with a configuration.
1679 """Create a new instance that can run tests with a configuration.
1684
1680
1685 testdir specifies the directory where tests are executed from. This
1681 testdir specifies the directory where tests are executed from. This
1686 is typically the ``tests`` directory from Mercurial's source
1682 is typically the ``tests`` directory from Mercurial's source
1687 repository.
1683 repository.
1688
1684
1689 jobs specifies the number of jobs to run concurrently. Each test
1685 jobs specifies the number of jobs to run concurrently. Each test
1690 executes on its own thread. Tests actually spawn new processes, so
1686 executes on its own thread. Tests actually spawn new processes, so
1691 state mutation should not be an issue.
1687 state mutation should not be an issue.
1692
1688
1693 If there is only one job, it will use the main thread.
1689 If there is only one job, it will use the main thread.
1694
1690
1695 whitelist and blacklist denote tests that have been whitelisted and
1691 whitelist and blacklist denote tests that have been whitelisted and
1696 blacklisted, respectively. These arguments don't belong in TestSuite.
1692 blacklisted, respectively. These arguments don't belong in TestSuite.
1697 Instead, whitelist and blacklist should be handled by the thing that
1693 Instead, whitelist and blacklist should be handled by the thing that
1698 populates the TestSuite with tests. They are present to preserve
1694 populates the TestSuite with tests. They are present to preserve
1699 backwards compatible behavior which reports skipped tests as part
1695 backwards compatible behavior which reports skipped tests as part
1700 of the results.
1696 of the results.
1701
1697
1702 retest denotes whether to retest failed tests. This arguably belongs
1698 retest denotes whether to retest failed tests. This arguably belongs
1703 outside of TestSuite.
1699 outside of TestSuite.
1704
1700
1705 keywords denotes key words that will be used to filter which tests
1701 keywords denotes key words that will be used to filter which tests
1706 to execute. This arguably belongs outside of TestSuite.
1702 to execute. This arguably belongs outside of TestSuite.
1707
1703
1708 loop denotes whether to loop over tests forever.
1704 loop denotes whether to loop over tests forever.
1709 """
1705 """
1710 super(TestSuite, self).__init__(*args, **kwargs)
1706 super(TestSuite, self).__init__(*args, **kwargs)
1711
1707
1712 self._jobs = jobs
1708 self._jobs = jobs
1713 self._whitelist = whitelist
1709 self._whitelist = whitelist
1714 self._blacklist = blacklist
1710 self._blacklist = blacklist
1715 self._retest = retest
1711 self._retest = retest
1716 self._keywords = keywords
1712 self._keywords = keywords
1717 self._loop = loop
1713 self._loop = loop
1718 self._runs_per_test = runs_per_test
1714 self._runs_per_test = runs_per_test
1719 self._loadtest = loadtest
1715 self._loadtest = loadtest
1720 self._showchannels = showchannels
1716 self._showchannels = showchannels
1721
1717
1722 def run(self, result):
1718 def run(self, result):
1723 # We have a number of filters that need to be applied. We do this
1719 # We have a number of filters that need to be applied. We do this
1724 # here instead of inside Test because it makes the running logic for
1720 # here instead of inside Test because it makes the running logic for
1725 # Test simpler.
1721 # Test simpler.
1726 tests = []
1722 tests = []
1727 num_tests = [0]
1723 num_tests = [0]
1728 for test in self._tests:
1724 for test in self._tests:
1729 def get():
1725 def get():
1730 num_tests[0] += 1
1726 num_tests[0] += 1
1731 if getattr(test, 'should_reload', False):
1727 if getattr(test, 'should_reload', False):
1732 return self._loadtest(test, num_tests[0])
1728 return self._loadtest(test, num_tests[0])
1733 return test
1729 return test
1734 if not os.path.exists(test.path):
1730 if not os.path.exists(test.path):
1735 result.addSkip(test, "Doesn't exist")
1731 result.addSkip(test, "Doesn't exist")
1736 continue
1732 continue
1737
1733
1738 if not (self._whitelist and test.name in self._whitelist):
1734 if not (self._whitelist and test.name in self._whitelist):
1739 if self._blacklist and test.bname in self._blacklist:
1735 if self._blacklist and test.bname in self._blacklist:
1740 result.addSkip(test, 'blacklisted')
1736 result.addSkip(test, 'blacklisted')
1741 continue
1737 continue
1742
1738
1743 if self._retest and not os.path.exists(test.errpath):
1739 if self._retest and not os.path.exists(test.errpath):
1744 result.addIgnore(test, 'not retesting')
1740 result.addIgnore(test, 'not retesting')
1745 continue
1741 continue
1746
1742
1747 if self._keywords:
1743 if self._keywords:
1748 f = open(test.path, 'rb')
1744 f = open(test.path, 'rb')
1749 t = f.read().lower() + test.bname.lower()
1745 t = f.read().lower() + test.bname.lower()
1750 f.close()
1746 f.close()
1751 ignored = False
1747 ignored = False
1752 for k in self._keywords.lower().split():
1748 for k in self._keywords.lower().split():
1753 if k not in t:
1749 if k not in t:
1754 result.addIgnore(test, "doesn't match keyword")
1750 result.addIgnore(test, "doesn't match keyword")
1755 ignored = True
1751 ignored = True
1756 break
1752 break
1757
1753
1758 if ignored:
1754 if ignored:
1759 continue
1755 continue
1760 for _ in xrange(self._runs_per_test):
1756 for _ in xrange(self._runs_per_test):
1761 tests.append(get())
1757 tests.append(get())
1762
1758
1763 runtests = list(tests)
1759 runtests = list(tests)
1764 done = queue.Queue()
1760 done = queue.Queue()
1765 running = 0
1761 running = 0
1766
1762
1767 channels = [""] * self._jobs
1763 channels = [""] * self._jobs
1768
1764
1769 def job(test, result):
1765 def job(test, result):
1770 for n, v in enumerate(channels):
1766 for n, v in enumerate(channels):
1771 if not v:
1767 if not v:
1772 channel = n
1768 channel = n
1773 break
1769 break
1774 channels[channel] = "=" + test.name[5:].split(".")[0]
1770 channels[channel] = "=" + test.name[5:].split(".")[0]
1775 try:
1771 try:
1776 test(result)
1772 test(result)
1777 done.put(None)
1773 done.put(None)
1778 except KeyboardInterrupt:
1774 except KeyboardInterrupt:
1779 pass
1775 pass
1780 except: # re-raises
1776 except: # re-raises
1781 done.put(('!', test, 'run-test raised an error, see traceback'))
1777 done.put(('!', test, 'run-test raised an error, see traceback'))
1782 raise
1778 raise
1783 try:
1779 try:
1784 channels[channel] = ''
1780 channels[channel] = ''
1785 except IndexError:
1781 except IndexError:
1786 pass
1782 pass
1787
1783
1788 def stat():
1784 def stat():
1789 count = 0
1785 count = 0
1790 while channels:
1786 while channels:
1791 d = '\n%03s ' % count
1787 d = '\n%03s ' % count
1792 for n, v in enumerate(channels):
1788 for n, v in enumerate(channels):
1793 if v:
1789 if v:
1794 d += v[0]
1790 d += v[0]
1795 channels[n] = v[1:] or '.'
1791 channels[n] = v[1:] or '.'
1796 else:
1792 else:
1797 d += ' '
1793 d += ' '
1798 d += ' '
1794 d += ' '
1799 with iolock:
1795 with iolock:
1800 sys.stdout.write(d + ' ')
1796 sys.stdout.write(d + ' ')
1801 sys.stdout.flush()
1797 sys.stdout.flush()
1802 for x in xrange(10):
1798 for x in xrange(10):
1803 if channels:
1799 if channels:
1804 time.sleep(.1)
1800 time.sleep(.1)
1805 count += 1
1801 count += 1
1806
1802
1807 stoppedearly = False
1803 stoppedearly = False
1808
1804
1809 if self._showchannels:
1805 if self._showchannels:
1810 statthread = threading.Thread(target=stat, name="stat")
1806 statthread = threading.Thread(target=stat, name="stat")
1811 statthread.start()
1807 statthread.start()
1812
1808
1813 try:
1809 try:
1814 while tests or running:
1810 while tests or running:
1815 if not done.empty() or running == self._jobs or not tests:
1811 if not done.empty() or running == self._jobs or not tests:
1816 try:
1812 try:
1817 done.get(True, 1)
1813 done.get(True, 1)
1818 running -= 1
1814 running -= 1
1819 if result and result.shouldStop:
1815 if result and result.shouldStop:
1820 stoppedearly = True
1816 stoppedearly = True
1821 break
1817 break
1822 except queue.Empty:
1818 except queue.Empty:
1823 continue
1819 continue
1824 if tests and not running == self._jobs:
1820 if tests and not running == self._jobs:
1825 test = tests.pop(0)
1821 test = tests.pop(0)
1826 if self._loop:
1822 if self._loop:
1827 if getattr(test, 'should_reload', False):
1823 if getattr(test, 'should_reload', False):
1828 num_tests[0] += 1
1824 num_tests[0] += 1
1829 tests.append(
1825 tests.append(
1830 self._loadtest(test, num_tests[0]))
1826 self._loadtest(test, num_tests[0]))
1831 else:
1827 else:
1832 tests.append(test)
1828 tests.append(test)
1833 if self._jobs == 1:
1829 if self._jobs == 1:
1834 job(test, result)
1830 job(test, result)
1835 else:
1831 else:
1836 t = threading.Thread(target=job, name=test.name,
1832 t = threading.Thread(target=job, name=test.name,
1837 args=(test, result))
1833 args=(test, result))
1838 t.start()
1834 t.start()
1839 running += 1
1835 running += 1
1840
1836
1841 # If we stop early we still need to wait on started tests to
1837 # If we stop early we still need to wait on started tests to
1842 # finish. Otherwise, there is a race between the test completing
1838 # finish. Otherwise, there is a race between the test completing
1843 # and the test's cleanup code running. This could result in the
1839 # and the test's cleanup code running. This could result in the
1844 # test reporting incorrect.
1840 # test reporting incorrect.
1845 if stoppedearly:
1841 if stoppedearly:
1846 while running:
1842 while running:
1847 try:
1843 try:
1848 done.get(True, 1)
1844 done.get(True, 1)
1849 running -= 1
1845 running -= 1
1850 except queue.Empty:
1846 except queue.Empty:
1851 continue
1847 continue
1852 except KeyboardInterrupt:
1848 except KeyboardInterrupt:
1853 for test in runtests:
1849 for test in runtests:
1854 test.abort()
1850 test.abort()
1855
1851
1856 channels = []
1852 channels = []
1857
1853
1858 return result
1854 return result
1859
1855
1860 # Save the most recent 5 wall-clock runtimes of each test to a
1856 # Save the most recent 5 wall-clock runtimes of each test to a
1861 # human-readable text file named .testtimes. Tests are sorted
1857 # human-readable text file named .testtimes. Tests are sorted
1862 # alphabetically, while times for each test are listed from oldest to
1858 # alphabetically, while times for each test are listed from oldest to
1863 # newest.
1859 # newest.
1864
1860
1865 def loadtimes(testdir):
1861 def loadtimes(testdir):
1866 times = []
1862 times = []
1867 try:
1863 try:
1868 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1864 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1869 for line in fp:
1865 for line in fp:
1870 ts = line.split()
1866 ts = line.split()
1871 times.append((ts[0], [float(t) for t in ts[1:]]))
1867 times.append((ts[0], [float(t) for t in ts[1:]]))
1872 except IOError as err:
1868 except IOError as err:
1873 if err.errno != errno.ENOENT:
1869 if err.errno != errno.ENOENT:
1874 raise
1870 raise
1875 return times
1871 return times
1876
1872
1877 def savetimes(testdir, result):
1873 def savetimes(testdir, result):
1878 saved = dict(loadtimes(testdir))
1874 saved = dict(loadtimes(testdir))
1879 maxruns = 5
1875 maxruns = 5
1880 skipped = set([str(t[0]) for t in result.skipped])
1876 skipped = set([str(t[0]) for t in result.skipped])
1881 for tdata in result.times:
1877 for tdata in result.times:
1882 test, real = tdata[0], tdata[3]
1878 test, real = tdata[0], tdata[3]
1883 if test not in skipped:
1879 if test not in skipped:
1884 ts = saved.setdefault(test, [])
1880 ts = saved.setdefault(test, [])
1885 ts.append(real)
1881 ts.append(real)
1886 ts[:] = ts[-maxruns:]
1882 ts[:] = ts[-maxruns:]
1887
1883
1888 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1884 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1889 dir=testdir, text=True)
1885 dir=testdir, text=True)
1890 with os.fdopen(fd, 'w') as fp:
1886 with os.fdopen(fd, 'w') as fp:
1891 for name, ts in sorted(saved.items()):
1887 for name, ts in sorted(saved.items()):
1892 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1888 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1893 timepath = os.path.join(testdir, b'.testtimes')
1889 timepath = os.path.join(testdir, b'.testtimes')
1894 try:
1890 try:
1895 os.unlink(timepath)
1891 os.unlink(timepath)
1896 except OSError:
1892 except OSError:
1897 pass
1893 pass
1898 try:
1894 try:
1899 os.rename(tmpname, timepath)
1895 os.rename(tmpname, timepath)
1900 except OSError:
1896 except OSError:
1901 pass
1897 pass
1902
1898
1903 class TextTestRunner(unittest.TextTestRunner):
1899 class TextTestRunner(unittest.TextTestRunner):
1904 """Custom unittest test runner that uses appropriate settings."""
1900 """Custom unittest test runner that uses appropriate settings."""
1905
1901
1906 def __init__(self, runner, *args, **kwargs):
1902 def __init__(self, runner, *args, **kwargs):
1907 super(TextTestRunner, self).__init__(*args, **kwargs)
1903 super(TextTestRunner, self).__init__(*args, **kwargs)
1908
1904
1909 self._runner = runner
1905 self._runner = runner
1910
1906
1911 def run(self, test):
1907 def run(self, test):
1912 result = TestResult(self._runner.options, self.stream,
1908 result = TestResult(self._runner.options, self.stream,
1913 self.descriptions, self.verbosity)
1909 self.descriptions, self.verbosity)
1914
1910
1915 test(result)
1911 test(result)
1916
1912
1917 failed = len(result.failures)
1913 failed = len(result.failures)
1918 warned = len(result.warned)
1914 warned = len(result.warned)
1919 skipped = len(result.skipped)
1915 skipped = len(result.skipped)
1920 ignored = len(result.ignored)
1916 ignored = len(result.ignored)
1921
1917
1922 with iolock:
1918 with iolock:
1923 self.stream.writeln('')
1919 self.stream.writeln('')
1924
1920
1925 if not self._runner.options.noskips:
1921 if not self._runner.options.noskips:
1926 for test, msg in result.skipped:
1922 for test, msg in result.skipped:
1927 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1923 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1928 for test, msg in result.warned:
1924 for test, msg in result.warned:
1929 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1925 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1930 for test, msg in result.failures:
1926 for test, msg in result.failures:
1931 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1927 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1932 for test, msg in result.errors:
1928 for test, msg in result.errors:
1933 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1929 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1934
1930
1935 if self._runner.options.xunit:
1931 if self._runner.options.xunit:
1936 with open(self._runner.options.xunit, 'wb') as xuf:
1932 with open(self._runner.options.xunit, 'wb') as xuf:
1937 timesd = dict((t[0], t[3]) for t in result.times)
1933 timesd = dict((t[0], t[3]) for t in result.times)
1938 doc = minidom.Document()
1934 doc = minidom.Document()
1939 s = doc.createElement('testsuite')
1935 s = doc.createElement('testsuite')
1940 s.setAttribute('name', 'run-tests')
1936 s.setAttribute('name', 'run-tests')
1941 s.setAttribute('tests', str(result.testsRun))
1937 s.setAttribute('tests', str(result.testsRun))
1942 s.setAttribute('errors', "0") # TODO
1938 s.setAttribute('errors', "0") # TODO
1943 s.setAttribute('failures', str(failed))
1939 s.setAttribute('failures', str(failed))
1944 s.setAttribute('skipped', str(skipped + ignored))
1940 s.setAttribute('skipped', str(skipped + ignored))
1945 doc.appendChild(s)
1941 doc.appendChild(s)
1946 for tc in result.successes:
1942 for tc in result.successes:
1947 t = doc.createElement('testcase')
1943 t = doc.createElement('testcase')
1948 t.setAttribute('name', tc.name)
1944 t.setAttribute('name', tc.name)
1949 t.setAttribute('time', '%.3f' % timesd[tc.name])
1945 t.setAttribute('time', '%.3f' % timesd[tc.name])
1950 s.appendChild(t)
1946 s.appendChild(t)
1951 for tc, err in sorted(result.faildata.items()):
1947 for tc, err in sorted(result.faildata.items()):
1952 t = doc.createElement('testcase')
1948 t = doc.createElement('testcase')
1953 t.setAttribute('name', tc)
1949 t.setAttribute('name', tc)
1954 t.setAttribute('time', '%.3f' % timesd[tc])
1950 t.setAttribute('time', '%.3f' % timesd[tc])
1955 # createCDATASection expects a unicode or it will
1951 # createCDATASection expects a unicode or it will
1956 # convert using default conversion rules, which will
1952 # convert using default conversion rules, which will
1957 # fail if string isn't ASCII.
1953 # fail if string isn't ASCII.
1958 err = cdatasafe(err).decode('utf-8', 'replace')
1954 err = cdatasafe(err).decode('utf-8', 'replace')
1959 cd = doc.createCDATASection(err)
1955 cd = doc.createCDATASection(err)
1960 t.appendChild(cd)
1956 t.appendChild(cd)
1961 s.appendChild(t)
1957 s.appendChild(t)
1962 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1958 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1963
1959
1964 if self._runner.options.json:
1960 if self._runner.options.json:
1965 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1961 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1966 with open(jsonpath, 'w') as fp:
1962 with open(jsonpath, 'w') as fp:
1967 timesd = {}
1963 timesd = {}
1968 for tdata in result.times:
1964 for tdata in result.times:
1969 test = tdata[0]
1965 test = tdata[0]
1970 timesd[test] = tdata[1:]
1966 timesd[test] = tdata[1:]
1971
1967
1972 outcome = {}
1968 outcome = {}
1973 groups = [('success', ((tc, None)
1969 groups = [('success', ((tc, None)
1974 for tc in result.successes)),
1970 for tc in result.successes)),
1975 ('failure', result.failures),
1971 ('failure', result.failures),
1976 ('skip', result.skipped)]
1972 ('skip', result.skipped)]
1977 for res, testcases in groups:
1973 for res, testcases in groups:
1978 for tc, __ in testcases:
1974 for tc, __ in testcases:
1979 if tc.name in timesd:
1975 if tc.name in timesd:
1980 diff = result.faildata.get(tc.name, b'')
1976 diff = result.faildata.get(tc.name, b'')
1981 tres = {'result': res,
1977 tres = {'result': res,
1982 'time': ('%0.3f' % timesd[tc.name][2]),
1978 'time': ('%0.3f' % timesd[tc.name][2]),
1983 'cuser': ('%0.3f' % timesd[tc.name][0]),
1979 'cuser': ('%0.3f' % timesd[tc.name][0]),
1984 'csys': ('%0.3f' % timesd[tc.name][1]),
1980 'csys': ('%0.3f' % timesd[tc.name][1]),
1985 'start': ('%0.3f' % timesd[tc.name][3]),
1981 'start': ('%0.3f' % timesd[tc.name][3]),
1986 'end': ('%0.3f' % timesd[tc.name][4]),
1982 'end': ('%0.3f' % timesd[tc.name][4]),
1987 'diff': diff.decode('unicode_escape'),
1983 'diff': diff.decode('unicode_escape'),
1988 }
1984 }
1989 else:
1985 else:
1990 # blacklisted test
1986 # blacklisted test
1991 tres = {'result': res}
1987 tres = {'result': res}
1992
1988
1993 outcome[tc.name] = tres
1989 outcome[tc.name] = tres
1994 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
1990 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
1995 separators=(',', ': '))
1991 separators=(',', ': '))
1996 fp.writelines(("testreport =", jsonout))
1992 fp.writelines(("testreport =", jsonout))
1997
1993
1998 self._runner._checkhglib('Tested')
1994 self._runner._checkhglib('Tested')
1999
1995
2000 savetimes(self._runner._testdir, result)
1996 savetimes(self._runner._testdir, result)
2001
1997
2002 if failed and self._runner.options.known_good_rev:
1998 if failed and self._runner.options.known_good_rev:
2003 def nooutput(args):
1999 def nooutput(args):
2004 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2000 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2005 stdout=subprocess.PIPE)
2001 stdout=subprocess.PIPE)
2006 p.stdout.read()
2002 p.stdout.read()
2007 p.wait()
2003 p.wait()
2008 for test, msg in result.failures:
2004 for test, msg in result.failures:
2009 nooutput(['hg', 'bisect', '--reset']),
2005 nooutput(['hg', 'bisect', '--reset']),
2010 nooutput(['hg', 'bisect', '--bad', '.'])
2006 nooutput(['hg', 'bisect', '--bad', '.'])
2011 nooutput(['hg', 'bisect', '--good',
2007 nooutput(['hg', 'bisect', '--good',
2012 self._runner.options.known_good_rev])
2008 self._runner.options.known_good_rev])
2013 # TODO: we probably need to forward some options
2009 # TODO: we probably need to forward some options
2014 # that alter hg's behavior inside the tests.
2010 # that alter hg's behavior inside the tests.
2015 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
2011 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
2016 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
2012 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
2017 stderr=subprocess.STDOUT,
2013 stderr=subprocess.STDOUT,
2018 stdout=subprocess.PIPE)
2014 stdout=subprocess.PIPE)
2019 data = sub.stdout.read()
2015 data = sub.stdout.read()
2020 sub.wait()
2016 sub.wait()
2021 m = re.search(
2017 m = re.search(
2022 (r'\nThe first (?P<goodbad>bad|good) revision '
2018 (r'\nThe first (?P<goodbad>bad|good) revision '
2023 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2019 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2024 r'summary: +(?P<summary>[^\n]+)\n'),
2020 r'summary: +(?P<summary>[^\n]+)\n'),
2025 data, (re.MULTILINE | re.DOTALL))
2021 data, (re.MULTILINE | re.DOTALL))
2026 if m is None:
2022 if m is None:
2027 self.stream.writeln(
2023 self.stream.writeln(
2028 'Failed to identify failure point for %s' % test)
2024 'Failed to identify failure point for %s' % test)
2029 continue
2025 continue
2030 dat = m.groupdict()
2026 dat = m.groupdict()
2031 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
2027 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
2032 self.stream.writeln(
2028 self.stream.writeln(
2033 '%s %s by %s (%s)' % (
2029 '%s %s by %s (%s)' % (
2034 test, verb, dat['node'], dat['summary']))
2030 test, verb, dat['node'], dat['summary']))
2035 self.stream.writeln(
2031 self.stream.writeln(
2036 '# Ran %d tests, %d skipped, %d warned, %d failed.'
2032 '# Ran %d tests, %d skipped, %d warned, %d failed.'
2037 % (result.testsRun,
2033 % (result.testsRun,
2038 skipped + ignored, warned, failed))
2034 skipped + ignored, warned, failed))
2039 if failed:
2035 if failed:
2040 self.stream.writeln('python hash seed: %s' %
2036 self.stream.writeln('python hash seed: %s' %
2041 os.environ['PYTHONHASHSEED'])
2037 os.environ['PYTHONHASHSEED'])
2042 if self._runner.options.time:
2038 if self._runner.options.time:
2043 self.printtimes(result.times)
2039 self.printtimes(result.times)
2044
2040
2045 return result
2041 return result
2046
2042
2047 def printtimes(self, times):
2043 def printtimes(self, times):
2048 # iolock held by run
2044 # iolock held by run
2049 self.stream.writeln('# Producing time report')
2045 self.stream.writeln('# Producing time report')
2050 times.sort(key=lambda t: (t[3]))
2046 times.sort(key=lambda t: (t[3]))
2051 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2047 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2052 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2048 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2053 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2049 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2054 for tdata in times:
2050 for tdata in times:
2055 test = tdata[0]
2051 test = tdata[0]
2056 cuser, csys, real, start, end = tdata[1:6]
2052 cuser, csys, real, start, end = tdata[1:6]
2057 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2053 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2058
2054
2059 class TestRunner(object):
2055 class TestRunner(object):
2060 """Holds context for executing tests.
2056 """Holds context for executing tests.
2061
2057
2062 Tests rely on a lot of state. This object holds it for them.
2058 Tests rely on a lot of state. This object holds it for them.
2063 """
2059 """
2064
2060
2065 # Programs required to run tests.
2061 # Programs required to run tests.
2066 REQUIREDTOOLS = [
2062 REQUIREDTOOLS = [
2067 os.path.basename(_bytespath(sys.executable)),
2063 os.path.basename(_bytespath(sys.executable)),
2068 b'diff',
2064 b'diff',
2069 b'grep',
2065 b'grep',
2070 b'unzip',
2066 b'unzip',
2071 b'gunzip',
2067 b'gunzip',
2072 b'bunzip2',
2068 b'bunzip2',
2073 b'sed',
2069 b'sed',
2074 ]
2070 ]
2075
2071
2076 # Maps file extensions to test class.
2072 # Maps file extensions to test class.
2077 TESTTYPES = [
2073 TESTTYPES = [
2078 (b'.py', PythonTest),
2074 (b'.py', PythonTest),
2079 (b'.t', TTest),
2075 (b'.t', TTest),
2080 ]
2076 ]
2081
2077
2082 def __init__(self):
2078 def __init__(self):
2083 self.options = None
2079 self.options = None
2084 self._hgroot = None
2080 self._hgroot = None
2085 self._testdir = None
2081 self._testdir = None
2086 self._hgtmp = None
2082 self._hgtmp = None
2087 self._installdir = None
2083 self._installdir = None
2088 self._bindir = None
2084 self._bindir = None
2089 self._tmpbinddir = None
2085 self._tmpbinddir = None
2090 self._pythondir = None
2086 self._pythondir = None
2091 self._coveragefile = None
2087 self._coveragefile = None
2092 self._createdfiles = []
2088 self._createdfiles = []
2093 self._hgcommand = None
2089 self._hgcommand = None
2094 self._hgpath = None
2090 self._hgpath = None
2095 self._portoffset = 0
2091 self._portoffset = 0
2096 self._ports = {}
2092 self._ports = {}
2097
2093
2098 def run(self, args, parser=None):
2094 def run(self, args, parser=None):
2099 """Run the test suite."""
2095 """Run the test suite."""
2100 oldmask = os.umask(0o22)
2096 oldmask = os.umask(0o22)
2101 try:
2097 try:
2102 parser = parser or getparser()
2098 parser = parser or getparser()
2103 options, args = parseargs(args, parser)
2099 options, args = parseargs(args, parser)
2104 # positional arguments are paths to test files to run, so
2100 # positional arguments are paths to test files to run, so
2105 # we make sure they're all bytestrings
2101 # we make sure they're all bytestrings
2106 args = [_bytespath(a) for a in args]
2102 args = [_bytespath(a) for a in args]
2107 self.options = options
2103 self.options = options
2108
2104
2109 self._checktools()
2105 self._checktools()
2110 testdescs = self.findtests(args)
2106 testdescs = self.findtests(args)
2111 if options.profile_runner:
2107 if options.profile_runner:
2112 import statprof
2108 import statprof
2113 statprof.start()
2109 statprof.start()
2114 result = self._run(testdescs)
2110 result = self._run(testdescs)
2115 if options.profile_runner:
2111 if options.profile_runner:
2116 statprof.stop()
2112 statprof.stop()
2117 statprof.display()
2113 statprof.display()
2118 return result
2114 return result
2119
2115
2120 finally:
2116 finally:
2121 os.umask(oldmask)
2117 os.umask(oldmask)
2122
2118
2123 def _run(self, testdescs):
2119 def _run(self, testdescs):
2124 if self.options.random:
2120 if self.options.random:
2125 random.shuffle(testdescs)
2121 random.shuffle(testdescs)
2126 else:
2122 else:
2127 # keywords for slow tests
2123 # keywords for slow tests
2128 slow = {b'svn': 10,
2124 slow = {b'svn': 10,
2129 b'cvs': 10,
2125 b'cvs': 10,
2130 b'hghave': 10,
2126 b'hghave': 10,
2131 b'largefiles-update': 10,
2127 b'largefiles-update': 10,
2132 b'run-tests': 10,
2128 b'run-tests': 10,
2133 b'corruption': 10,
2129 b'corruption': 10,
2134 b'race': 10,
2130 b'race': 10,
2135 b'i18n': 10,
2131 b'i18n': 10,
2136 b'check': 100,
2132 b'check': 100,
2137 b'gendoc': 100,
2133 b'gendoc': 100,
2138 b'contrib-perf': 200,
2134 b'contrib-perf': 200,
2139 }
2135 }
2140 perf = {}
2136 perf = {}
2141 def sortkey(f):
2137 def sortkey(f):
2142 # run largest tests first, as they tend to take the longest
2138 # run largest tests first, as they tend to take the longest
2143 f = f['path']
2139 f = f['path']
2144 try:
2140 try:
2145 return perf[f]
2141 return perf[f]
2146 except KeyError:
2142 except KeyError:
2147 try:
2143 try:
2148 val = -os.stat(f).st_size
2144 val = -os.stat(f).st_size
2149 except OSError as e:
2145 except OSError as e:
2150 if e.errno != errno.ENOENT:
2146 if e.errno != errno.ENOENT:
2151 raise
2147 raise
2152 perf[f] = -1e9 # file does not exist, tell early
2148 perf[f] = -1e9 # file does not exist, tell early
2153 return -1e9
2149 return -1e9
2154 for kw, mul in slow.items():
2150 for kw, mul in slow.items():
2155 if kw in f:
2151 if kw in f:
2156 val *= mul
2152 val *= mul
2157 if f.endswith(b'.py'):
2153 if f.endswith(b'.py'):
2158 val /= 10.0
2154 val /= 10.0
2159 perf[f] = val / 1000.0
2155 perf[f] = val / 1000.0
2160 return perf[f]
2156 return perf[f]
2161 testdescs.sort(key=sortkey)
2157 testdescs.sort(key=sortkey)
2162
2158
2163 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2159 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2164 os, 'getcwdb', os.getcwd)()
2160 os, 'getcwdb', os.getcwd)()
2165
2161
2166 if 'PYTHONHASHSEED' not in os.environ:
2162 if 'PYTHONHASHSEED' not in os.environ:
2167 # use a random python hash seed all the time
2163 # use a random python hash seed all the time
2168 # we do the randomness ourself to know what seed is used
2164 # we do the randomness ourself to know what seed is used
2169 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2165 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2170
2166
2171 if self.options.tmpdir:
2167 if self.options.tmpdir:
2172 self.options.keep_tmpdir = True
2168 self.options.keep_tmpdir = True
2173 tmpdir = _bytespath(self.options.tmpdir)
2169 tmpdir = _bytespath(self.options.tmpdir)
2174 if os.path.exists(tmpdir):
2170 if os.path.exists(tmpdir):
2175 # Meaning of tmpdir has changed since 1.3: we used to create
2171 # Meaning of tmpdir has changed since 1.3: we used to create
2176 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2172 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2177 # tmpdir already exists.
2173 # tmpdir already exists.
2178 print("error: temp dir %r already exists" % tmpdir)
2174 print("error: temp dir %r already exists" % tmpdir)
2179 return 1
2175 return 1
2180
2176
2181 # Automatically removing tmpdir sounds convenient, but could
2177 # Automatically removing tmpdir sounds convenient, but could
2182 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2178 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2183 # or "--tmpdir=$HOME".
2179 # or "--tmpdir=$HOME".
2184 #vlog("# Removing temp dir", tmpdir)
2180 #vlog("# Removing temp dir", tmpdir)
2185 #shutil.rmtree(tmpdir)
2181 #shutil.rmtree(tmpdir)
2186 os.makedirs(tmpdir)
2182 os.makedirs(tmpdir)
2187 else:
2183 else:
2188 d = None
2184 d = None
2189 if os.name == 'nt':
2185 if os.name == 'nt':
2190 # without this, we get the default temp dir location, but
2186 # without this, we get the default temp dir location, but
2191 # in all lowercase, which causes troubles with paths (issue3490)
2187 # in all lowercase, which causes troubles with paths (issue3490)
2192 d = osenvironb.get(b'TMP', None)
2188 d = osenvironb.get(b'TMP', None)
2193 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2189 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2194
2190
2195 self._hgtmp = osenvironb[b'HGTMP'] = (
2191 self._hgtmp = osenvironb[b'HGTMP'] = (
2196 os.path.realpath(tmpdir))
2192 os.path.realpath(tmpdir))
2197
2193
2198 if self.options.with_hg:
2194 if self.options.with_hg:
2199 self._installdir = None
2195 self._installdir = None
2200 whg = self.options.with_hg
2196 whg = self.options.with_hg
2201 self._bindir = os.path.dirname(os.path.realpath(whg))
2197 self._bindir = os.path.dirname(os.path.realpath(whg))
2202 assert isinstance(self._bindir, bytes)
2198 assert isinstance(self._bindir, bytes)
2203 self._hgcommand = os.path.basename(whg)
2199 self._hgcommand = os.path.basename(whg)
2204 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2200 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2205 os.makedirs(self._tmpbindir)
2201 os.makedirs(self._tmpbindir)
2206
2202
2207 # This looks redundant with how Python initializes sys.path from
2203 # This looks redundant with how Python initializes sys.path from
2208 # the location of the script being executed. Needed because the
2204 # the location of the script being executed. Needed because the
2209 # "hg" specified by --with-hg is not the only Python script
2205 # "hg" specified by --with-hg is not the only Python script
2210 # executed in the test suite that needs to import 'mercurial'
2206 # executed in the test suite that needs to import 'mercurial'
2211 # ... which means it's not really redundant at all.
2207 # ... which means it's not really redundant at all.
2212 self._pythondir = self._bindir
2208 self._pythondir = self._bindir
2213 else:
2209 else:
2214 self._installdir = os.path.join(self._hgtmp, b"install")
2210 self._installdir = os.path.join(self._hgtmp, b"install")
2215 self._bindir = os.path.join(self._installdir, b"bin")
2211 self._bindir = os.path.join(self._installdir, b"bin")
2216 self._hgcommand = b'hg'
2212 self._hgcommand = b'hg'
2217 self._tmpbindir = self._bindir
2213 self._tmpbindir = self._bindir
2218 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2214 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2219
2215
2220 # set CHGHG, then replace "hg" command by "chg"
2216 # set CHGHG, then replace "hg" command by "chg"
2221 chgbindir = self._bindir
2217 chgbindir = self._bindir
2222 if self.options.chg or self.options.with_chg:
2218 if self.options.chg or self.options.with_chg:
2223 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2219 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2224 else:
2220 else:
2225 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2221 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2226 if self.options.chg:
2222 if self.options.chg:
2227 self._hgcommand = b'chg'
2223 self._hgcommand = b'chg'
2228 elif self.options.with_chg:
2224 elif self.options.with_chg:
2229 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2225 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2230 self._hgcommand = os.path.basename(self.options.with_chg)
2226 self._hgcommand = os.path.basename(self.options.with_chg)
2231
2227
2232 osenvironb[b"BINDIR"] = self._bindir
2228 osenvironb[b"BINDIR"] = self._bindir
2233 osenvironb[b"PYTHON"] = PYTHON
2229 osenvironb[b"PYTHON"] = PYTHON
2234
2230
2235 if self.options.with_python3:
2231 if self.options.with_python3:
2236 osenvironb[b'PYTHON3'] = self.options.with_python3
2232 osenvironb[b'PYTHON3'] = self.options.with_python3
2237
2233
2238 fileb = _bytespath(__file__)
2234 fileb = _bytespath(__file__)
2239 runtestdir = os.path.abspath(os.path.dirname(fileb))
2235 runtestdir = os.path.abspath(os.path.dirname(fileb))
2240 osenvironb[b'RUNTESTDIR'] = runtestdir
2236 osenvironb[b'RUNTESTDIR'] = runtestdir
2241 if PYTHON3:
2237 if PYTHON3:
2242 sepb = _bytespath(os.pathsep)
2238 sepb = _bytespath(os.pathsep)
2243 else:
2239 else:
2244 sepb = os.pathsep
2240 sepb = os.pathsep
2245 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2241 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2246 if os.path.islink(__file__):
2242 if os.path.islink(__file__):
2247 # test helper will likely be at the end of the symlink
2243 # test helper will likely be at the end of the symlink
2248 realfile = os.path.realpath(fileb)
2244 realfile = os.path.realpath(fileb)
2249 realdir = os.path.abspath(os.path.dirname(realfile))
2245 realdir = os.path.abspath(os.path.dirname(realfile))
2250 path.insert(2, realdir)
2246 path.insert(2, realdir)
2251 if chgbindir != self._bindir:
2247 if chgbindir != self._bindir:
2252 path.insert(1, chgbindir)
2248 path.insert(1, chgbindir)
2253 if self._testdir != runtestdir:
2249 if self._testdir != runtestdir:
2254 path = [self._testdir] + path
2250 path = [self._testdir] + path
2255 if self._tmpbindir != self._bindir:
2251 if self._tmpbindir != self._bindir:
2256 path = [self._tmpbindir] + path
2252 path = [self._tmpbindir] + path
2257 osenvironb[b"PATH"] = sepb.join(path)
2253 osenvironb[b"PATH"] = sepb.join(path)
2258
2254
2259 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2255 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2260 # can run .../tests/run-tests.py test-foo where test-foo
2256 # can run .../tests/run-tests.py test-foo where test-foo
2261 # adds an extension to HGRC. Also include run-test.py directory to
2257 # adds an extension to HGRC. Also include run-test.py directory to
2262 # import modules like heredoctest.
2258 # import modules like heredoctest.
2263 pypath = [self._pythondir, self._testdir, runtestdir]
2259 pypath = [self._pythondir, self._testdir, runtestdir]
2264 # We have to augment PYTHONPATH, rather than simply replacing
2260 # We have to augment PYTHONPATH, rather than simply replacing
2265 # it, in case external libraries are only available via current
2261 # it, in case external libraries are only available via current
2266 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2262 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2267 # are in /opt/subversion.)
2263 # are in /opt/subversion.)
2268 oldpypath = osenvironb.get(IMPL_PATH)
2264 oldpypath = osenvironb.get(IMPL_PATH)
2269 if oldpypath:
2265 if oldpypath:
2270 pypath.append(oldpypath)
2266 pypath.append(oldpypath)
2271 osenvironb[IMPL_PATH] = sepb.join(pypath)
2267 osenvironb[IMPL_PATH] = sepb.join(pypath)
2272
2268
2273 if self.options.pure:
2269 if self.options.pure:
2274 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2270 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2275 os.environ["HGMODULEPOLICY"] = "py"
2271 os.environ["HGMODULEPOLICY"] = "py"
2276
2272
2277 if self.options.allow_slow_tests:
2273 if self.options.allow_slow_tests:
2278 os.environ["HGTEST_SLOW"] = "slow"
2274 os.environ["HGTEST_SLOW"] = "slow"
2279 elif 'HGTEST_SLOW' in os.environ:
2275 elif 'HGTEST_SLOW' in os.environ:
2280 del os.environ['HGTEST_SLOW']
2276 del os.environ['HGTEST_SLOW']
2281
2277
2282 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2278 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2283
2279
2284 vlog("# Using TESTDIR", self._testdir)
2280 vlog("# Using TESTDIR", self._testdir)
2285 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2281 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2286 vlog("# Using HGTMP", self._hgtmp)
2282 vlog("# Using HGTMP", self._hgtmp)
2287 vlog("# Using PATH", os.environ["PATH"])
2283 vlog("# Using PATH", os.environ["PATH"])
2288 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2284 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2289
2285
2290 try:
2286 try:
2291 return self._runtests(testdescs) or 0
2287 return self._runtests(testdescs) or 0
2292 finally:
2288 finally:
2293 time.sleep(.1)
2289 time.sleep(.1)
2294 self._cleanup()
2290 self._cleanup()
2295
2291
2296 def findtests(self, args):
2292 def findtests(self, args):
2297 """Finds possible test files from arguments.
2293 """Finds possible test files from arguments.
2298
2294
2299 If you wish to inject custom tests into the test harness, this would
2295 If you wish to inject custom tests into the test harness, this would
2300 be a good function to monkeypatch or override in a derived class.
2296 be a good function to monkeypatch or override in a derived class.
2301 """
2297 """
2302 if not args:
2298 if not args:
2303 if self.options.changed:
2299 if self.options.changed:
2304 proc = Popen4('hg st --rev "%s" -man0 .' %
2300 proc = Popen4('hg st --rev "%s" -man0 .' %
2305 self.options.changed, None, 0)
2301 self.options.changed, None, 0)
2306 stdout, stderr = proc.communicate()
2302 stdout, stderr = proc.communicate()
2307 args = stdout.strip(b'\0').split(b'\0')
2303 args = stdout.strip(b'\0').split(b'\0')
2308 else:
2304 else:
2309 args = os.listdir(b'.')
2305 args = os.listdir(b'.')
2310
2306
2311 tests = []
2307 tests = []
2312 for t in args:
2308 for t in args:
2313 if not (os.path.basename(t).startswith(b'test-')
2309 if not (os.path.basename(t).startswith(b'test-')
2314 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2310 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2315 continue
2311 continue
2316 if t.endswith(b'.t'):
2312 if t.endswith(b'.t'):
2317 # .t file may contain multiple test cases
2313 # .t file may contain multiple test cases
2318 cases = sorted(parsettestcases(t))
2314 cases = sorted(parsettestcases(t))
2319 if cases:
2315 if cases:
2320 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2316 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2321 else:
2317 else:
2322 tests.append({'path': t})
2318 tests.append({'path': t})
2323 else:
2319 else:
2324 tests.append({'path': t})
2320 tests.append({'path': t})
2325 return tests
2321 return tests
2326
2322
2327 def _runtests(self, testdescs):
2323 def _runtests(self, testdescs):
2328 def _reloadtest(test, i):
2324 def _reloadtest(test, i):
2329 # convert a test back to its description dict
2325 # convert a test back to its description dict
2330 desc = {'path': test.path}
2326 desc = {'path': test.path}
2331 case = getattr(test, '_case', None)
2327 case = getattr(test, '_case', None)
2332 if case:
2328 if case:
2333 desc['case'] = case
2329 desc['case'] = case
2334 return self._gettest(desc, i)
2330 return self._gettest(desc, i)
2335
2331
2336 try:
2332 try:
2337 if self._installdir:
2333 if self._installdir:
2338 self._installhg()
2334 self._installhg()
2339 self._checkhglib("Testing")
2335 self._checkhglib("Testing")
2340 else:
2336 else:
2341 self._usecorrectpython()
2337 self._usecorrectpython()
2342 if self.options.chg:
2338 if self.options.chg:
2343 assert self._installdir
2339 assert self._installdir
2344 self._installchg()
2340 self._installchg()
2345
2341
2346 if self.options.restart:
2342 if self.options.restart:
2347 orig = list(testdescs)
2343 orig = list(testdescs)
2348 while testdescs:
2344 while testdescs:
2349 desc = testdescs[0]
2345 desc = testdescs[0]
2350 if 'case' in desc:
2346 if 'case' in desc:
2351 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2347 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2352 else:
2348 else:
2353 errpath = b'%s.err' % desc['path']
2349 errpath = b'%s.err' % desc['path']
2354 if os.path.exists(errpath):
2350 if os.path.exists(errpath):
2355 break
2351 break
2356 testdescs.pop(0)
2352 testdescs.pop(0)
2357 if not testdescs:
2353 if not testdescs:
2358 print("running all tests")
2354 print("running all tests")
2359 testdescs = orig
2355 testdescs = orig
2360
2356
2361 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2357 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2362
2358
2363 failed = False
2359 failed = False
2364 warned = False
2360 warned = False
2365 kws = self.options.keywords
2361 kws = self.options.keywords
2366 if kws is not None and PYTHON3:
2362 if kws is not None and PYTHON3:
2367 kws = kws.encode('utf-8')
2363 kws = kws.encode('utf-8')
2368
2364
2369 suite = TestSuite(self._testdir,
2365 suite = TestSuite(self._testdir,
2370 jobs=self.options.jobs,
2366 jobs=self.options.jobs,
2371 whitelist=self.options.whitelisted,
2367 whitelist=self.options.whitelisted,
2372 blacklist=self.options.blacklist,
2368 blacklist=self.options.blacklist,
2373 retest=self.options.retest,
2369 retest=self.options.retest,
2374 keywords=kws,
2370 keywords=kws,
2375 loop=self.options.loop,
2371 loop=self.options.loop,
2376 runs_per_test=self.options.runs_per_test,
2372 runs_per_test=self.options.runs_per_test,
2377 showchannels=self.options.showchannels,
2373 showchannels=self.options.showchannels,
2378 tests=tests, loadtest=_reloadtest)
2374 tests=tests, loadtest=_reloadtest)
2379 verbosity = 1
2375 verbosity = 1
2380 if self.options.verbose:
2376 if self.options.verbose:
2381 verbosity = 2
2377 verbosity = 2
2382 runner = TextTestRunner(self, verbosity=verbosity)
2378 runner = TextTestRunner(self, verbosity=verbosity)
2383 result = runner.run(suite)
2379 result = runner.run(suite)
2384
2380
2385 if result.failures:
2381 if result.failures:
2386 failed = True
2382 failed = True
2387 if result.warned:
2383 if result.warned:
2388 warned = True
2384 warned = True
2389
2385
2390 if self.options.anycoverage:
2386 if self.options.anycoverage:
2391 self._outputcoverage()
2387 self._outputcoverage()
2392 except KeyboardInterrupt:
2388 except KeyboardInterrupt:
2393 failed = True
2389 failed = True
2394 print("\ninterrupted!")
2390 print("\ninterrupted!")
2395
2391
2396 if failed:
2392 if failed:
2397 return 1
2393 return 1
2398 if warned:
2394 if warned:
2399 return 80
2395 return 80
2400
2396
2401 def _getport(self, count):
2397 def _getport(self, count):
2402 port = self._ports.get(count) # do we have a cached entry?
2398 port = self._ports.get(count) # do we have a cached entry?
2403 if port is None:
2399 if port is None:
2404 portneeded = 3
2400 portneeded = 3
2405 # above 100 tries we just give up and let test reports failure
2401 # above 100 tries we just give up and let test reports failure
2406 for tries in xrange(100):
2402 for tries in xrange(100):
2407 allfree = True
2403 allfree = True
2408 port = self.options.port + self._portoffset
2404 port = self.options.port + self._portoffset
2409 for idx in xrange(portneeded):
2405 for idx in xrange(portneeded):
2410 if not checkportisavailable(port + idx):
2406 if not checkportisavailable(port + idx):
2411 allfree = False
2407 allfree = False
2412 break
2408 break
2413 self._portoffset += portneeded
2409 self._portoffset += portneeded
2414 if allfree:
2410 if allfree:
2415 break
2411 break
2416 self._ports[count] = port
2412 self._ports[count] = port
2417 return port
2413 return port
2418
2414
2419 def _gettest(self, testdesc, count):
2415 def _gettest(self, testdesc, count):
2420 """Obtain a Test by looking at its filename.
2416 """Obtain a Test by looking at its filename.
2421
2417
2422 Returns a Test instance. The Test may not be runnable if it doesn't
2418 Returns a Test instance. The Test may not be runnable if it doesn't
2423 map to a known type.
2419 map to a known type.
2424 """
2420 """
2425 path = testdesc['path']
2421 path = testdesc['path']
2426 lctest = path.lower()
2422 lctest = path.lower()
2427 testcls = Test
2423 testcls = Test
2428
2424
2429 for ext, cls in self.TESTTYPES:
2425 for ext, cls in self.TESTTYPES:
2430 if lctest.endswith(ext):
2426 if lctest.endswith(ext):
2431 testcls = cls
2427 testcls = cls
2432 break
2428 break
2433
2429
2434 refpath = os.path.join(self._testdir, path)
2430 refpath = os.path.join(self._testdir, path)
2435 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2431 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2436
2432
2437 # extra keyword parameters. 'case' is used by .t tests
2433 # extra keyword parameters. 'case' is used by .t tests
2438 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2434 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2439
2435
2440 t = testcls(refpath, tmpdir,
2436 t = testcls(refpath, tmpdir,
2441 keeptmpdir=self.options.keep_tmpdir,
2437 keeptmpdir=self.options.keep_tmpdir,
2442 debug=self.options.debug,
2438 debug=self.options.debug,
2443 timeout=self.options.timeout,
2439 timeout=self.options.timeout,
2444 startport=self._getport(count),
2440 startport=self._getport(count),
2445 extraconfigopts=self.options.extra_config_opt,
2441 extraconfigopts=self.options.extra_config_opt,
2446 py3kwarnings=self.options.py3k_warnings,
2442 py3kwarnings=self.options.py3k_warnings,
2447 shell=self.options.shell,
2443 shell=self.options.shell,
2448 hgcommand=self._hgcommand,
2444 hgcommand=self._hgcommand,
2449 usechg=bool(self.options.with_chg or self.options.chg),
2445 usechg=bool(self.options.with_chg or self.options.chg),
2450 useipv6=useipv6, **kwds)
2446 useipv6=useipv6, **kwds)
2451 t.should_reload = True
2447 t.should_reload = True
2452 return t
2448 return t
2453
2449
2454 def _cleanup(self):
2450 def _cleanup(self):
2455 """Clean up state from this test invocation."""
2451 """Clean up state from this test invocation."""
2456 if self.options.keep_tmpdir:
2452 if self.options.keep_tmpdir:
2457 return
2453 return
2458
2454
2459 vlog("# Cleaning up HGTMP", self._hgtmp)
2455 vlog("# Cleaning up HGTMP", self._hgtmp)
2460 shutil.rmtree(self._hgtmp, True)
2456 shutil.rmtree(self._hgtmp, True)
2461 for f in self._createdfiles:
2457 for f in self._createdfiles:
2462 try:
2458 try:
2463 os.remove(f)
2459 os.remove(f)
2464 except OSError:
2460 except OSError:
2465 pass
2461 pass
2466
2462
2467 def _usecorrectpython(self):
2463 def _usecorrectpython(self):
2468 """Configure the environment to use the appropriate Python in tests."""
2464 """Configure the environment to use the appropriate Python in tests."""
2469 # Tests must use the same interpreter as us or bad things will happen.
2465 # Tests must use the same interpreter as us or bad things will happen.
2470 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2466 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2471 if getattr(os, 'symlink', None):
2467 if getattr(os, 'symlink', None):
2472 vlog("# Making python executable in test path a symlink to '%s'" %
2468 vlog("# Making python executable in test path a symlink to '%s'" %
2473 sys.executable)
2469 sys.executable)
2474 mypython = os.path.join(self._tmpbindir, pyexename)
2470 mypython = os.path.join(self._tmpbindir, pyexename)
2475 try:
2471 try:
2476 if os.readlink(mypython) == sys.executable:
2472 if os.readlink(mypython) == sys.executable:
2477 return
2473 return
2478 os.unlink(mypython)
2474 os.unlink(mypython)
2479 except OSError as err:
2475 except OSError as err:
2480 if err.errno != errno.ENOENT:
2476 if err.errno != errno.ENOENT:
2481 raise
2477 raise
2482 if self._findprogram(pyexename) != sys.executable:
2478 if self._findprogram(pyexename) != sys.executable:
2483 try:
2479 try:
2484 os.symlink(sys.executable, mypython)
2480 os.symlink(sys.executable, mypython)
2485 self._createdfiles.append(mypython)
2481 self._createdfiles.append(mypython)
2486 except OSError as err:
2482 except OSError as err:
2487 # child processes may race, which is harmless
2483 # child processes may race, which is harmless
2488 if err.errno != errno.EEXIST:
2484 if err.errno != errno.EEXIST:
2489 raise
2485 raise
2490 else:
2486 else:
2491 exedir, exename = os.path.split(sys.executable)
2487 exedir, exename = os.path.split(sys.executable)
2492 vlog("# Modifying search path to find %s as %s in '%s'" %
2488 vlog("# Modifying search path to find %s as %s in '%s'" %
2493 (exename, pyexename, exedir))
2489 (exename, pyexename, exedir))
2494 path = os.environ['PATH'].split(os.pathsep)
2490 path = os.environ['PATH'].split(os.pathsep)
2495 while exedir in path:
2491 while exedir in path:
2496 path.remove(exedir)
2492 path.remove(exedir)
2497 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2493 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2498 if not self._findprogram(pyexename):
2494 if not self._findprogram(pyexename):
2499 print("WARNING: Cannot find %s in search path" % pyexename)
2495 print("WARNING: Cannot find %s in search path" % pyexename)
2500
2496
2501 def _installhg(self):
2497 def _installhg(self):
2502 """Install hg into the test environment.
2498 """Install hg into the test environment.
2503
2499
2504 This will also configure hg with the appropriate testing settings.
2500 This will also configure hg with the appropriate testing settings.
2505 """
2501 """
2506 vlog("# Performing temporary installation of HG")
2502 vlog("# Performing temporary installation of HG")
2507 installerrs = os.path.join(self._hgtmp, b"install.err")
2503 installerrs = os.path.join(self._hgtmp, b"install.err")
2508 compiler = ''
2504 compiler = ''
2509 if self.options.compiler:
2505 if self.options.compiler:
2510 compiler = '--compiler ' + self.options.compiler
2506 compiler = '--compiler ' + self.options.compiler
2511 if self.options.pure:
2507 if self.options.pure:
2512 pure = b"--pure"
2508 pure = b"--pure"
2513 else:
2509 else:
2514 pure = b""
2510 pure = b""
2515
2511
2516 # Run installer in hg root
2512 # Run installer in hg root
2517 script = os.path.realpath(sys.argv[0])
2513 script = os.path.realpath(sys.argv[0])
2518 exe = sys.executable
2514 exe = sys.executable
2519 if PYTHON3:
2515 if PYTHON3:
2520 compiler = _bytespath(compiler)
2516 compiler = _bytespath(compiler)
2521 script = _bytespath(script)
2517 script = _bytespath(script)
2522 exe = _bytespath(exe)
2518 exe = _bytespath(exe)
2523 hgroot = os.path.dirname(os.path.dirname(script))
2519 hgroot = os.path.dirname(os.path.dirname(script))
2524 self._hgroot = hgroot
2520 self._hgroot = hgroot
2525 os.chdir(hgroot)
2521 os.chdir(hgroot)
2526 nohome = b'--home=""'
2522 nohome = b'--home=""'
2527 if os.name == 'nt':
2523 if os.name == 'nt':
2528 # The --home="" trick works only on OS where os.sep == '/'
2524 # The --home="" trick works only on OS where os.sep == '/'
2529 # because of a distutils convert_path() fast-path. Avoid it at
2525 # because of a distutils convert_path() fast-path. Avoid it at
2530 # least on Windows for now, deal with .pydistutils.cfg bugs
2526 # least on Windows for now, deal with .pydistutils.cfg bugs
2531 # when they happen.
2527 # when they happen.
2532 nohome = b''
2528 nohome = b''
2533 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2529 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2534 b' build %(compiler)s --build-base="%(base)s"'
2530 b' build %(compiler)s --build-base="%(base)s"'
2535 b' install --force --prefix="%(prefix)s"'
2531 b' install --force --prefix="%(prefix)s"'
2536 b' --install-lib="%(libdir)s"'
2532 b' --install-lib="%(libdir)s"'
2537 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2533 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2538 % {b'exe': exe, b'pure': pure,
2534 % {b'exe': exe, b'pure': pure,
2539 b'compiler': compiler,
2535 b'compiler': compiler,
2540 b'base': os.path.join(self._hgtmp, b"build"),
2536 b'base': os.path.join(self._hgtmp, b"build"),
2541 b'prefix': self._installdir, b'libdir': self._pythondir,
2537 b'prefix': self._installdir, b'libdir': self._pythondir,
2542 b'bindir': self._bindir,
2538 b'bindir': self._bindir,
2543 b'nohome': nohome, b'logfile': installerrs})
2539 b'nohome': nohome, b'logfile': installerrs})
2544
2540
2545 # setuptools requires install directories to exist.
2541 # setuptools requires install directories to exist.
2546 def makedirs(p):
2542 def makedirs(p):
2547 try:
2543 try:
2548 os.makedirs(p)
2544 os.makedirs(p)
2549 except OSError as e:
2545 except OSError as e:
2550 if e.errno != errno.EEXIST:
2546 if e.errno != errno.EEXIST:
2551 raise
2547 raise
2552 makedirs(self._pythondir)
2548 makedirs(self._pythondir)
2553 makedirs(self._bindir)
2549 makedirs(self._bindir)
2554
2550
2555 vlog("# Running", cmd)
2551 vlog("# Running", cmd)
2556 if os.system(cmd) == 0:
2552 if os.system(cmd) == 0:
2557 if not self.options.verbose:
2553 if not self.options.verbose:
2558 try:
2554 try:
2559 os.remove(installerrs)
2555 os.remove(installerrs)
2560 except OSError as e:
2556 except OSError as e:
2561 if e.errno != errno.ENOENT:
2557 if e.errno != errno.ENOENT:
2562 raise
2558 raise
2563 else:
2559 else:
2564 f = open(installerrs, 'rb')
2560 f = open(installerrs, 'rb')
2565 for line in f:
2561 for line in f:
2566 if PYTHON3:
2562 if PYTHON3:
2567 sys.stdout.buffer.write(line)
2563 sys.stdout.buffer.write(line)
2568 else:
2564 else:
2569 sys.stdout.write(line)
2565 sys.stdout.write(line)
2570 f.close()
2566 f.close()
2571 sys.exit(1)
2567 sys.exit(1)
2572 os.chdir(self._testdir)
2568 os.chdir(self._testdir)
2573
2569
2574 self._usecorrectpython()
2570 self._usecorrectpython()
2575
2571
2576 if self.options.py3k_warnings and not self.options.anycoverage:
2572 if self.options.py3k_warnings and not self.options.anycoverage:
2577 vlog("# Updating hg command to enable Py3k Warnings switch")
2573 vlog("# Updating hg command to enable Py3k Warnings switch")
2578 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2574 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2579 lines = [line.rstrip() for line in f]
2575 lines = [line.rstrip() for line in f]
2580 lines[0] += ' -3'
2576 lines[0] += ' -3'
2581 f.close()
2577 f.close()
2582 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2578 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2583 for line in lines:
2579 for line in lines:
2584 f.write(line + '\n')
2580 f.write(line + '\n')
2585 f.close()
2581 f.close()
2586
2582
2587 hgbat = os.path.join(self._bindir, b'hg.bat')
2583 hgbat = os.path.join(self._bindir, b'hg.bat')
2588 if os.path.isfile(hgbat):
2584 if os.path.isfile(hgbat):
2589 # hg.bat expects to be put in bin/scripts while run-tests.py
2585 # hg.bat expects to be put in bin/scripts while run-tests.py
2590 # installation layout put it in bin/ directly. Fix it
2586 # installation layout put it in bin/ directly. Fix it
2591 f = open(hgbat, 'rb')
2587 f = open(hgbat, 'rb')
2592 data = f.read()
2588 data = f.read()
2593 f.close()
2589 f.close()
2594 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2590 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2595 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2591 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2596 b'"%~dp0python" "%~dp0hg" %*')
2592 b'"%~dp0python" "%~dp0hg" %*')
2597 f = open(hgbat, 'wb')
2593 f = open(hgbat, 'wb')
2598 f.write(data)
2594 f.write(data)
2599 f.close()
2595 f.close()
2600 else:
2596 else:
2601 print('WARNING: cannot fix hg.bat reference to python.exe')
2597 print('WARNING: cannot fix hg.bat reference to python.exe')
2602
2598
2603 if self.options.anycoverage:
2599 if self.options.anycoverage:
2604 custom = os.path.join(self._testdir, 'sitecustomize.py')
2600 custom = os.path.join(self._testdir, 'sitecustomize.py')
2605 target = os.path.join(self._pythondir, 'sitecustomize.py')
2601 target = os.path.join(self._pythondir, 'sitecustomize.py')
2606 vlog('# Installing coverage trigger to %s' % target)
2602 vlog('# Installing coverage trigger to %s' % target)
2607 shutil.copyfile(custom, target)
2603 shutil.copyfile(custom, target)
2608 rc = os.path.join(self._testdir, '.coveragerc')
2604 rc = os.path.join(self._testdir, '.coveragerc')
2609 vlog('# Installing coverage rc to %s' % rc)
2605 vlog('# Installing coverage rc to %s' % rc)
2610 os.environ['COVERAGE_PROCESS_START'] = rc
2606 os.environ['COVERAGE_PROCESS_START'] = rc
2611 covdir = os.path.join(self._installdir, '..', 'coverage')
2607 covdir = os.path.join(self._installdir, '..', 'coverage')
2612 try:
2608 try:
2613 os.mkdir(covdir)
2609 os.mkdir(covdir)
2614 except OSError as e:
2610 except OSError as e:
2615 if e.errno != errno.EEXIST:
2611 if e.errno != errno.EEXIST:
2616 raise
2612 raise
2617
2613
2618 os.environ['COVERAGE_DIR'] = covdir
2614 os.environ['COVERAGE_DIR'] = covdir
2619
2615
2620 def _checkhglib(self, verb):
2616 def _checkhglib(self, verb):
2621 """Ensure that the 'mercurial' package imported by python is
2617 """Ensure that the 'mercurial' package imported by python is
2622 the one we expect it to be. If not, print a warning to stderr."""
2618 the one we expect it to be. If not, print a warning to stderr."""
2623 if ((self._bindir == self._pythondir) and
2619 if ((self._bindir == self._pythondir) and
2624 (self._bindir != self._tmpbindir)):
2620 (self._bindir != self._tmpbindir)):
2625 # The pythondir has been inferred from --with-hg flag.
2621 # The pythondir has been inferred from --with-hg flag.
2626 # We cannot expect anything sensible here.
2622 # We cannot expect anything sensible here.
2627 return
2623 return
2628 expecthg = os.path.join(self._pythondir, b'mercurial')
2624 expecthg = os.path.join(self._pythondir, b'mercurial')
2629 actualhg = self._gethgpath()
2625 actualhg = self._gethgpath()
2630 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2626 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2631 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2627 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2632 ' (expected %s)\n'
2628 ' (expected %s)\n'
2633 % (verb, actualhg, expecthg))
2629 % (verb, actualhg, expecthg))
2634 def _gethgpath(self):
2630 def _gethgpath(self):
2635 """Return the path to the mercurial package that is actually found by
2631 """Return the path to the mercurial package that is actually found by
2636 the current Python interpreter."""
2632 the current Python interpreter."""
2637 if self._hgpath is not None:
2633 if self._hgpath is not None:
2638 return self._hgpath
2634 return self._hgpath
2639
2635
2640 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2636 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2641 cmd = cmd % PYTHON
2637 cmd = cmd % PYTHON
2642 if PYTHON3:
2638 if PYTHON3:
2643 cmd = _strpath(cmd)
2639 cmd = _strpath(cmd)
2644 pipe = os.popen(cmd)
2640 pipe = os.popen(cmd)
2645 try:
2641 try:
2646 self._hgpath = _bytespath(pipe.read().strip())
2642 self._hgpath = _bytespath(pipe.read().strip())
2647 finally:
2643 finally:
2648 pipe.close()
2644 pipe.close()
2649
2645
2650 return self._hgpath
2646 return self._hgpath
2651
2647
2652 def _installchg(self):
2648 def _installchg(self):
2653 """Install chg into the test environment"""
2649 """Install chg into the test environment"""
2654 vlog('# Performing temporary installation of CHG')
2650 vlog('# Performing temporary installation of CHG')
2655 assert os.path.dirname(self._bindir) == self._installdir
2651 assert os.path.dirname(self._bindir) == self._installdir
2656 assert self._hgroot, 'must be called after _installhg()'
2652 assert self._hgroot, 'must be called after _installhg()'
2657 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2653 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2658 % {b'make': 'make', # TODO: switch by option or environment?
2654 % {b'make': 'make', # TODO: switch by option or environment?
2659 b'prefix': self._installdir})
2655 b'prefix': self._installdir})
2660 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2656 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2661 vlog("# Running", cmd)
2657 vlog("# Running", cmd)
2662 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2658 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2663 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2659 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2664 stderr=subprocess.STDOUT)
2660 stderr=subprocess.STDOUT)
2665 out, _err = proc.communicate()
2661 out, _err = proc.communicate()
2666 if proc.returncode != 0:
2662 if proc.returncode != 0:
2667 if PYTHON3:
2663 if PYTHON3:
2668 sys.stdout.buffer.write(out)
2664 sys.stdout.buffer.write(out)
2669 else:
2665 else:
2670 sys.stdout.write(out)
2666 sys.stdout.write(out)
2671 sys.exit(1)
2667 sys.exit(1)
2672
2668
2673 def _outputcoverage(self):
2669 def _outputcoverage(self):
2674 """Produce code coverage output."""
2670 """Produce code coverage output."""
2675 import coverage
2671 import coverage
2676 coverage = coverage.coverage
2672 coverage = coverage.coverage
2677
2673
2678 vlog('# Producing coverage report')
2674 vlog('# Producing coverage report')
2679 # chdir is the easiest way to get short, relative paths in the
2675 # chdir is the easiest way to get short, relative paths in the
2680 # output.
2676 # output.
2681 os.chdir(self._hgroot)
2677 os.chdir(self._hgroot)
2682 covdir = os.path.join(self._installdir, '..', 'coverage')
2678 covdir = os.path.join(self._installdir, '..', 'coverage')
2683 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2679 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2684
2680
2685 # Map install directory paths back to source directory.
2681 # Map install directory paths back to source directory.
2686 cov.config.paths['srcdir'] = ['.', self._pythondir]
2682 cov.config.paths['srcdir'] = ['.', self._pythondir]
2687
2683
2688 cov.combine()
2684 cov.combine()
2689
2685
2690 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2686 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2691 cov.report(ignore_errors=True, omit=omit)
2687 cov.report(ignore_errors=True, omit=omit)
2692
2688
2693 if self.options.htmlcov:
2689 if self.options.htmlcov:
2694 htmldir = os.path.join(self._testdir, 'htmlcov')
2690 htmldir = os.path.join(self._testdir, 'htmlcov')
2695 cov.html_report(directory=htmldir, omit=omit)
2691 cov.html_report(directory=htmldir, omit=omit)
2696 if self.options.annotate:
2692 if self.options.annotate:
2697 adir = os.path.join(self._testdir, 'annotated')
2693 adir = os.path.join(self._testdir, 'annotated')
2698 if not os.path.isdir(adir):
2694 if not os.path.isdir(adir):
2699 os.mkdir(adir)
2695 os.mkdir(adir)
2700 cov.annotate(directory=adir, omit=omit)
2696 cov.annotate(directory=adir, omit=omit)
2701
2697
2702 def _findprogram(self, program):
2698 def _findprogram(self, program):
2703 """Search PATH for a executable program"""
2699 """Search PATH for a executable program"""
2704 dpb = _bytespath(os.defpath)
2700 dpb = _bytespath(os.defpath)
2705 sepb = _bytespath(os.pathsep)
2701 sepb = _bytespath(os.pathsep)
2706 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2702 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2707 name = os.path.join(p, program)
2703 name = os.path.join(p, program)
2708 if os.name == 'nt' or os.access(name, os.X_OK):
2704 if os.name == 'nt' or os.access(name, os.X_OK):
2709 return name
2705 return name
2710 return None
2706 return None
2711
2707
2712 def _checktools(self):
2708 def _checktools(self):
2713 """Ensure tools required to run tests are present."""
2709 """Ensure tools required to run tests are present."""
2714 for p in self.REQUIREDTOOLS:
2710 for p in self.REQUIREDTOOLS:
2715 if os.name == 'nt' and not p.endswith('.exe'):
2711 if os.name == 'nt' and not p.endswith('.exe'):
2716 p += '.exe'
2712 p += '.exe'
2717 found = self._findprogram(p)
2713 found = self._findprogram(p)
2718 if found:
2714 if found:
2719 vlog("# Found prerequisite", p, "at", found)
2715 vlog("# Found prerequisite", p, "at", found)
2720 else:
2716 else:
2721 print("WARNING: Did not find prerequisite tool: %s " %
2717 print("WARNING: Did not find prerequisite tool: %s " %
2722 p.decode("utf-8"))
2718 p.decode("utf-8"))
2723
2719
2724 if __name__ == '__main__':
2720 if __name__ == '__main__':
2725 runner = TestRunner()
2721 runner = TestRunner()
2726
2722
2727 try:
2723 try:
2728 import msvcrt
2724 import msvcrt
2729 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2725 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2730 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2726 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2731 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2727 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2732 except ImportError:
2728 except ImportError:
2733 pass
2729 pass
2734
2730
2735 sys.exit(runner.run(sys.argv[1:]))
2731 sys.exit(runner.run(sys.argv[1:]))
@@ -1,92 +1,88 b''
1 Create a repository:
1 Create a repository:
2
2
3 $ hg config
3 $ hg config
4 defaults.backout=-d "0 0"
5 defaults.commit=-d "0 0"
6 defaults.shelve=--date "0 0"
7 defaults.tag=-d "0 0"
8 devel.all-warnings=true
4 devel.all-warnings=true
9 devel.default-date=0 0
5 devel.default-date=0 0
10 largefiles.usercache=$TESTTMP/.cache/largefiles (glob)
6 largefiles.usercache=$TESTTMP/.cache/largefiles (glob)
11 ui.slash=True
7 ui.slash=True
12 ui.interactive=False
8 ui.interactive=False
13 ui.mergemarkers=detailed
9 ui.mergemarkers=detailed
14 ui.promptecho=True
10 ui.promptecho=True
15 web.address=localhost
11 web.address=localhost
16 web\.ipv6=(?:True|False) (re)
12 web\.ipv6=(?:True|False) (re)
17 $ hg init t
13 $ hg init t
18 $ cd t
14 $ cd t
19
15
20 Prepare a changeset:
16 Prepare a changeset:
21
17
22 $ echo a > a
18 $ echo a > a
23 $ hg add a
19 $ hg add a
24
20
25 $ hg status
21 $ hg status
26 A a
22 A a
27
23
28 Writes to stdio succeed and fail appropriately
24 Writes to stdio succeed and fail appropriately
29
25
30 #if devfull
26 #if devfull
31 $ hg status 2>/dev/full
27 $ hg status 2>/dev/full
32 A a
28 A a
33
29
34 $ hg status >/dev/full
30 $ hg status >/dev/full
35 abort: No space left on device
31 abort: No space left on device
36 [255]
32 [255]
37
33
38 $ hg status >/dev/full 2>&1
34 $ hg status >/dev/full 2>&1
39 [1]
35 [1]
40
36
41 $ hg status ENOENT 2>/dev/full
37 $ hg status ENOENT 2>/dev/full
42 [1]
38 [1]
43 #endif
39 #endif
44
40
45 $ hg commit -m test
41 $ hg commit -m test
46
42
47 This command is ancient:
43 This command is ancient:
48
44
49 $ hg history
45 $ hg history
50 changeset: 0:acb14030fe0a
46 changeset: 0:acb14030fe0a
51 tag: tip
47 tag: tip
52 user: test
48 user: test
53 date: Thu Jan 01 00:00:00 1970 +0000
49 date: Thu Jan 01 00:00:00 1970 +0000
54 summary: test
50 summary: test
55
51
56
52
57 Verify that updating to revision 0 via commands.update() works properly
53 Verify that updating to revision 0 via commands.update() works properly
58
54
59 $ cat <<EOF > update_to_rev0.py
55 $ cat <<EOF > update_to_rev0.py
60 > from mercurial import ui, hg, commands
56 > from mercurial import ui, hg, commands
61 > myui = ui.ui.load()
57 > myui = ui.ui.load()
62 > repo = hg.repository(myui, path='.')
58 > repo = hg.repository(myui, path='.')
63 > commands.update(myui, repo, rev=0)
59 > commands.update(myui, repo, rev=0)
64 > EOF
60 > EOF
65 $ hg up null
61 $ hg up null
66 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
62 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 $ python ./update_to_rev0.py
63 $ python ./update_to_rev0.py
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ hg identify -n
65 $ hg identify -n
70 0
66 0
71
67
72
68
73 Poke around at hashes:
69 Poke around at hashes:
74
70
75 $ hg manifest --debug
71 $ hg manifest --debug
76 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
72 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
77
73
78 $ hg cat a
74 $ hg cat a
79 a
75 a
80
76
81 Verify should succeed:
77 Verify should succeed:
82
78
83 $ hg verify
79 $ hg verify
84 checking changesets
80 checking changesets
85 checking manifests
81 checking manifests
86 crosschecking files in changesets and manifests
82 crosschecking files in changesets and manifests
87 checking files
83 checking files
88 1 files, 1 changesets, 1 total revisions
84 1 files, 1 changesets, 1 total revisions
89
85
90 At the end...
86 At the end...
91
87
92 $ cd ..
88 $ cd ..
@@ -1,914 +1,906 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from __future__ import print_function
16 >>> from __future__ import print_function
17 >>> from hgclient import readchannel, runcommand, check
17 >>> from hgclient import readchannel, runcommand, check
18 >>> @check
18 >>> @check
19 ... def hellomessage(server):
19 ... def hellomessage(server):
20 ... ch, data = readchannel(server)
20 ... ch, data = readchannel(server)
21 ... print('%c, %r' % (ch, data))
21 ... print('%c, %r' % (ch, data))
22 ... # run an arbitrary command to make sure the next thing the server
22 ... # run an arbitrary command to make sure the next thing the server
23 ... # sends isn't part of the hello message
23 ... # sends isn't part of the hello message
24 ... runcommand(server, ['id'])
24 ... runcommand(server, ['id'])
25 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
25 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
26 *** runcommand id
26 *** runcommand id
27 000000000000 tip
27 000000000000 tip
28
28
29 >>> from hgclient import check
29 >>> from hgclient import check
30 >>> @check
30 >>> @check
31 ... def unknowncommand(server):
31 ... def unknowncommand(server):
32 ... server.stdin.write('unknowncommand\n')
32 ... server.stdin.write('unknowncommand\n')
33 abort: unknown command unknowncommand
33 abort: unknown command unknowncommand
34
34
35 >>> from hgclient import readchannel, runcommand, check
35 >>> from hgclient import readchannel, runcommand, check
36 >>> @check
36 >>> @check
37 ... def checkruncommand(server):
37 ... def checkruncommand(server):
38 ... # hello block
38 ... # hello block
39 ... readchannel(server)
39 ... readchannel(server)
40 ...
40 ...
41 ... # no args
41 ... # no args
42 ... runcommand(server, [])
42 ... runcommand(server, [])
43 ...
43 ...
44 ... # global options
44 ... # global options
45 ... runcommand(server, ['id', '--quiet'])
45 ... runcommand(server, ['id', '--quiet'])
46 ...
46 ...
47 ... # make sure global options don't stick through requests
47 ... # make sure global options don't stick through requests
48 ... runcommand(server, ['id'])
48 ... runcommand(server, ['id'])
49 ...
49 ...
50 ... # --config
50 ... # --config
51 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
51 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
52 ...
52 ...
53 ... # make sure --config doesn't stick
53 ... # make sure --config doesn't stick
54 ... runcommand(server, ['id'])
54 ... runcommand(server, ['id'])
55 ...
55 ...
56 ... # negative return code should be masked
56 ... # negative return code should be masked
57 ... runcommand(server, ['id', '-runknown'])
57 ... runcommand(server, ['id', '-runknown'])
58 *** runcommand
58 *** runcommand
59 Mercurial Distributed SCM
59 Mercurial Distributed SCM
60
60
61 basic commands:
61 basic commands:
62
62
63 add add the specified files on the next commit
63 add add the specified files on the next commit
64 annotate show changeset information by line for each file
64 annotate show changeset information by line for each file
65 clone make a copy of an existing repository
65 clone make a copy of an existing repository
66 commit commit the specified files or all outstanding changes
66 commit commit the specified files or all outstanding changes
67 diff diff repository (or selected files)
67 diff diff repository (or selected files)
68 export dump the header and diffs for one or more changesets
68 export dump the header and diffs for one or more changesets
69 forget forget the specified files on the next commit
69 forget forget the specified files on the next commit
70 init create a new repository in the given directory
70 init create a new repository in the given directory
71 log show revision history of entire repository or files
71 log show revision history of entire repository or files
72 merge merge another revision into working directory
72 merge merge another revision into working directory
73 pull pull changes from the specified source
73 pull pull changes from the specified source
74 push push changes to the specified destination
74 push push changes to the specified destination
75 remove remove the specified files on the next commit
75 remove remove the specified files on the next commit
76 serve start stand-alone webserver
76 serve start stand-alone webserver
77 status show changed files in the working directory
77 status show changed files in the working directory
78 summary summarize working directory state
78 summary summarize working directory state
79 update update working directory (or switch revisions)
79 update update working directory (or switch revisions)
80
80
81 (use 'hg help' for the full list of commands or 'hg -v' for details)
81 (use 'hg help' for the full list of commands or 'hg -v' for details)
82 *** runcommand id --quiet
82 *** runcommand id --quiet
83 000000000000
83 000000000000
84 *** runcommand id
84 *** runcommand id
85 000000000000 tip
85 000000000000 tip
86 *** runcommand id --config ui.quiet=True
86 *** runcommand id --config ui.quiet=True
87 000000000000
87 000000000000
88 *** runcommand id
88 *** runcommand id
89 000000000000 tip
89 000000000000 tip
90 *** runcommand id -runknown
90 *** runcommand id -runknown
91 abort: unknown revision 'unknown'!
91 abort: unknown revision 'unknown'!
92 [255]
92 [255]
93
93
94 >>> from hgclient import readchannel, check
94 >>> from hgclient import readchannel, check
95 >>> @check
95 >>> @check
96 ... def inputeof(server):
96 ... def inputeof(server):
97 ... readchannel(server)
97 ... readchannel(server)
98 ... server.stdin.write('runcommand\n')
98 ... server.stdin.write('runcommand\n')
99 ... # close stdin while server is waiting for input
99 ... # close stdin while server is waiting for input
100 ... server.stdin.close()
100 ... server.stdin.close()
101 ...
101 ...
102 ... # server exits with 1 if the pipe closed while reading the command
102 ... # server exits with 1 if the pipe closed while reading the command
103 ... print('server exit code =', server.wait())
103 ... print('server exit code =', server.wait())
104 server exit code = 1
104 server exit code = 1
105
105
106 >>> from hgclient import readchannel, runcommand, check, stringio
106 >>> from hgclient import readchannel, runcommand, check, stringio
107 >>> @check
107 >>> @check
108 ... def serverinput(server):
108 ... def serverinput(server):
109 ... readchannel(server)
109 ... readchannel(server)
110 ...
110 ...
111 ... patch = """
111 ... patch = """
112 ... # HG changeset patch
112 ... # HG changeset patch
113 ... # User test
113 ... # User test
114 ... # Date 0 0
114 ... # Date 0 0
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
116 ... # Parent 0000000000000000000000000000000000000000
116 ... # Parent 0000000000000000000000000000000000000000
117 ... 1
117 ... 1
118 ...
118 ...
119 ... diff -r 000000000000 -r c103a3dec114 a
119 ... diff -r 000000000000 -r c103a3dec114 a
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
122 ... @@ -0,0 +1,1 @@
122 ... @@ -0,0 +1,1 @@
123 ... +1
123 ... +1
124 ... """
124 ... """
125 ...
125 ...
126 ... runcommand(server, ['import', '-'], input=stringio(patch))
126 ... runcommand(server, ['import', '-'], input=stringio(patch))
127 ... runcommand(server, ['log'])
127 ... runcommand(server, ['log'])
128 *** runcommand import -
128 *** runcommand import -
129 applying patch from stdin
129 applying patch from stdin
130 *** runcommand log
130 *** runcommand log
131 changeset: 0:eff892de26ec
131 changeset: 0:eff892de26ec
132 tag: tip
132 tag: tip
133 user: test
133 user: test
134 date: Thu Jan 01 00:00:00 1970 +0000
134 date: Thu Jan 01 00:00:00 1970 +0000
135 summary: 1
135 summary: 1
136
136
137
137
138 check that "histedit --commands=-" can read rules from the input channel:
138 check that "histedit --commands=-" can read rules from the input channel:
139
139
140 >>> import cStringIO
140 >>> import cStringIO
141 >>> from hgclient import readchannel, runcommand, check
141 >>> from hgclient import readchannel, runcommand, check
142 >>> @check
142 >>> @check
143 ... def serverinput(server):
143 ... def serverinput(server):
144 ... readchannel(server)
144 ... readchannel(server)
145 ... rules = 'pick eff892de26ec\n'
145 ... rules = 'pick eff892de26ec\n'
146 ... runcommand(server, ['histedit', '0', '--commands=-',
146 ... runcommand(server, ['histedit', '0', '--commands=-',
147 ... '--config', 'extensions.histedit='],
147 ... '--config', 'extensions.histedit='],
148 ... input=cStringIO.StringIO(rules))
148 ... input=cStringIO.StringIO(rules))
149 *** runcommand histedit 0 --commands=- --config extensions.histedit=
149 *** runcommand histedit 0 --commands=- --config extensions.histedit=
150
150
151 check that --cwd doesn't persist between requests:
151 check that --cwd doesn't persist between requests:
152
152
153 $ mkdir foo
153 $ mkdir foo
154 $ touch foo/bar
154 $ touch foo/bar
155 >>> from hgclient import readchannel, runcommand, check
155 >>> from hgclient import readchannel, runcommand, check
156 >>> @check
156 >>> @check
157 ... def cwd(server):
157 ... def cwd(server):
158 ... readchannel(server)
158 ... readchannel(server)
159 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
159 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
160 ... runcommand(server, ['st', 'foo/bar'])
160 ... runcommand(server, ['st', 'foo/bar'])
161 *** runcommand --cwd foo st bar
161 *** runcommand --cwd foo st bar
162 ? bar
162 ? bar
163 *** runcommand st foo/bar
163 *** runcommand st foo/bar
164 ? foo/bar
164 ? foo/bar
165
165
166 $ rm foo/bar
166 $ rm foo/bar
167
167
168
168
169 check that local configs for the cached repo aren't inherited when -R is used:
169 check that local configs for the cached repo aren't inherited when -R is used:
170
170
171 $ cat <<EOF >> .hg/hgrc
171 $ cat <<EOF >> .hg/hgrc
172 > [ui]
172 > [ui]
173 > foo = bar
173 > foo = bar
174 > EOF
174 > EOF
175
175
176 >>> from hgclient import readchannel, sep, runcommand, check
176 >>> from hgclient import readchannel, sep, runcommand, check
177 >>> @check
177 >>> @check
178 ... def localhgrc(server):
178 ... def localhgrc(server):
179 ... readchannel(server)
179 ... readchannel(server)
180 ...
180 ...
181 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
181 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
182 ... # show it
182 ... # show it
183 ... runcommand(server, ['showconfig'], outfilter=sep)
183 ... runcommand(server, ['showconfig'], outfilter=sep)
184 ...
184 ...
185 ... # but not for this repo
185 ... # but not for this repo
186 ... runcommand(server, ['init', 'foo'])
186 ... runcommand(server, ['init', 'foo'])
187 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
187 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
188 *** runcommand showconfig
188 *** runcommand showconfig
189 bundle.mainreporoot=$TESTTMP/repo
189 bundle.mainreporoot=$TESTTMP/repo
190 defaults.backout=-d "0 0"
191 defaults.commit=-d "0 0"
192 defaults.shelve=--date "0 0"
193 defaults.tag=-d "0 0"
194 devel.all-warnings=true
190 devel.all-warnings=true
195 devel.default-date=0 0
191 devel.default-date=0 0
196 largefiles.usercache=$TESTTMP/.cache/largefiles
192 largefiles.usercache=$TESTTMP/.cache/largefiles
197 ui.slash=True
193 ui.slash=True
198 ui.interactive=False
194 ui.interactive=False
199 ui.mergemarkers=detailed
195 ui.mergemarkers=detailed
200 ui.usehttp2=true (?)
196 ui.usehttp2=true (?)
201 ui.foo=bar
197 ui.foo=bar
202 ui.nontty=true
198 ui.nontty=true
203 web.address=localhost
199 web.address=localhost
204 web\.ipv6=(?:True|False) (re)
200 web\.ipv6=(?:True|False) (re)
205 *** runcommand init foo
201 *** runcommand init foo
206 *** runcommand -R foo showconfig ui defaults
202 *** runcommand -R foo showconfig ui defaults
207 defaults.backout=-d "0 0"
208 defaults.commit=-d "0 0"
209 defaults.shelve=--date "0 0"
210 defaults.tag=-d "0 0"
211 ui.slash=True
203 ui.slash=True
212 ui.interactive=False
204 ui.interactive=False
213 ui.mergemarkers=detailed
205 ui.mergemarkers=detailed
214 ui.usehttp2=true (?)
206 ui.usehttp2=true (?)
215 ui.nontty=true
207 ui.nontty=true
216
208
217 $ rm -R foo
209 $ rm -R foo
218
210
219 #if windows
211 #if windows
220 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
212 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
221 #else
213 #else
222 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
214 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
223 #endif
215 #endif
224
216
225 $ cat <<EOF > hook.py
217 $ cat <<EOF > hook.py
226 > from __future__ import print_function
218 > from __future__ import print_function
227 > import sys
219 > import sys
228 > def hook(**args):
220 > def hook(**args):
229 > print('hook talking')
221 > print('hook talking')
230 > print('now try to read something: %r' % sys.stdin.read())
222 > print('now try to read something: %r' % sys.stdin.read())
231 > EOF
223 > EOF
232
224
233 >>> from hgclient import readchannel, runcommand, check, stringio
225 >>> from hgclient import readchannel, runcommand, check, stringio
234 >>> @check
226 >>> @check
235 ... def hookoutput(server):
227 ... def hookoutput(server):
236 ... readchannel(server)
228 ... readchannel(server)
237 ... runcommand(server, ['--config',
229 ... runcommand(server, ['--config',
238 ... 'hooks.pre-identify=python:hook.hook',
230 ... 'hooks.pre-identify=python:hook.hook',
239 ... 'id'],
231 ... 'id'],
240 ... input=stringio('some input'))
232 ... input=stringio('some input'))
241 *** runcommand --config hooks.pre-identify=python:hook.hook id
233 *** runcommand --config hooks.pre-identify=python:hook.hook id
242 eff892de26ec tip
234 eff892de26ec tip
243
235
244 $ rm hook.py*
236 $ rm hook.py*
245
237
246 $ echo a >> a
238 $ echo a >> a
247 >>> import os
239 >>> import os
248 >>> from hgclient import readchannel, runcommand, check
240 >>> from hgclient import readchannel, runcommand, check
249 >>> @check
241 >>> @check
250 ... def outsidechanges(server):
242 ... def outsidechanges(server):
251 ... readchannel(server)
243 ... readchannel(server)
252 ... runcommand(server, ['status'])
244 ... runcommand(server, ['status'])
253 ... os.system('hg ci -Am2')
245 ... os.system('hg ci -Am2')
254 ... runcommand(server, ['tip'])
246 ... runcommand(server, ['tip'])
255 ... runcommand(server, ['status'])
247 ... runcommand(server, ['status'])
256 *** runcommand status
248 *** runcommand status
257 M a
249 M a
258 *** runcommand tip
250 *** runcommand tip
259 changeset: 1:d3a0a68be6de
251 changeset: 1:d3a0a68be6de
260 tag: tip
252 tag: tip
261 user: test
253 user: test
262 date: Thu Jan 01 00:00:00 1970 +0000
254 date: Thu Jan 01 00:00:00 1970 +0000
263 summary: 2
255 summary: 2
264
256
265 *** runcommand status
257 *** runcommand status
266
258
267 >>> import os
259 >>> import os
268 >>> from hgclient import readchannel, runcommand, check
260 >>> from hgclient import readchannel, runcommand, check
269 >>> @check
261 >>> @check
270 ... def bookmarks(server):
262 ... def bookmarks(server):
271 ... readchannel(server)
263 ... readchannel(server)
272 ... runcommand(server, ['bookmarks'])
264 ... runcommand(server, ['bookmarks'])
273 ...
265 ...
274 ... # changes .hg/bookmarks
266 ... # changes .hg/bookmarks
275 ... os.system('hg bookmark -i bm1')
267 ... os.system('hg bookmark -i bm1')
276 ... os.system('hg bookmark -i bm2')
268 ... os.system('hg bookmark -i bm2')
277 ... runcommand(server, ['bookmarks'])
269 ... runcommand(server, ['bookmarks'])
278 ...
270 ...
279 ... # changes .hg/bookmarks.current
271 ... # changes .hg/bookmarks.current
280 ... os.system('hg upd bm1 -q')
272 ... os.system('hg upd bm1 -q')
281 ... runcommand(server, ['bookmarks'])
273 ... runcommand(server, ['bookmarks'])
282 ...
274 ...
283 ... runcommand(server, ['bookmarks', 'bm3'])
275 ... runcommand(server, ['bookmarks', 'bm3'])
284 ... f = open('a', 'ab')
276 ... f = open('a', 'ab')
285 ... f.write('a\n')
277 ... f.write('a\n')
286 ... f.close()
278 ... f.close()
287 ... runcommand(server, ['commit', '-Amm'])
279 ... runcommand(server, ['commit', '-Amm'])
288 ... runcommand(server, ['bookmarks'])
280 ... runcommand(server, ['bookmarks'])
289 *** runcommand bookmarks
281 *** runcommand bookmarks
290 no bookmarks set
282 no bookmarks set
291 *** runcommand bookmarks
283 *** runcommand bookmarks
292 bm1 1:d3a0a68be6de
284 bm1 1:d3a0a68be6de
293 bm2 1:d3a0a68be6de
285 bm2 1:d3a0a68be6de
294 *** runcommand bookmarks
286 *** runcommand bookmarks
295 * bm1 1:d3a0a68be6de
287 * bm1 1:d3a0a68be6de
296 bm2 1:d3a0a68be6de
288 bm2 1:d3a0a68be6de
297 *** runcommand bookmarks bm3
289 *** runcommand bookmarks bm3
298 *** runcommand commit -Amm
290 *** runcommand commit -Amm
299 *** runcommand bookmarks
291 *** runcommand bookmarks
300 bm1 1:d3a0a68be6de
292 bm1 1:d3a0a68be6de
301 bm2 1:d3a0a68be6de
293 bm2 1:d3a0a68be6de
302 * bm3 2:aef17e88f5f0
294 * bm3 2:aef17e88f5f0
303
295
304 >>> import os
296 >>> import os
305 >>> from hgclient import readchannel, runcommand, check
297 >>> from hgclient import readchannel, runcommand, check
306 >>> @check
298 >>> @check
307 ... def tagscache(server):
299 ... def tagscache(server):
308 ... readchannel(server)
300 ... readchannel(server)
309 ... runcommand(server, ['id', '-t', '-r', '0'])
301 ... runcommand(server, ['id', '-t', '-r', '0'])
310 ... os.system('hg tag -r 0 foo')
302 ... os.system('hg tag -r 0 foo')
311 ... runcommand(server, ['id', '-t', '-r', '0'])
303 ... runcommand(server, ['id', '-t', '-r', '0'])
312 *** runcommand id -t -r 0
304 *** runcommand id -t -r 0
313
305
314 *** runcommand id -t -r 0
306 *** runcommand id -t -r 0
315 foo
307 foo
316
308
317 >>> import os
309 >>> import os
318 >>> from hgclient import readchannel, runcommand, check
310 >>> from hgclient import readchannel, runcommand, check
319 >>> @check
311 >>> @check
320 ... def setphase(server):
312 ... def setphase(server):
321 ... readchannel(server)
313 ... readchannel(server)
322 ... runcommand(server, ['phase', '-r', '.'])
314 ... runcommand(server, ['phase', '-r', '.'])
323 ... os.system('hg phase -r . -p')
315 ... os.system('hg phase -r . -p')
324 ... runcommand(server, ['phase', '-r', '.'])
316 ... runcommand(server, ['phase', '-r', '.'])
325 *** runcommand phase -r .
317 *** runcommand phase -r .
326 3: draft
318 3: draft
327 *** runcommand phase -r .
319 *** runcommand phase -r .
328 3: public
320 3: public
329
321
330 $ echo a >> a
322 $ echo a >> a
331 >>> from hgclient import readchannel, runcommand, check
323 >>> from hgclient import readchannel, runcommand, check
332 >>> @check
324 >>> @check
333 ... def rollback(server):
325 ... def rollback(server):
334 ... readchannel(server)
326 ... readchannel(server)
335 ... runcommand(server, ['phase', '-r', '.', '-p'])
327 ... runcommand(server, ['phase', '-r', '.', '-p'])
336 ... runcommand(server, ['commit', '-Am.'])
328 ... runcommand(server, ['commit', '-Am.'])
337 ... runcommand(server, ['rollback'])
329 ... runcommand(server, ['rollback'])
338 ... runcommand(server, ['phase', '-r', '.'])
330 ... runcommand(server, ['phase', '-r', '.'])
339 *** runcommand phase -r . -p
331 *** runcommand phase -r . -p
340 no phases changed
332 no phases changed
341 *** runcommand commit -Am.
333 *** runcommand commit -Am.
342 *** runcommand rollback
334 *** runcommand rollback
343 repository tip rolled back to revision 3 (undo commit)
335 repository tip rolled back to revision 3 (undo commit)
344 working directory now based on revision 3
336 working directory now based on revision 3
345 *** runcommand phase -r .
337 *** runcommand phase -r .
346 3: public
338 3: public
347
339
348 >>> import os
340 >>> import os
349 >>> from hgclient import readchannel, runcommand, check
341 >>> from hgclient import readchannel, runcommand, check
350 >>> @check
342 >>> @check
351 ... def branch(server):
343 ... def branch(server):
352 ... readchannel(server)
344 ... readchannel(server)
353 ... runcommand(server, ['branch'])
345 ... runcommand(server, ['branch'])
354 ... os.system('hg branch foo')
346 ... os.system('hg branch foo')
355 ... runcommand(server, ['branch'])
347 ... runcommand(server, ['branch'])
356 ... os.system('hg branch default')
348 ... os.system('hg branch default')
357 *** runcommand branch
349 *** runcommand branch
358 default
350 default
359 marked working directory as branch foo
351 marked working directory as branch foo
360 (branches are permanent and global, did you want a bookmark?)
352 (branches are permanent and global, did you want a bookmark?)
361 *** runcommand branch
353 *** runcommand branch
362 foo
354 foo
363 marked working directory as branch default
355 marked working directory as branch default
364 (branches are permanent and global, did you want a bookmark?)
356 (branches are permanent and global, did you want a bookmark?)
365
357
366 $ touch .hgignore
358 $ touch .hgignore
367 >>> import os
359 >>> import os
368 >>> from hgclient import readchannel, runcommand, check
360 >>> from hgclient import readchannel, runcommand, check
369 >>> @check
361 >>> @check
370 ... def hgignore(server):
362 ... def hgignore(server):
371 ... readchannel(server)
363 ... readchannel(server)
372 ... runcommand(server, ['commit', '-Am.'])
364 ... runcommand(server, ['commit', '-Am.'])
373 ... f = open('ignored-file', 'ab')
365 ... f = open('ignored-file', 'ab')
374 ... f.write('')
366 ... f.write('')
375 ... f.close()
367 ... f.close()
376 ... f = open('.hgignore', 'ab')
368 ... f = open('.hgignore', 'ab')
377 ... f.write('ignored-file')
369 ... f.write('ignored-file')
378 ... f.close()
370 ... f.close()
379 ... runcommand(server, ['status', '-i', '-u'])
371 ... runcommand(server, ['status', '-i', '-u'])
380 *** runcommand commit -Am.
372 *** runcommand commit -Am.
381 adding .hgignore
373 adding .hgignore
382 *** runcommand status -i -u
374 *** runcommand status -i -u
383 I ignored-file
375 I ignored-file
384
376
385 cache of non-public revisions should be invalidated on repository change
377 cache of non-public revisions should be invalidated on repository change
386 (issue4855):
378 (issue4855):
387
379
388 >>> import os
380 >>> import os
389 >>> from hgclient import readchannel, runcommand, check
381 >>> from hgclient import readchannel, runcommand, check
390 >>> @check
382 >>> @check
391 ... def phasesetscacheaftercommit(server):
383 ... def phasesetscacheaftercommit(server):
392 ... readchannel(server)
384 ... readchannel(server)
393 ... # load _phasecache._phaserevs and _phasesets
385 ... # load _phasecache._phaserevs and _phasesets
394 ... runcommand(server, ['log', '-qr', 'draft()'])
386 ... runcommand(server, ['log', '-qr', 'draft()'])
395 ... # create draft commits by another process
387 ... # create draft commits by another process
396 ... for i in xrange(5, 7):
388 ... for i in xrange(5, 7):
397 ... f = open('a', 'ab')
389 ... f = open('a', 'ab')
398 ... f.seek(0, os.SEEK_END)
390 ... f.seek(0, os.SEEK_END)
399 ... f.write('a\n')
391 ... f.write('a\n')
400 ... f.close()
392 ... f.close()
401 ... os.system('hg commit -Aqm%d' % i)
393 ... os.system('hg commit -Aqm%d' % i)
402 ... # new commits should be listed as draft revisions
394 ... # new commits should be listed as draft revisions
403 ... runcommand(server, ['log', '-qr', 'draft()'])
395 ... runcommand(server, ['log', '-qr', 'draft()'])
404 *** runcommand log -qr draft()
396 *** runcommand log -qr draft()
405 4:7966c8e3734d
397 4:7966c8e3734d
406 *** runcommand log -qr draft()
398 *** runcommand log -qr draft()
407 4:7966c8e3734d
399 4:7966c8e3734d
408 5:41f6602d1c4f
400 5:41f6602d1c4f
409 6:10501e202c35
401 6:10501e202c35
410
402
411 >>> import os
403 >>> import os
412 >>> from hgclient import readchannel, runcommand, check
404 >>> from hgclient import readchannel, runcommand, check
413 >>> @check
405 >>> @check
414 ... def phasesetscacheafterstrip(server):
406 ... def phasesetscacheafterstrip(server):
415 ... readchannel(server)
407 ... readchannel(server)
416 ... # load _phasecache._phaserevs and _phasesets
408 ... # load _phasecache._phaserevs and _phasesets
417 ... runcommand(server, ['log', '-qr', 'draft()'])
409 ... runcommand(server, ['log', '-qr', 'draft()'])
418 ... # strip cached revisions by another process
410 ... # strip cached revisions by another process
419 ... os.system('hg --config extensions.strip= strip -q 5')
411 ... os.system('hg --config extensions.strip= strip -q 5')
420 ... # shouldn't abort by "unknown revision '6'"
412 ... # shouldn't abort by "unknown revision '6'"
421 ... runcommand(server, ['log', '-qr', 'draft()'])
413 ... runcommand(server, ['log', '-qr', 'draft()'])
422 *** runcommand log -qr draft()
414 *** runcommand log -qr draft()
423 4:7966c8e3734d
415 4:7966c8e3734d
424 5:41f6602d1c4f
416 5:41f6602d1c4f
425 6:10501e202c35
417 6:10501e202c35
426 *** runcommand log -qr draft()
418 *** runcommand log -qr draft()
427 4:7966c8e3734d
419 4:7966c8e3734d
428
420
429 cache of phase roots should be invalidated on strip (issue3827):
421 cache of phase roots should be invalidated on strip (issue3827):
430
422
431 >>> import os
423 >>> import os
432 >>> from hgclient import readchannel, sep, runcommand, check
424 >>> from hgclient import readchannel, sep, runcommand, check
433 >>> @check
425 >>> @check
434 ... def phasecacheafterstrip(server):
426 ... def phasecacheafterstrip(server):
435 ... readchannel(server)
427 ... readchannel(server)
436 ...
428 ...
437 ... # create new head, 5:731265503d86
429 ... # create new head, 5:731265503d86
438 ... runcommand(server, ['update', '-C', '0'])
430 ... runcommand(server, ['update', '-C', '0'])
439 ... f = open('a', 'ab')
431 ... f = open('a', 'ab')
440 ... f.write('a\n')
432 ... f.write('a\n')
441 ... f.close()
433 ... f.close()
442 ... runcommand(server, ['commit', '-Am.', 'a'])
434 ... runcommand(server, ['commit', '-Am.', 'a'])
443 ... runcommand(server, ['log', '-Gq'])
435 ... runcommand(server, ['log', '-Gq'])
444 ...
436 ...
445 ... # make it public; draft marker moves to 4:7966c8e3734d
437 ... # make it public; draft marker moves to 4:7966c8e3734d
446 ... runcommand(server, ['phase', '-p', '.'])
438 ... runcommand(server, ['phase', '-p', '.'])
447 ... # load _phasecache.phaseroots
439 ... # load _phasecache.phaseroots
448 ... runcommand(server, ['phase', '.'], outfilter=sep)
440 ... runcommand(server, ['phase', '.'], outfilter=sep)
449 ...
441 ...
450 ... # strip 1::4 outside server
442 ... # strip 1::4 outside server
451 ... os.system('hg -q --config extensions.mq= strip 1')
443 ... os.system('hg -q --config extensions.mq= strip 1')
452 ...
444 ...
453 ... # shouldn't raise "7966c8e3734d: no node!"
445 ... # shouldn't raise "7966c8e3734d: no node!"
454 ... runcommand(server, ['branches'])
446 ... runcommand(server, ['branches'])
455 *** runcommand update -C 0
447 *** runcommand update -C 0
456 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
448 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
457 (leaving bookmark bm3)
449 (leaving bookmark bm3)
458 *** runcommand commit -Am. a
450 *** runcommand commit -Am. a
459 created new head
451 created new head
460 *** runcommand log -Gq
452 *** runcommand log -Gq
461 @ 5:731265503d86
453 @ 5:731265503d86
462 |
454 |
463 | o 4:7966c8e3734d
455 | o 4:7966c8e3734d
464 | |
456 | |
465 | o 3:b9b85890c400
457 | o 3:b9b85890c400
466 | |
458 | |
467 | o 2:aef17e88f5f0
459 | o 2:aef17e88f5f0
468 | |
460 | |
469 | o 1:d3a0a68be6de
461 | o 1:d3a0a68be6de
470 |/
462 |/
471 o 0:eff892de26ec
463 o 0:eff892de26ec
472
464
473 *** runcommand phase -p .
465 *** runcommand phase -p .
474 *** runcommand phase .
466 *** runcommand phase .
475 5: public
467 5: public
476 *** runcommand branches
468 *** runcommand branches
477 default 1:731265503d86
469 default 1:731265503d86
478
470
479 in-memory cache must be reloaded if transaction is aborted. otherwise
471 in-memory cache must be reloaded if transaction is aborted. otherwise
480 changelog and manifest would have invalid node:
472 changelog and manifest would have invalid node:
481
473
482 $ echo a >> a
474 $ echo a >> a
483 >>> from hgclient import readchannel, runcommand, check
475 >>> from hgclient import readchannel, runcommand, check
484 >>> @check
476 >>> @check
485 ... def txabort(server):
477 ... def txabort(server):
486 ... readchannel(server)
478 ... readchannel(server)
487 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
479 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
488 ... '-mfoo'])
480 ... '-mfoo'])
489 ... runcommand(server, ['verify'])
481 ... runcommand(server, ['verify'])
490 *** runcommand commit --config hooks.pretxncommit=false -mfoo
482 *** runcommand commit --config hooks.pretxncommit=false -mfoo
491 transaction abort!
483 transaction abort!
492 rollback completed
484 rollback completed
493 abort: pretxncommit hook exited with status 1
485 abort: pretxncommit hook exited with status 1
494 [255]
486 [255]
495 *** runcommand verify
487 *** runcommand verify
496 checking changesets
488 checking changesets
497 checking manifests
489 checking manifests
498 crosschecking files in changesets and manifests
490 crosschecking files in changesets and manifests
499 checking files
491 checking files
500 1 files, 2 changesets, 2 total revisions
492 1 files, 2 changesets, 2 total revisions
501 $ hg revert --no-backup -aq
493 $ hg revert --no-backup -aq
502
494
503 $ cat >> .hg/hgrc << EOF
495 $ cat >> .hg/hgrc << EOF
504 > [experimental]
496 > [experimental]
505 > evolution=createmarkers
497 > evolution=createmarkers
506 > EOF
498 > EOF
507
499
508 >>> import os
500 >>> import os
509 >>> from hgclient import readchannel, runcommand, check
501 >>> from hgclient import readchannel, runcommand, check
510 >>> @check
502 >>> @check
511 ... def obsolete(server):
503 ... def obsolete(server):
512 ... readchannel(server)
504 ... readchannel(server)
513 ...
505 ...
514 ... runcommand(server, ['up', 'null'])
506 ... runcommand(server, ['up', 'null'])
515 ... runcommand(server, ['phase', '-df', 'tip'])
507 ... runcommand(server, ['phase', '-df', 'tip'])
516 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
508 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
517 ... if os.name == 'nt':
509 ... if os.name == 'nt':
518 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
510 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
519 ... os.system(cmd)
511 ... os.system(cmd)
520 ... runcommand(server, ['log', '--hidden'])
512 ... runcommand(server, ['log', '--hidden'])
521 ... runcommand(server, ['log'])
513 ... runcommand(server, ['log'])
522 *** runcommand up null
514 *** runcommand up null
523 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
515 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
524 *** runcommand phase -df tip
516 *** runcommand phase -df tip
525 *** runcommand log --hidden
517 *** runcommand log --hidden
526 changeset: 1:731265503d86
518 changeset: 1:731265503d86
527 tag: tip
519 tag: tip
528 user: test
520 user: test
529 date: Thu Jan 01 00:00:00 1970 +0000
521 date: Thu Jan 01 00:00:00 1970 +0000
530 summary: .
522 summary: .
531
523
532 changeset: 0:eff892de26ec
524 changeset: 0:eff892de26ec
533 bookmark: bm1
525 bookmark: bm1
534 bookmark: bm2
526 bookmark: bm2
535 bookmark: bm3
527 bookmark: bm3
536 user: test
528 user: test
537 date: Thu Jan 01 00:00:00 1970 +0000
529 date: Thu Jan 01 00:00:00 1970 +0000
538 summary: 1
530 summary: 1
539
531
540 *** runcommand log
532 *** runcommand log
541 changeset: 0:eff892de26ec
533 changeset: 0:eff892de26ec
542 bookmark: bm1
534 bookmark: bm1
543 bookmark: bm2
535 bookmark: bm2
544 bookmark: bm3
536 bookmark: bm3
545 tag: tip
537 tag: tip
546 user: test
538 user: test
547 date: Thu Jan 01 00:00:00 1970 +0000
539 date: Thu Jan 01 00:00:00 1970 +0000
548 summary: 1
540 summary: 1
549
541
550
542
551 $ cat <<EOF >> .hg/hgrc
543 $ cat <<EOF >> .hg/hgrc
552 > [extensions]
544 > [extensions]
553 > mq =
545 > mq =
554 > EOF
546 > EOF
555
547
556 >>> import os
548 >>> import os
557 >>> from hgclient import readchannel, runcommand, check
549 >>> from hgclient import readchannel, runcommand, check
558 >>> @check
550 >>> @check
559 ... def mqoutsidechanges(server):
551 ... def mqoutsidechanges(server):
560 ... readchannel(server)
552 ... readchannel(server)
561 ...
553 ...
562 ... # load repo.mq
554 ... # load repo.mq
563 ... runcommand(server, ['qapplied'])
555 ... runcommand(server, ['qapplied'])
564 ... os.system('hg qnew 0.diff')
556 ... os.system('hg qnew 0.diff')
565 ... # repo.mq should be invalidated
557 ... # repo.mq should be invalidated
566 ... runcommand(server, ['qapplied'])
558 ... runcommand(server, ['qapplied'])
567 ...
559 ...
568 ... runcommand(server, ['qpop', '--all'])
560 ... runcommand(server, ['qpop', '--all'])
569 ... os.system('hg qqueue --create foo')
561 ... os.system('hg qqueue --create foo')
570 ... # repo.mq should be recreated to point to new queue
562 ... # repo.mq should be recreated to point to new queue
571 ... runcommand(server, ['qqueue', '--active'])
563 ... runcommand(server, ['qqueue', '--active'])
572 *** runcommand qapplied
564 *** runcommand qapplied
573 *** runcommand qapplied
565 *** runcommand qapplied
574 0.diff
566 0.diff
575 *** runcommand qpop --all
567 *** runcommand qpop --all
576 popping 0.diff
568 popping 0.diff
577 patch queue now empty
569 patch queue now empty
578 *** runcommand qqueue --active
570 *** runcommand qqueue --active
579 foo
571 foo
580
572
581 $ cat <<EOF > dbgui.py
573 $ cat <<EOF > dbgui.py
582 > import os, sys
574 > import os, sys
583 > from mercurial import commands, registrar
575 > from mercurial import commands, registrar
584 > cmdtable = {}
576 > cmdtable = {}
585 > command = registrar.command(cmdtable)
577 > command = registrar.command(cmdtable)
586 > @command("debuggetpass", norepo=True)
578 > @command("debuggetpass", norepo=True)
587 > def debuggetpass(ui):
579 > def debuggetpass(ui):
588 > ui.write("%s\\n" % ui.getpass())
580 > ui.write("%s\\n" % ui.getpass())
589 > @command("debugprompt", norepo=True)
581 > @command("debugprompt", norepo=True)
590 > def debugprompt(ui):
582 > def debugprompt(ui):
591 > ui.write("%s\\n" % ui.prompt("prompt:"))
583 > ui.write("%s\\n" % ui.prompt("prompt:"))
592 > @command("debugreadstdin", norepo=True)
584 > @command("debugreadstdin", norepo=True)
593 > def debugreadstdin(ui):
585 > def debugreadstdin(ui):
594 > ui.write("read: %r\n" % sys.stdin.read(1))
586 > ui.write("read: %r\n" % sys.stdin.read(1))
595 > @command("debugwritestdout", norepo=True)
587 > @command("debugwritestdout", norepo=True)
596 > def debugwritestdout(ui):
588 > def debugwritestdout(ui):
597 > os.write(1, "low-level stdout fd and\n")
589 > os.write(1, "low-level stdout fd and\n")
598 > sys.stdout.write("stdout should be redirected to /dev/null\n")
590 > sys.stdout.write("stdout should be redirected to /dev/null\n")
599 > sys.stdout.flush()
591 > sys.stdout.flush()
600 > EOF
592 > EOF
601 $ cat <<EOF >> .hg/hgrc
593 $ cat <<EOF >> .hg/hgrc
602 > [extensions]
594 > [extensions]
603 > dbgui = dbgui.py
595 > dbgui = dbgui.py
604 > EOF
596 > EOF
605
597
606 >>> from hgclient import readchannel, runcommand, check, stringio
598 >>> from hgclient import readchannel, runcommand, check, stringio
607 >>> @check
599 >>> @check
608 ... def getpass(server):
600 ... def getpass(server):
609 ... readchannel(server)
601 ... readchannel(server)
610 ... runcommand(server, ['debuggetpass', '--config',
602 ... runcommand(server, ['debuggetpass', '--config',
611 ... 'ui.interactive=True'],
603 ... 'ui.interactive=True'],
612 ... input=stringio('1234\n'))
604 ... input=stringio('1234\n'))
613 ... runcommand(server, ['debuggetpass', '--config',
605 ... runcommand(server, ['debuggetpass', '--config',
614 ... 'ui.interactive=True'],
606 ... 'ui.interactive=True'],
615 ... input=stringio('\n'))
607 ... input=stringio('\n'))
616 ... runcommand(server, ['debuggetpass', '--config',
608 ... runcommand(server, ['debuggetpass', '--config',
617 ... 'ui.interactive=True'],
609 ... 'ui.interactive=True'],
618 ... input=stringio(''))
610 ... input=stringio(''))
619 ... runcommand(server, ['debugprompt', '--config',
611 ... runcommand(server, ['debugprompt', '--config',
620 ... 'ui.interactive=True'],
612 ... 'ui.interactive=True'],
621 ... input=stringio('5678\n'))
613 ... input=stringio('5678\n'))
622 ... runcommand(server, ['debugreadstdin'])
614 ... runcommand(server, ['debugreadstdin'])
623 ... runcommand(server, ['debugwritestdout'])
615 ... runcommand(server, ['debugwritestdout'])
624 *** runcommand debuggetpass --config ui.interactive=True
616 *** runcommand debuggetpass --config ui.interactive=True
625 password: 1234
617 password: 1234
626 *** runcommand debuggetpass --config ui.interactive=True
618 *** runcommand debuggetpass --config ui.interactive=True
627 password:
619 password:
628 *** runcommand debuggetpass --config ui.interactive=True
620 *** runcommand debuggetpass --config ui.interactive=True
629 password: abort: response expected
621 password: abort: response expected
630 [255]
622 [255]
631 *** runcommand debugprompt --config ui.interactive=True
623 *** runcommand debugprompt --config ui.interactive=True
632 prompt: 5678
624 prompt: 5678
633 *** runcommand debugreadstdin
625 *** runcommand debugreadstdin
634 read: ''
626 read: ''
635 *** runcommand debugwritestdout
627 *** runcommand debugwritestdout
636
628
637
629
638 run commandserver in commandserver, which is silly but should work:
630 run commandserver in commandserver, which is silly but should work:
639
631
640 >>> from __future__ import print_function
632 >>> from __future__ import print_function
641 >>> from hgclient import readchannel, runcommand, check, stringio
633 >>> from hgclient import readchannel, runcommand, check, stringio
642 >>> @check
634 >>> @check
643 ... def nested(server):
635 ... def nested(server):
644 ... print('%c, %r' % readchannel(server))
636 ... print('%c, %r' % readchannel(server))
645 ... class nestedserver(object):
637 ... class nestedserver(object):
646 ... stdin = stringio('getencoding\n')
638 ... stdin = stringio('getencoding\n')
647 ... stdout = stringio()
639 ... stdout = stringio()
648 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
640 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
649 ... output=nestedserver.stdout, input=nestedserver.stdin)
641 ... output=nestedserver.stdout, input=nestedserver.stdin)
650 ... nestedserver.stdout.seek(0)
642 ... nestedserver.stdout.seek(0)
651 ... print('%c, %r' % readchannel(nestedserver)) # hello
643 ... print('%c, %r' % readchannel(nestedserver)) # hello
652 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
644 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
653 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
645 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
654 *** runcommand serve --cmdserver pipe
646 *** runcommand serve --cmdserver pipe
655 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
647 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
656 r, '*' (glob)
648 r, '*' (glob)
657
649
658
650
659 start without repository:
651 start without repository:
660
652
661 $ cd ..
653 $ cd ..
662
654
663 >>> from __future__ import print_function
655 >>> from __future__ import print_function
664 >>> from hgclient import readchannel, runcommand, check
656 >>> from hgclient import readchannel, runcommand, check
665 >>> @check
657 >>> @check
666 ... def hellomessage(server):
658 ... def hellomessage(server):
667 ... ch, data = readchannel(server)
659 ... ch, data = readchannel(server)
668 ... print('%c, %r' % (ch, data))
660 ... print('%c, %r' % (ch, data))
669 ... # run an arbitrary command to make sure the next thing the server
661 ... # run an arbitrary command to make sure the next thing the server
670 ... # sends isn't part of the hello message
662 ... # sends isn't part of the hello message
671 ... runcommand(server, ['id'])
663 ... runcommand(server, ['id'])
672 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
664 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
673 *** runcommand id
665 *** runcommand id
674 abort: there is no Mercurial repository here (.hg not found)
666 abort: there is no Mercurial repository here (.hg not found)
675 [255]
667 [255]
676
668
677 >>> from hgclient import readchannel, runcommand, check
669 >>> from hgclient import readchannel, runcommand, check
678 >>> @check
670 >>> @check
679 ... def startwithoutrepo(server):
671 ... def startwithoutrepo(server):
680 ... readchannel(server)
672 ... readchannel(server)
681 ... runcommand(server, ['init', 'repo2'])
673 ... runcommand(server, ['init', 'repo2'])
682 ... runcommand(server, ['id', '-R', 'repo2'])
674 ... runcommand(server, ['id', '-R', 'repo2'])
683 *** runcommand init repo2
675 *** runcommand init repo2
684 *** runcommand id -R repo2
676 *** runcommand id -R repo2
685 000000000000 tip
677 000000000000 tip
686
678
687
679
688 don't fall back to cwd if invalid -R path is specified (issue4805):
680 don't fall back to cwd if invalid -R path is specified (issue4805):
689
681
690 $ cd repo
682 $ cd repo
691 $ hg serve --cmdserver pipe -R ../nonexistent
683 $ hg serve --cmdserver pipe -R ../nonexistent
692 abort: repository ../nonexistent not found!
684 abort: repository ../nonexistent not found!
693 [255]
685 [255]
694 $ cd ..
686 $ cd ..
695
687
696
688
697 unix domain socket:
689 unix domain socket:
698
690
699 $ cd repo
691 $ cd repo
700 $ hg update -q
692 $ hg update -q
701
693
702 #if unix-socket unix-permissions
694 #if unix-socket unix-permissions
703
695
704 >>> from __future__ import print_function
696 >>> from __future__ import print_function
705 >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
697 >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
706 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
698 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
707 >>> def hellomessage(conn):
699 >>> def hellomessage(conn):
708 ... ch, data = readchannel(conn)
700 ... ch, data = readchannel(conn)
709 ... print('%c, %r' % (ch, data))
701 ... print('%c, %r' % (ch, data))
710 ... runcommand(conn, ['id'])
702 ... runcommand(conn, ['id'])
711 >>> check(hellomessage, server.connect)
703 >>> check(hellomessage, server.connect)
712 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
704 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
713 *** runcommand id
705 *** runcommand id
714 eff892de26ec tip bm1/bm2/bm3
706 eff892de26ec tip bm1/bm2/bm3
715 >>> def unknowncommand(conn):
707 >>> def unknowncommand(conn):
716 ... readchannel(conn)
708 ... readchannel(conn)
717 ... conn.stdin.write('unknowncommand\n')
709 ... conn.stdin.write('unknowncommand\n')
718 >>> check(unknowncommand, server.connect) # error sent to server.log
710 >>> check(unknowncommand, server.connect) # error sent to server.log
719 >>> def serverinput(conn):
711 >>> def serverinput(conn):
720 ... readchannel(conn)
712 ... readchannel(conn)
721 ... patch = """
713 ... patch = """
722 ... # HG changeset patch
714 ... # HG changeset patch
723 ... # User test
715 ... # User test
724 ... # Date 0 0
716 ... # Date 0 0
725 ... 2
717 ... 2
726 ...
718 ...
727 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
719 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
728 ... --- a/a
720 ... --- a/a
729 ... +++ b/a
721 ... +++ b/a
730 ... @@ -1,1 +1,2 @@
722 ... @@ -1,1 +1,2 @@
731 ... 1
723 ... 1
732 ... +2
724 ... +2
733 ... """
725 ... """
734 ... runcommand(conn, ['import', '-'], input=stringio(patch))
726 ... runcommand(conn, ['import', '-'], input=stringio(patch))
735 ... runcommand(conn, ['log', '-rtip', '-q'])
727 ... runcommand(conn, ['log', '-rtip', '-q'])
736 >>> check(serverinput, server.connect)
728 >>> check(serverinput, server.connect)
737 *** runcommand import -
729 *** runcommand import -
738 applying patch from stdin
730 applying patch from stdin
739 *** runcommand log -rtip -q
731 *** runcommand log -rtip -q
740 2:1ed24be7e7a0
732 2:1ed24be7e7a0
741 >>> server.shutdown()
733 >>> server.shutdown()
742
734
743 $ cat .hg/server.log
735 $ cat .hg/server.log
744 listening at .hg/server.sock
736 listening at .hg/server.sock
745 abort: unknown command unknowncommand
737 abort: unknown command unknowncommand
746 killed!
738 killed!
747 $ rm .hg/server.log
739 $ rm .hg/server.log
748
740
749 if server crashed before hello, traceback will be sent to 'e' channel as
741 if server crashed before hello, traceback will be sent to 'e' channel as
750 last ditch:
742 last ditch:
751
743
752 $ cat <<EOF >> .hg/hgrc
744 $ cat <<EOF >> .hg/hgrc
753 > [cmdserver]
745 > [cmdserver]
754 > log = inexistent/path.log
746 > log = inexistent/path.log
755 > EOF
747 > EOF
756 >>> from __future__ import print_function
748 >>> from __future__ import print_function
757 >>> from hgclient import unixserver, readchannel, check
749 >>> from hgclient import unixserver, readchannel, check
758 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
750 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
759 >>> def earlycrash(conn):
751 >>> def earlycrash(conn):
760 ... while True:
752 ... while True:
761 ... try:
753 ... try:
762 ... ch, data = readchannel(conn)
754 ... ch, data = readchannel(conn)
763 ... if not data.startswith(' '):
755 ... if not data.startswith(' '):
764 ... print('%c, %r' % (ch, data))
756 ... print('%c, %r' % (ch, data))
765 ... except EOFError:
757 ... except EOFError:
766 ... break
758 ... break
767 >>> check(earlycrash, server.connect)
759 >>> check(earlycrash, server.connect)
768 e, 'Traceback (most recent call last):\n'
760 e, 'Traceback (most recent call last):\n'
769 e, "IOError: *" (glob)
761 e, "IOError: *" (glob)
770 >>> server.shutdown()
762 >>> server.shutdown()
771
763
772 $ cat .hg/server.log | grep -v '^ '
764 $ cat .hg/server.log | grep -v '^ '
773 listening at .hg/server.sock
765 listening at .hg/server.sock
774 Traceback (most recent call last):
766 Traceback (most recent call last):
775 IOError: * (glob)
767 IOError: * (glob)
776 killed!
768 killed!
777 #endif
769 #endif
778 #if no-unix-socket
770 #if no-unix-socket
779
771
780 $ hg serve --cmdserver unix -a .hg/server.sock
772 $ hg serve --cmdserver unix -a .hg/server.sock
781 abort: unsupported platform
773 abort: unsupported platform
782 [255]
774 [255]
783
775
784 #endif
776 #endif
785
777
786 $ cd ..
778 $ cd ..
787
779
788 Test that accessing to invalid changelog cache is avoided at
780 Test that accessing to invalid changelog cache is avoided at
789 subsequent operations even if repo object is reused even after failure
781 subsequent operations even if repo object is reused even after failure
790 of transaction (see 0a7610758c42 also)
782 of transaction (see 0a7610758c42 also)
791
783
792 "hg log" after failure of transaction is needed to detect invalid
784 "hg log" after failure of transaction is needed to detect invalid
793 cache in repoview: this can't detect by "hg verify" only.
785 cache in repoview: this can't detect by "hg verify" only.
794
786
795 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
787 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
796 4) are tested, because '00changelog.i' are differently changed in each
788 4) are tested, because '00changelog.i' are differently changed in each
797 cases.
789 cases.
798
790
799 $ cat > $TESTTMP/failafterfinalize.py <<EOF
791 $ cat > $TESTTMP/failafterfinalize.py <<EOF
800 > # extension to abort transaction after finalization forcibly
792 > # extension to abort transaction after finalization forcibly
801 > from mercurial import commands, error, extensions, lock as lockmod
793 > from mercurial import commands, error, extensions, lock as lockmod
802 > def fail(tr):
794 > def fail(tr):
803 > raise error.Abort('fail after finalization')
795 > raise error.Abort('fail after finalization')
804 > def reposetup(ui, repo):
796 > def reposetup(ui, repo):
805 > class failrepo(repo.__class__):
797 > class failrepo(repo.__class__):
806 > def commitctx(self, ctx, error=False):
798 > def commitctx(self, ctx, error=False):
807 > if self.ui.configbool('failafterfinalize', 'fail'):
799 > if self.ui.configbool('failafterfinalize', 'fail'):
808 > # 'sorted()' by ASCII code on category names causes
800 > # 'sorted()' by ASCII code on category names causes
809 > # invoking 'fail' after finalization of changelog
801 > # invoking 'fail' after finalization of changelog
810 > # using "'cl-%i' % id(self)" as category name
802 > # using "'cl-%i' % id(self)" as category name
811 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
803 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
812 > return super(failrepo, self).commitctx(ctx, error)
804 > return super(failrepo, self).commitctx(ctx, error)
813 > repo.__class__ = failrepo
805 > repo.__class__ = failrepo
814 > EOF
806 > EOF
815
807
816 $ hg init repo3
808 $ hg init repo3
817 $ cd repo3
809 $ cd repo3
818
810
819 $ cat <<EOF >> $HGRCPATH
811 $ cat <<EOF >> $HGRCPATH
820 > [ui]
812 > [ui]
821 > logtemplate = {rev} {desc|firstline} ({files})\n
813 > logtemplate = {rev} {desc|firstline} ({files})\n
822 >
814 >
823 > [extensions]
815 > [extensions]
824 > failafterfinalize = $TESTTMP/failafterfinalize.py
816 > failafterfinalize = $TESTTMP/failafterfinalize.py
825 > EOF
817 > EOF
826
818
827 - test failure with "empty changelog"
819 - test failure with "empty changelog"
828
820
829 $ echo foo > foo
821 $ echo foo > foo
830 $ hg add foo
822 $ hg add foo
831
823
832 (failure before finalization)
824 (failure before finalization)
833
825
834 >>> from hgclient import readchannel, runcommand, check
826 >>> from hgclient import readchannel, runcommand, check
835 >>> @check
827 >>> @check
836 ... def abort(server):
828 ... def abort(server):
837 ... readchannel(server)
829 ... readchannel(server)
838 ... runcommand(server, ['commit',
830 ... runcommand(server, ['commit',
839 ... '--config', 'hooks.pretxncommit=false',
831 ... '--config', 'hooks.pretxncommit=false',
840 ... '-mfoo'])
832 ... '-mfoo'])
841 ... runcommand(server, ['log'])
833 ... runcommand(server, ['log'])
842 ... runcommand(server, ['verify', '-q'])
834 ... runcommand(server, ['verify', '-q'])
843 *** runcommand commit --config hooks.pretxncommit=false -mfoo
835 *** runcommand commit --config hooks.pretxncommit=false -mfoo
844 transaction abort!
836 transaction abort!
845 rollback completed
837 rollback completed
846 abort: pretxncommit hook exited with status 1
838 abort: pretxncommit hook exited with status 1
847 [255]
839 [255]
848 *** runcommand log
840 *** runcommand log
849 *** runcommand verify -q
841 *** runcommand verify -q
850
842
851 (failure after finalization)
843 (failure after finalization)
852
844
853 >>> from hgclient import readchannel, runcommand, check
845 >>> from hgclient import readchannel, runcommand, check
854 >>> @check
846 >>> @check
855 ... def abort(server):
847 ... def abort(server):
856 ... readchannel(server)
848 ... readchannel(server)
857 ... runcommand(server, ['commit',
849 ... runcommand(server, ['commit',
858 ... '--config', 'failafterfinalize.fail=true',
850 ... '--config', 'failafterfinalize.fail=true',
859 ... '-mfoo'])
851 ... '-mfoo'])
860 ... runcommand(server, ['log'])
852 ... runcommand(server, ['log'])
861 ... runcommand(server, ['verify', '-q'])
853 ... runcommand(server, ['verify', '-q'])
862 *** runcommand commit --config failafterfinalize.fail=true -mfoo
854 *** runcommand commit --config failafterfinalize.fail=true -mfoo
863 transaction abort!
855 transaction abort!
864 rollback completed
856 rollback completed
865 abort: fail after finalization
857 abort: fail after finalization
866 [255]
858 [255]
867 *** runcommand log
859 *** runcommand log
868 *** runcommand verify -q
860 *** runcommand verify -q
869
861
870 - test failure with "not-empty changelog"
862 - test failure with "not-empty changelog"
871
863
872 $ echo bar > bar
864 $ echo bar > bar
873 $ hg add bar
865 $ hg add bar
874 $ hg commit -mbar bar
866 $ hg commit -mbar bar
875
867
876 (failure before finalization)
868 (failure before finalization)
877
869
878 >>> from hgclient import readchannel, runcommand, check
870 >>> from hgclient import readchannel, runcommand, check
879 >>> @check
871 >>> @check
880 ... def abort(server):
872 ... def abort(server):
881 ... readchannel(server)
873 ... readchannel(server)
882 ... runcommand(server, ['commit',
874 ... runcommand(server, ['commit',
883 ... '--config', 'hooks.pretxncommit=false',
875 ... '--config', 'hooks.pretxncommit=false',
884 ... '-mfoo', 'foo'])
876 ... '-mfoo', 'foo'])
885 ... runcommand(server, ['log'])
877 ... runcommand(server, ['log'])
886 ... runcommand(server, ['verify', '-q'])
878 ... runcommand(server, ['verify', '-q'])
887 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
879 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
888 transaction abort!
880 transaction abort!
889 rollback completed
881 rollback completed
890 abort: pretxncommit hook exited with status 1
882 abort: pretxncommit hook exited with status 1
891 [255]
883 [255]
892 *** runcommand log
884 *** runcommand log
893 0 bar (bar)
885 0 bar (bar)
894 *** runcommand verify -q
886 *** runcommand verify -q
895
887
896 (failure after finalization)
888 (failure after finalization)
897
889
898 >>> from hgclient import readchannel, runcommand, check
890 >>> from hgclient import readchannel, runcommand, check
899 >>> @check
891 >>> @check
900 ... def abort(server):
892 ... def abort(server):
901 ... readchannel(server)
893 ... readchannel(server)
902 ... runcommand(server, ['commit',
894 ... runcommand(server, ['commit',
903 ... '--config', 'failafterfinalize.fail=true',
895 ... '--config', 'failafterfinalize.fail=true',
904 ... '-mfoo', 'foo'])
896 ... '-mfoo', 'foo'])
905 ... runcommand(server, ['log'])
897 ... runcommand(server, ['log'])
906 ... runcommand(server, ['verify', '-q'])
898 ... runcommand(server, ['verify', '-q'])
907 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
899 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
908 transaction abort!
900 transaction abort!
909 rollback completed
901 rollback completed
910 abort: fail after finalization
902 abort: fail after finalization
911 [255]
903 [255]
912 *** runcommand log
904 *** runcommand log
913 0 bar (bar)
905 0 bar (bar)
914 *** runcommand verify -q
906 *** runcommand verify -q
General Comments 0
You need to be logged in to leave comments. Login now