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