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