##// END OF EJS Templates
run-tests: move retesting result to Test.run()
Gregory Szorc -
r21330:3f79bacb default
parent child Browse files
Show More
@@ -1,1435 +1,1435 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 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from distutils import version
44 from distutils import version
45 import difflib
45 import difflib
46 import errno
46 import errno
47 import optparse
47 import optparse
48 import os
48 import os
49 import shutil
49 import shutil
50 import subprocess
50 import subprocess
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55 import random
55 import random
56 import re
56 import re
57 import threading
57 import threading
58 import killdaemons as killmod
58 import killdaemons as killmod
59 import Queue as queue
59 import Queue as queue
60
60
61 processlock = threading.Lock()
61 processlock = threading.Lock()
62
62
63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 # zombies but it's pretty harmless even if we do.
65 # zombies but it's pretty harmless even if we do.
66 if sys.version_info < (2, 5):
66 if sys.version_info < (2, 5):
67 subprocess._cleanup = lambda: None
67 subprocess._cleanup = lambda: None
68
68
69 closefds = os.name == 'posix'
69 closefds = os.name == 'posix'
70 def Popen4(cmd, wd, timeout, env=None):
70 def Popen4(cmd, wd, timeout, env=None):
71 processlock.acquire()
71 processlock.acquire()
72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 close_fds=closefds,
73 close_fds=closefds,
74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 stderr=subprocess.STDOUT)
75 stderr=subprocess.STDOUT)
76 processlock.release()
76 processlock.release()
77
77
78 p.fromchild = p.stdout
78 p.fromchild = p.stdout
79 p.tochild = p.stdin
79 p.tochild = p.stdin
80 p.childerr = p.stderr
80 p.childerr = p.stderr
81
81
82 p.timeout = False
82 p.timeout = False
83 if timeout:
83 if timeout:
84 def t():
84 def t():
85 start = time.time()
85 start = time.time()
86 while time.time() - start < timeout and p.returncode is None:
86 while time.time() - start < timeout and p.returncode is None:
87 time.sleep(.1)
87 time.sleep(.1)
88 p.timeout = True
88 p.timeout = True
89 if p.returncode is None:
89 if p.returncode is None:
90 terminate(p)
90 terminate(p)
91 threading.Thread(target=t).start()
91 threading.Thread(target=t).start()
92
92
93 return p
93 return p
94
94
95 # reserved exit code to skip test (used by hghave)
95 # reserved exit code to skip test (used by hghave)
96 SKIPPED_STATUS = 80
96 SKIPPED_STATUS = 80
97 SKIPPED_PREFIX = 'skipped: '
97 SKIPPED_PREFIX = 'skipped: '
98 FAILED_PREFIX = 'hghave check failed: '
98 FAILED_PREFIX = 'hghave check failed: '
99 PYTHON = sys.executable.replace('\\', '/')
99 PYTHON = sys.executable.replace('\\', '/')
100 IMPL_PATH = 'PYTHONPATH'
100 IMPL_PATH = 'PYTHONPATH'
101 if 'java' in sys.platform:
101 if 'java' in sys.platform:
102 IMPL_PATH = 'JYTHONPATH'
102 IMPL_PATH = 'JYTHONPATH'
103
103
104 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
104 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
105
105
106 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
106 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
107 "gunzip", "bunzip2", "sed"]
107 "gunzip", "bunzip2", "sed"]
108 createdfiles = []
108 createdfiles = []
109
109
110 defaults = {
110 defaults = {
111 'jobs': ('HGTEST_JOBS', 1),
111 'jobs': ('HGTEST_JOBS', 1),
112 'timeout': ('HGTEST_TIMEOUT', 180),
112 'timeout': ('HGTEST_TIMEOUT', 180),
113 'port': ('HGTEST_PORT', 20059),
113 'port': ('HGTEST_PORT', 20059),
114 'shell': ('HGTEST_SHELL', 'sh'),
114 'shell': ('HGTEST_SHELL', 'sh'),
115 }
115 }
116
116
117 def parselistfiles(files, listtype, warn=True):
117 def parselistfiles(files, listtype, warn=True):
118 entries = dict()
118 entries = dict()
119 for filename in files:
119 for filename in files:
120 try:
120 try:
121 path = os.path.expanduser(os.path.expandvars(filename))
121 path = os.path.expanduser(os.path.expandvars(filename))
122 f = open(path, "r")
122 f = open(path, "r")
123 except IOError, err:
123 except IOError, err:
124 if err.errno != errno.ENOENT:
124 if err.errno != errno.ENOENT:
125 raise
125 raise
126 if warn:
126 if warn:
127 print "warning: no such %s file: %s" % (listtype, filename)
127 print "warning: no such %s file: %s" % (listtype, filename)
128 continue
128 continue
129
129
130 for line in f.readlines():
130 for line in f.readlines():
131 line = line.split('#', 1)[0].strip()
131 line = line.split('#', 1)[0].strip()
132 if line:
132 if line:
133 entries[line] = filename
133 entries[line] = filename
134
134
135 f.close()
135 f.close()
136 return entries
136 return entries
137
137
138 def getparser():
138 def getparser():
139 parser = optparse.OptionParser("%prog [options] [tests]")
139 parser = optparse.OptionParser("%prog [options] [tests]")
140
140
141 # keep these sorted
141 # keep these sorted
142 parser.add_option("--blacklist", action="append",
142 parser.add_option("--blacklist", action="append",
143 help="skip tests listed in the specified blacklist file")
143 help="skip tests listed in the specified blacklist file")
144 parser.add_option("--whitelist", action="append",
144 parser.add_option("--whitelist", action="append",
145 help="always run tests listed in the specified whitelist file")
145 help="always run tests listed in the specified whitelist file")
146 parser.add_option("--changed", type="string",
146 parser.add_option("--changed", type="string",
147 help="run tests that are changed in parent rev or working directory")
147 help="run tests that are changed in parent rev or working directory")
148 parser.add_option("-C", "--annotate", action="store_true",
148 parser.add_option("-C", "--annotate", action="store_true",
149 help="output files annotated with coverage")
149 help="output files annotated with coverage")
150 parser.add_option("-c", "--cover", action="store_true",
150 parser.add_option("-c", "--cover", action="store_true",
151 help="print a test coverage report")
151 help="print a test coverage report")
152 parser.add_option("-d", "--debug", action="store_true",
152 parser.add_option("-d", "--debug", action="store_true",
153 help="debug mode: write output of test scripts to console"
153 help="debug mode: write output of test scripts to console"
154 " rather than capturing and diffing it (disables timeout)")
154 " rather than capturing and diffing it (disables timeout)")
155 parser.add_option("-f", "--first", action="store_true",
155 parser.add_option("-f", "--first", action="store_true",
156 help="exit on the first test failure")
156 help="exit on the first test failure")
157 parser.add_option("-H", "--htmlcov", action="store_true",
157 parser.add_option("-H", "--htmlcov", action="store_true",
158 help="create an HTML report of the coverage of the files")
158 help="create an HTML report of the coverage of the files")
159 parser.add_option("-i", "--interactive", action="store_true",
159 parser.add_option("-i", "--interactive", action="store_true",
160 help="prompt to accept changed output")
160 help="prompt to accept changed output")
161 parser.add_option("-j", "--jobs", type="int",
161 parser.add_option("-j", "--jobs", type="int",
162 help="number of jobs to run in parallel"
162 help="number of jobs to run in parallel"
163 " (default: $%s or %d)" % defaults['jobs'])
163 " (default: $%s or %d)" % defaults['jobs'])
164 parser.add_option("--keep-tmpdir", action="store_true",
164 parser.add_option("--keep-tmpdir", action="store_true",
165 help="keep temporary directory after running tests")
165 help="keep temporary directory after running tests")
166 parser.add_option("-k", "--keywords",
166 parser.add_option("-k", "--keywords",
167 help="run tests matching keywords")
167 help="run tests matching keywords")
168 parser.add_option("-l", "--local", action="store_true",
168 parser.add_option("-l", "--local", action="store_true",
169 help="shortcut for --with-hg=<testdir>/../hg")
169 help="shortcut for --with-hg=<testdir>/../hg")
170 parser.add_option("--loop", action="store_true",
170 parser.add_option("--loop", action="store_true",
171 help="loop tests repeatedly")
171 help="loop tests repeatedly")
172 parser.add_option("-n", "--nodiff", action="store_true",
172 parser.add_option("-n", "--nodiff", action="store_true",
173 help="skip showing test changes")
173 help="skip showing test changes")
174 parser.add_option("-p", "--port", type="int",
174 parser.add_option("-p", "--port", type="int",
175 help="port on which servers should listen"
175 help="port on which servers should listen"
176 " (default: $%s or %d)" % defaults['port'])
176 " (default: $%s or %d)" % defaults['port'])
177 parser.add_option("--compiler", type="string",
177 parser.add_option("--compiler", type="string",
178 help="compiler to build with")
178 help="compiler to build with")
179 parser.add_option("--pure", action="store_true",
179 parser.add_option("--pure", action="store_true",
180 help="use pure Python code instead of C extensions")
180 help="use pure Python code instead of C extensions")
181 parser.add_option("-R", "--restart", action="store_true",
181 parser.add_option("-R", "--restart", action="store_true",
182 help="restart at last error")
182 help="restart at last error")
183 parser.add_option("-r", "--retest", action="store_true",
183 parser.add_option("-r", "--retest", action="store_true",
184 help="retest failed tests")
184 help="retest failed tests")
185 parser.add_option("-S", "--noskips", action="store_true",
185 parser.add_option("-S", "--noskips", action="store_true",
186 help="don't report skip tests verbosely")
186 help="don't report skip tests verbosely")
187 parser.add_option("--shell", type="string",
187 parser.add_option("--shell", type="string",
188 help="shell to use (default: $%s or %s)" % defaults['shell'])
188 help="shell to use (default: $%s or %s)" % defaults['shell'])
189 parser.add_option("-t", "--timeout", type="int",
189 parser.add_option("-t", "--timeout", type="int",
190 help="kill errant tests after TIMEOUT seconds"
190 help="kill errant tests after TIMEOUT seconds"
191 " (default: $%s or %d)" % defaults['timeout'])
191 " (default: $%s or %d)" % defaults['timeout'])
192 parser.add_option("--time", action="store_true",
192 parser.add_option("--time", action="store_true",
193 help="time how long each test takes")
193 help="time how long each test takes")
194 parser.add_option("--tmpdir", type="string",
194 parser.add_option("--tmpdir", type="string",
195 help="run tests in the given temporary directory"
195 help="run tests in the given temporary directory"
196 " (implies --keep-tmpdir)")
196 " (implies --keep-tmpdir)")
197 parser.add_option("-v", "--verbose", action="store_true",
197 parser.add_option("-v", "--verbose", action="store_true",
198 help="output verbose messages")
198 help="output verbose messages")
199 parser.add_option("--view", type="string",
199 parser.add_option("--view", type="string",
200 help="external diff viewer")
200 help="external diff viewer")
201 parser.add_option("--with-hg", type="string",
201 parser.add_option("--with-hg", type="string",
202 metavar="HG",
202 metavar="HG",
203 help="test using specified hg script rather than a "
203 help="test using specified hg script rather than a "
204 "temporary installation")
204 "temporary installation")
205 parser.add_option("-3", "--py3k-warnings", action="store_true",
205 parser.add_option("-3", "--py3k-warnings", action="store_true",
206 help="enable Py3k warnings on Python 2.6+")
206 help="enable Py3k warnings on Python 2.6+")
207 parser.add_option('--extra-config-opt', action="append",
207 parser.add_option('--extra-config-opt', action="append",
208 help='set the given config opt in the test hgrc')
208 help='set the given config opt in the test hgrc')
209 parser.add_option('--random', action="store_true",
209 parser.add_option('--random', action="store_true",
210 help='run tests in random order')
210 help='run tests in random order')
211
211
212 for option, (envvar, default) in defaults.items():
212 for option, (envvar, default) in defaults.items():
213 defaults[option] = type(default)(os.environ.get(envvar, default))
213 defaults[option] = type(default)(os.environ.get(envvar, default))
214 parser.set_defaults(**defaults)
214 parser.set_defaults(**defaults)
215
215
216 return parser
216 return parser
217
217
218 def parseargs(args, parser):
218 def parseargs(args, parser):
219 (options, args) = parser.parse_args(args)
219 (options, args) = parser.parse_args(args)
220
220
221 # jython is always pure
221 # jython is always pure
222 if 'java' in sys.platform or '__pypy__' in sys.modules:
222 if 'java' in sys.platform or '__pypy__' in sys.modules:
223 options.pure = True
223 options.pure = True
224
224
225 if options.with_hg:
225 if options.with_hg:
226 options.with_hg = os.path.expanduser(options.with_hg)
226 options.with_hg = os.path.expanduser(options.with_hg)
227 if not (os.path.isfile(options.with_hg) and
227 if not (os.path.isfile(options.with_hg) and
228 os.access(options.with_hg, os.X_OK)):
228 os.access(options.with_hg, os.X_OK)):
229 parser.error('--with-hg must specify an executable hg script')
229 parser.error('--with-hg must specify an executable hg script')
230 if not os.path.basename(options.with_hg) == 'hg':
230 if not os.path.basename(options.with_hg) == 'hg':
231 sys.stderr.write('warning: --with-hg should specify an hg script\n')
231 sys.stderr.write('warning: --with-hg should specify an hg script\n')
232 if options.local:
232 if options.local:
233 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
233 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
234 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
234 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
235 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
235 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
236 parser.error('--local specified, but %r not found or not executable'
236 parser.error('--local specified, but %r not found or not executable'
237 % hgbin)
237 % hgbin)
238 options.with_hg = hgbin
238 options.with_hg = hgbin
239
239
240 options.anycoverage = options.cover or options.annotate or options.htmlcov
240 options.anycoverage = options.cover or options.annotate or options.htmlcov
241 if options.anycoverage:
241 if options.anycoverage:
242 try:
242 try:
243 import coverage
243 import coverage
244 covver = version.StrictVersion(coverage.__version__).version
244 covver = version.StrictVersion(coverage.__version__).version
245 if covver < (3, 3):
245 if covver < (3, 3):
246 parser.error('coverage options require coverage 3.3 or later')
246 parser.error('coverage options require coverage 3.3 or later')
247 except ImportError:
247 except ImportError:
248 parser.error('coverage options now require the coverage package')
248 parser.error('coverage options now require the coverage package')
249
249
250 if options.anycoverage and options.local:
250 if options.anycoverage and options.local:
251 # this needs some path mangling somewhere, I guess
251 # this needs some path mangling somewhere, I guess
252 parser.error("sorry, coverage options do not work when --local "
252 parser.error("sorry, coverage options do not work when --local "
253 "is specified")
253 "is specified")
254
254
255 global verbose
255 global verbose
256 if options.verbose:
256 if options.verbose:
257 verbose = ''
257 verbose = ''
258
258
259 if options.tmpdir:
259 if options.tmpdir:
260 options.tmpdir = os.path.expanduser(options.tmpdir)
260 options.tmpdir = os.path.expanduser(options.tmpdir)
261
261
262 if options.jobs < 1:
262 if options.jobs < 1:
263 parser.error('--jobs must be positive')
263 parser.error('--jobs must be positive')
264 if options.interactive and options.debug:
264 if options.interactive and options.debug:
265 parser.error("-i/--interactive and -d/--debug are incompatible")
265 parser.error("-i/--interactive and -d/--debug are incompatible")
266 if options.debug:
266 if options.debug:
267 if options.timeout != defaults['timeout']:
267 if options.timeout != defaults['timeout']:
268 sys.stderr.write(
268 sys.stderr.write(
269 'warning: --timeout option ignored with --debug\n')
269 'warning: --timeout option ignored with --debug\n')
270 options.timeout = 0
270 options.timeout = 0
271 if options.py3k_warnings:
271 if options.py3k_warnings:
272 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
272 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
273 parser.error('--py3k-warnings can only be used on Python 2.6+')
273 parser.error('--py3k-warnings can only be used on Python 2.6+')
274 if options.blacklist:
274 if options.blacklist:
275 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
275 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
276 if options.whitelist:
276 if options.whitelist:
277 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
277 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
278 else:
278 else:
279 options.whitelisted = {}
279 options.whitelisted = {}
280
280
281 return (options, args)
281 return (options, args)
282
282
283 def rename(src, dst):
283 def rename(src, dst):
284 """Like os.rename(), trade atomicity and opened files friendliness
284 """Like os.rename(), trade atomicity and opened files friendliness
285 for existing destination support.
285 for existing destination support.
286 """
286 """
287 shutil.copy(src, dst)
287 shutil.copy(src, dst)
288 os.remove(src)
288 os.remove(src)
289
289
290 def parsehghaveoutput(lines):
290 def parsehghaveoutput(lines):
291 '''Parse hghave log lines.
291 '''Parse hghave log lines.
292 Return tuple of lists (missing, failed):
292 Return tuple of lists (missing, failed):
293 * the missing/unknown features
293 * the missing/unknown features
294 * the features for which existence check failed'''
294 * the features for which existence check failed'''
295 missing = []
295 missing = []
296 failed = []
296 failed = []
297 for line in lines:
297 for line in lines:
298 if line.startswith(SKIPPED_PREFIX):
298 if line.startswith(SKIPPED_PREFIX):
299 line = line.splitlines()[0]
299 line = line.splitlines()[0]
300 missing.append(line[len(SKIPPED_PREFIX):])
300 missing.append(line[len(SKIPPED_PREFIX):])
301 elif line.startswith(FAILED_PREFIX):
301 elif line.startswith(FAILED_PREFIX):
302 line = line.splitlines()[0]
302 line = line.splitlines()[0]
303 failed.append(line[len(FAILED_PREFIX):])
303 failed.append(line[len(FAILED_PREFIX):])
304
304
305 return missing, failed
305 return missing, failed
306
306
307 def showdiff(expected, output, ref, err):
307 def showdiff(expected, output, ref, err):
308 print
308 print
309 servefail = False
309 servefail = False
310 for line in difflib.unified_diff(expected, output, ref, err):
310 for line in difflib.unified_diff(expected, output, ref, err):
311 sys.stdout.write(line)
311 sys.stdout.write(line)
312 if not servefail and line.startswith(
312 if not servefail and line.startswith(
313 '+ abort: child process failed to start'):
313 '+ abort: child process failed to start'):
314 servefail = True
314 servefail = True
315 return {'servefail': servefail}
315 return {'servefail': servefail}
316
316
317
317
318 verbose = False
318 verbose = False
319 def vlog(*msg):
319 def vlog(*msg):
320 if verbose is not False:
320 if verbose is not False:
321 iolock.acquire()
321 iolock.acquire()
322 if verbose:
322 if verbose:
323 print verbose,
323 print verbose,
324 for m in msg:
324 for m in msg:
325 print m,
325 print m,
326 print
326 print
327 sys.stdout.flush()
327 sys.stdout.flush()
328 iolock.release()
328 iolock.release()
329
329
330 def log(*msg):
330 def log(*msg):
331 iolock.acquire()
331 iolock.acquire()
332 if verbose:
332 if verbose:
333 print verbose,
333 print verbose,
334 for m in msg:
334 for m in msg:
335 print m,
335 print m,
336 print
336 print
337 sys.stdout.flush()
337 sys.stdout.flush()
338 iolock.release()
338 iolock.release()
339
339
340 def findprogram(program):
340 def findprogram(program):
341 """Search PATH for a executable program"""
341 """Search PATH for a executable program"""
342 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
342 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
343 name = os.path.join(p, program)
343 name = os.path.join(p, program)
344 if os.name == 'nt' or os.access(name, os.X_OK):
344 if os.name == 'nt' or os.access(name, os.X_OK):
345 return name
345 return name
346 return None
346 return None
347
347
348 def createhgrc(path, options):
348 def createhgrc(path, options):
349 # create a fresh hgrc
349 # create a fresh hgrc
350 hgrc = open(path, 'w')
350 hgrc = open(path, 'w')
351 hgrc.write('[ui]\n')
351 hgrc.write('[ui]\n')
352 hgrc.write('slash = True\n')
352 hgrc.write('slash = True\n')
353 hgrc.write('interactive = False\n')
353 hgrc.write('interactive = False\n')
354 hgrc.write('[defaults]\n')
354 hgrc.write('[defaults]\n')
355 hgrc.write('backout = -d "0 0"\n')
355 hgrc.write('backout = -d "0 0"\n')
356 hgrc.write('commit = -d "0 0"\n')
356 hgrc.write('commit = -d "0 0"\n')
357 hgrc.write('shelve = --date "0 0"\n')
357 hgrc.write('shelve = --date "0 0"\n')
358 hgrc.write('tag = -d "0 0"\n')
358 hgrc.write('tag = -d "0 0"\n')
359 if options.extra_config_opt:
359 if options.extra_config_opt:
360 for opt in options.extra_config_opt:
360 for opt in options.extra_config_opt:
361 section, key = opt.split('.', 1)
361 section, key = opt.split('.', 1)
362 assert '=' in key, ('extra config opt %s must '
362 assert '=' in key, ('extra config opt %s must '
363 'have an = for assignment' % opt)
363 'have an = for assignment' % opt)
364 hgrc.write('[%s]\n%s\n' % (section, key))
364 hgrc.write('[%s]\n%s\n' % (section, key))
365 hgrc.close()
365 hgrc.close()
366
366
367 def checktools():
367 def checktools():
368 # Before we go any further, check for pre-requisite tools
368 # Before we go any further, check for pre-requisite tools
369 # stuff from coreutils (cat, rm, etc) are not tested
369 # stuff from coreutils (cat, rm, etc) are not tested
370 for p in requiredtools:
370 for p in requiredtools:
371 if os.name == 'nt' and not p.endswith('.exe'):
371 if os.name == 'nt' and not p.endswith('.exe'):
372 p += '.exe'
372 p += '.exe'
373 found = findprogram(p)
373 found = findprogram(p)
374 if found:
374 if found:
375 vlog("# Found prerequisite", p, "at", found)
375 vlog("# Found prerequisite", p, "at", found)
376 else:
376 else:
377 print "WARNING: Did not find prerequisite tool: "+p
377 print "WARNING: Did not find prerequisite tool: "+p
378
378
379 def terminate(proc):
379 def terminate(proc):
380 """Terminate subprocess (with fallback for Python versions < 2.6)"""
380 """Terminate subprocess (with fallback for Python versions < 2.6)"""
381 vlog('# Terminating process %d' % proc.pid)
381 vlog('# Terminating process %d' % proc.pid)
382 try:
382 try:
383 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
383 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
384 except OSError:
384 except OSError:
385 pass
385 pass
386
386
387 def killdaemons(pidfile):
387 def killdaemons(pidfile):
388 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
388 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
389 logfn=vlog)
389 logfn=vlog)
390
390
391 def cleanup(options):
391 def cleanup(options):
392 if not options.keep_tmpdir:
392 if not options.keep_tmpdir:
393 vlog("# Cleaning up HGTMP", HGTMP)
393 vlog("# Cleaning up HGTMP", HGTMP)
394 shutil.rmtree(HGTMP, True)
394 shutil.rmtree(HGTMP, True)
395 for f in createdfiles:
395 for f in createdfiles:
396 try:
396 try:
397 os.remove(f)
397 os.remove(f)
398 except OSError:
398 except OSError:
399 pass
399 pass
400
400
401 def usecorrectpython():
401 def usecorrectpython():
402 # some tests run python interpreter. they must use same
402 # some tests run python interpreter. they must use same
403 # interpreter we use or bad things will happen.
403 # interpreter we use or bad things will happen.
404 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
404 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
405 if getattr(os, 'symlink', None):
405 if getattr(os, 'symlink', None):
406 vlog("# Making python executable in test path a symlink to '%s'" %
406 vlog("# Making python executable in test path a symlink to '%s'" %
407 sys.executable)
407 sys.executable)
408 mypython = os.path.join(TMPBINDIR, pyexename)
408 mypython = os.path.join(TMPBINDIR, pyexename)
409 try:
409 try:
410 if os.readlink(mypython) == sys.executable:
410 if os.readlink(mypython) == sys.executable:
411 return
411 return
412 os.unlink(mypython)
412 os.unlink(mypython)
413 except OSError, err:
413 except OSError, err:
414 if err.errno != errno.ENOENT:
414 if err.errno != errno.ENOENT:
415 raise
415 raise
416 if findprogram(pyexename) != sys.executable:
416 if findprogram(pyexename) != sys.executable:
417 try:
417 try:
418 os.symlink(sys.executable, mypython)
418 os.symlink(sys.executable, mypython)
419 createdfiles.append(mypython)
419 createdfiles.append(mypython)
420 except OSError, err:
420 except OSError, err:
421 # child processes may race, which is harmless
421 # child processes may race, which is harmless
422 if err.errno != errno.EEXIST:
422 if err.errno != errno.EEXIST:
423 raise
423 raise
424 else:
424 else:
425 exedir, exename = os.path.split(sys.executable)
425 exedir, exename = os.path.split(sys.executable)
426 vlog("# Modifying search path to find %s as %s in '%s'" %
426 vlog("# Modifying search path to find %s as %s in '%s'" %
427 (exename, pyexename, exedir))
427 (exename, pyexename, exedir))
428 path = os.environ['PATH'].split(os.pathsep)
428 path = os.environ['PATH'].split(os.pathsep)
429 while exedir in path:
429 while exedir in path:
430 path.remove(exedir)
430 path.remove(exedir)
431 os.environ['PATH'] = os.pathsep.join([exedir] + path)
431 os.environ['PATH'] = os.pathsep.join([exedir] + path)
432 if not findprogram(pyexename):
432 if not findprogram(pyexename):
433 print "WARNING: Cannot find %s in search path" % pyexename
433 print "WARNING: Cannot find %s in search path" % pyexename
434
434
435 def installhg(options):
435 def installhg(options):
436 vlog("# Performing temporary installation of HG")
436 vlog("# Performing temporary installation of HG")
437 installerrs = os.path.join("tests", "install.err")
437 installerrs = os.path.join("tests", "install.err")
438 compiler = ''
438 compiler = ''
439 if options.compiler:
439 if options.compiler:
440 compiler = '--compiler ' + options.compiler
440 compiler = '--compiler ' + options.compiler
441 pure = options.pure and "--pure" or ""
441 pure = options.pure and "--pure" or ""
442 py3 = ''
442 py3 = ''
443 if sys.version_info[0] == 3:
443 if sys.version_info[0] == 3:
444 py3 = '--c2to3'
444 py3 = '--c2to3'
445
445
446 # Run installer in hg root
446 # Run installer in hg root
447 script = os.path.realpath(sys.argv[0])
447 script = os.path.realpath(sys.argv[0])
448 hgroot = os.path.dirname(os.path.dirname(script))
448 hgroot = os.path.dirname(os.path.dirname(script))
449 os.chdir(hgroot)
449 os.chdir(hgroot)
450 nohome = '--home=""'
450 nohome = '--home=""'
451 if os.name == 'nt':
451 if os.name == 'nt':
452 # The --home="" trick works only on OS where os.sep == '/'
452 # The --home="" trick works only on OS where os.sep == '/'
453 # because of a distutils convert_path() fast-path. Avoid it at
453 # because of a distutils convert_path() fast-path. Avoid it at
454 # least on Windows for now, deal with .pydistutils.cfg bugs
454 # least on Windows for now, deal with .pydistutils.cfg bugs
455 # when they happen.
455 # when they happen.
456 nohome = ''
456 nohome = ''
457 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
457 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
458 ' build %(compiler)s --build-base="%(base)s"'
458 ' build %(compiler)s --build-base="%(base)s"'
459 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
459 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
460 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
460 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
461 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
461 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
462 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
462 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
463 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
463 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
464 'nohome': nohome, 'logfile': installerrs})
464 'nohome': nohome, 'logfile': installerrs})
465 vlog("# Running", cmd)
465 vlog("# Running", cmd)
466 if os.system(cmd) == 0:
466 if os.system(cmd) == 0:
467 if not options.verbose:
467 if not options.verbose:
468 os.remove(installerrs)
468 os.remove(installerrs)
469 else:
469 else:
470 f = open(installerrs)
470 f = open(installerrs)
471 for line in f:
471 for line in f:
472 print line,
472 print line,
473 f.close()
473 f.close()
474 sys.exit(1)
474 sys.exit(1)
475 os.chdir(TESTDIR)
475 os.chdir(TESTDIR)
476
476
477 usecorrectpython()
477 usecorrectpython()
478
478
479 if options.py3k_warnings and not options.anycoverage:
479 if options.py3k_warnings and not options.anycoverage:
480 vlog("# Updating hg command to enable Py3k Warnings switch")
480 vlog("# Updating hg command to enable Py3k Warnings switch")
481 f = open(os.path.join(BINDIR, 'hg'), 'r')
481 f = open(os.path.join(BINDIR, 'hg'), 'r')
482 lines = [line.rstrip() for line in f]
482 lines = [line.rstrip() for line in f]
483 lines[0] += ' -3'
483 lines[0] += ' -3'
484 f.close()
484 f.close()
485 f = open(os.path.join(BINDIR, 'hg'), 'w')
485 f = open(os.path.join(BINDIR, 'hg'), 'w')
486 for line in lines:
486 for line in lines:
487 f.write(line + '\n')
487 f.write(line + '\n')
488 f.close()
488 f.close()
489
489
490 hgbat = os.path.join(BINDIR, 'hg.bat')
490 hgbat = os.path.join(BINDIR, 'hg.bat')
491 if os.path.isfile(hgbat):
491 if os.path.isfile(hgbat):
492 # hg.bat expects to be put in bin/scripts while run-tests.py
492 # hg.bat expects to be put in bin/scripts while run-tests.py
493 # installation layout put it in bin/ directly. Fix it
493 # installation layout put it in bin/ directly. Fix it
494 f = open(hgbat, 'rb')
494 f = open(hgbat, 'rb')
495 data = f.read()
495 data = f.read()
496 f.close()
496 f.close()
497 if '"%~dp0..\python" "%~dp0hg" %*' in data:
497 if '"%~dp0..\python" "%~dp0hg" %*' in data:
498 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
498 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
499 '"%~dp0python" "%~dp0hg" %*')
499 '"%~dp0python" "%~dp0hg" %*')
500 f = open(hgbat, 'wb')
500 f = open(hgbat, 'wb')
501 f.write(data)
501 f.write(data)
502 f.close()
502 f.close()
503 else:
503 else:
504 print 'WARNING: cannot fix hg.bat reference to python.exe'
504 print 'WARNING: cannot fix hg.bat reference to python.exe'
505
505
506 if options.anycoverage:
506 if options.anycoverage:
507 custom = os.path.join(TESTDIR, 'sitecustomize.py')
507 custom = os.path.join(TESTDIR, 'sitecustomize.py')
508 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
508 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
509 vlog('# Installing coverage trigger to %s' % target)
509 vlog('# Installing coverage trigger to %s' % target)
510 shutil.copyfile(custom, target)
510 shutil.copyfile(custom, target)
511 rc = os.path.join(TESTDIR, '.coveragerc')
511 rc = os.path.join(TESTDIR, '.coveragerc')
512 vlog('# Installing coverage rc to %s' % rc)
512 vlog('# Installing coverage rc to %s' % rc)
513 os.environ['COVERAGE_PROCESS_START'] = rc
513 os.environ['COVERAGE_PROCESS_START'] = rc
514 fn = os.path.join(INST, '..', '.coverage')
514 fn = os.path.join(INST, '..', '.coverage')
515 os.environ['COVERAGE_FILE'] = fn
515 os.environ['COVERAGE_FILE'] = fn
516
516
517 def outputtimes(options):
517 def outputtimes(options):
518 vlog('# Producing time report')
518 vlog('# Producing time report')
519 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
519 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
520 cols = '%7.3f %s'
520 cols = '%7.3f %s'
521 print '\n%-7s %s' % ('Time', 'Test')
521 print '\n%-7s %s' % ('Time', 'Test')
522 for test, timetaken in times:
522 for test, timetaken in times:
523 print cols % (timetaken, test)
523 print cols % (timetaken, test)
524
524
525 def outputcoverage(options):
525 def outputcoverage(options):
526
526
527 vlog('# Producing coverage report')
527 vlog('# Producing coverage report')
528 os.chdir(PYTHONDIR)
528 os.chdir(PYTHONDIR)
529
529
530 def covrun(*args):
530 def covrun(*args):
531 cmd = 'coverage %s' % ' '.join(args)
531 cmd = 'coverage %s' % ' '.join(args)
532 vlog('# Running: %s' % cmd)
532 vlog('# Running: %s' % cmd)
533 os.system(cmd)
533 os.system(cmd)
534
534
535 covrun('-c')
535 covrun('-c')
536 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
536 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
537 covrun('-i', '-r', '"--omit=%s"' % omit) # report
537 covrun('-i', '-r', '"--omit=%s"' % omit) # report
538 if options.htmlcov:
538 if options.htmlcov:
539 htmldir = os.path.join(TESTDIR, 'htmlcov')
539 htmldir = os.path.join(TESTDIR, 'htmlcov')
540 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
540 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
541 if options.annotate:
541 if options.annotate:
542 adir = os.path.join(TESTDIR, 'annotated')
542 adir = os.path.join(TESTDIR, 'annotated')
543 if not os.path.isdir(adir):
543 if not os.path.isdir(adir):
544 os.mkdir(adir)
544 os.mkdir(adir)
545 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
545 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
546
546
547 class Test(object):
547 class Test(object):
548 """Encapsulates a single, runnable test.
548 """Encapsulates a single, runnable test.
549
549
550 Test instances can be run multiple times via run(). However, multiple
550 Test instances can be run multiple times via run(). However, multiple
551 runs cannot be run concurrently.
551 runs cannot be run concurrently.
552 """
552 """
553
553
554 def __init__(self, test, path, options, count, refpath, errpath):
554 def __init__(self, test, path, options, count, refpath, errpath):
555 self._test = test
555 self._test = test
556 self._path = path
556 self._path = path
557 self._options = options
557 self._options = options
558 self._count = count
558 self._count = count
559 self._daemonpids = []
559 self._daemonpids = []
560 self._refpath = refpath
560 self._refpath = refpath
561 self._errpath = errpath
561 self._errpath = errpath
562
562
563 # If we're not in --debug mode and reference output file exists,
563 # If we're not in --debug mode and reference output file exists,
564 # check test output against it.
564 # check test output against it.
565 if options.debug:
565 if options.debug:
566 self._refout = None # to match "out is None"
566 self._refout = None # to match "out is None"
567 elif os.path.exists(refpath):
567 elif os.path.exists(refpath):
568 f = open(refpath, 'r')
568 f = open(refpath, 'r')
569 self._refout = f.read().splitlines(True)
569 self._refout = f.read().splitlines(True)
570 f.close()
570 f.close()
571 else:
571 else:
572 self._refout = []
572 self._refout = []
573
573
574 self._threadtmp = os.path.join(HGTMP, 'child%d' % count)
574 self._threadtmp = os.path.join(HGTMP, 'child%d' % count)
575 os.mkdir(self._threadtmp)
575 os.mkdir(self._threadtmp)
576
576
577 def cleanup(self):
577 def cleanup(self):
578 for entry in self._daemonpids:
578 for entry in self._daemonpids:
579 killdaemons(entry)
579 killdaemons(entry)
580
580
581 if self._threadtmp and not self._options.keep_tmpdir:
581 if self._threadtmp and not self._options.keep_tmpdir:
582 shutil.rmtree(self._threadtmp, True)
582 shutil.rmtree(self._threadtmp, True)
583
583
584 def run(self, result):
584 def run(self, result):
585 if not os.path.exists(self._path):
585 if not os.path.exists(self._path):
586 result.skipped = True
586 result.skipped = True
587 return self.skip("Doesn't exist")
587 return self.skip("Doesn't exist")
588
588
589 options = self._options
589 options = self._options
590 if not (options.whitelisted and self._test in options.whitelisted):
590 if not (options.whitelisted and self._test in options.whitelisted):
591 if options.blacklist and self._test in options.blacklist:
591 if options.blacklist and self._test in options.blacklist:
592 result.skipped = True
592 result.skipped = True
593 return self.skip('blacklisted')
593 return self.skip('blacklisted')
594
594
595 if options.retest and not os.path.exists('%s.err' % self._test):
596 return self.ignore('not retesting')
597
595 # Remove any previous output files.
598 # Remove any previous output files.
596 if os.path.exists(self._errpath):
599 if os.path.exists(self._errpath):
597 os.remove(self._errpath)
600 os.remove(self._errpath)
598
601
599 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
602 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
600 os.mkdir(testtmp)
603 os.mkdir(testtmp)
601 replacements, port = self._getreplacements(testtmp)
604 replacements, port = self._getreplacements(testtmp)
602 env = self._getenv(testtmp, port)
605 env = self._getenv(testtmp, port)
603 self._daemonpids.append(env['DAEMON_PIDS'])
606 self._daemonpids.append(env['DAEMON_PIDS'])
604 createhgrc(env['HGRCPATH'], options)
607 createhgrc(env['HGRCPATH'], options)
605
608
606 starttime = time.time()
609 starttime = time.time()
607
610
608 def updateduration():
611 def updateduration():
609 result.duration = time.time() - starttime
612 result.duration = time.time() - starttime
610
613
611 try:
614 try:
612 ret, out = self._run(testtmp, replacements, env)
615 ret, out = self._run(testtmp, replacements, env)
613 updateduration()
616 updateduration()
614 result.ret = ret
617 result.ret = ret
615 result.out = out
618 result.out = out
616 except KeyboardInterrupt:
619 except KeyboardInterrupt:
617 updateduration()
620 updateduration()
618 log('INTERRUPTED: %s (after %d seconds)' % (self._test,
621 log('INTERRUPTED: %s (after %d seconds)' % (self._test,
619 result.duration))
622 result.duration))
620 raise
623 raise
621 except Exception, e:
624 except Exception, e:
622 updateduration()
625 updateduration()
623 result.exception = e
626 result.exception = e
624
627
625 killdaemons(env['DAEMON_PIDS'])
628 killdaemons(env['DAEMON_PIDS'])
626
629
627 result.refout = self._refout
630 result.refout = self._refout
628
631
629 if not options.keep_tmpdir:
632 if not options.keep_tmpdir:
630 shutil.rmtree(testtmp)
633 shutil.rmtree(testtmp)
631
634
632 def describe(ret):
635 def describe(ret):
633 if ret < 0:
636 if ret < 0:
634 return 'killed by signal: %d' % -ret
637 return 'killed by signal: %d' % -ret
635 return 'returned error code %d' % ret
638 return 'returned error code %d' % ret
636
639
637 if ret == SKIPPED_STATUS:
640 if ret == SKIPPED_STATUS:
638 if out is None: # Debug mode, nothing to parse.
641 if out is None: # Debug mode, nothing to parse.
639 missing = ['unknown']
642 missing = ['unknown']
640 failed = None
643 failed = None
641 else:
644 else:
642 missing, failed = parsehghaveoutput(out)
645 missing, failed = parsehghaveoutput(out)
643
646
644 if not missing:
647 if not missing:
645 missing = ['irrelevant']
648 missing = ['irrelevant']
646
649
647 if failed:
650 if failed:
648 return self.fail('hg have failed checking for %s' % failed[-1],
651 return self.fail('hg have failed checking for %s' % failed[-1],
649 ret)
652 ret)
650 else:
653 else:
651 result.skipped = True
654 result.skipped = True
652 return self.skip(missing[-1])
655 return self.skip(missing[-1])
653 elif ret == 'timeout':
656 elif ret == 'timeout':
654 return self.fail('timed out', ret)
657 return self.fail('timed out', ret)
655 elif out != self._refout:
658 elif out != self._refout:
656 info = {}
659 info = {}
657 if not options.nodiff:
660 if not options.nodiff:
658 iolock.acquire()
661 iolock.acquire()
659 if options.view:
662 if options.view:
660 os.system("%s %s %s" % (options.view, self._refpath,
663 os.system("%s %s %s" % (options.view, self._refpath,
661 self._errpath))
664 self._errpath))
662 else:
665 else:
663 info = showdiff(self._refout, out, self._refpath,
666 info = showdiff(self._refout, out, self._refpath,
664 self._errpath)
667 self._errpath)
665 iolock.release()
668 iolock.release()
666 msg = ''
669 msg = ''
667 if info.get('servefail'):
670 if info.get('servefail'):
668 msg += 'serve failed and '
671 msg += 'serve failed and '
669 if ret:
672 if ret:
670 msg += 'output changed and ' + describe(ret)
673 msg += 'output changed and ' + describe(ret)
671 else:
674 else:
672 msg += 'output changed'
675 msg += 'output changed'
673
676
674 return self.fail(msg, ret)
677 return self.fail(msg, ret)
675 elif ret:
678 elif ret:
676 return self.fail(describe(ret), ret)
679 return self.fail(describe(ret), ret)
677 else:
680 else:
678 return self.success()
681 return self.success()
679
682
680 def _run(self, testtmp, replacements, env):
683 def _run(self, testtmp, replacements, env):
681 raise NotImplemented('Subclasses must implement Test.run()')
684 raise NotImplemented('Subclasses must implement Test.run()')
682
685
683 def _getreplacements(self, testtmp):
686 def _getreplacements(self, testtmp):
684 port = self._options.port + self._count * 3
687 port = self._options.port + self._count * 3
685 r = [
688 r = [
686 (r':%s\b' % port, ':$HGPORT'),
689 (r':%s\b' % port, ':$HGPORT'),
687 (r':%s\b' % (port + 1), ':$HGPORT1'),
690 (r':%s\b' % (port + 1), ':$HGPORT1'),
688 (r':%s\b' % (port + 2), ':$HGPORT2'),
691 (r':%s\b' % (port + 2), ':$HGPORT2'),
689 ]
692 ]
690
693
691 if os.name == 'nt':
694 if os.name == 'nt':
692 r.append(
695 r.append(
693 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
696 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
694 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
697 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
695 for c in testtmp), '$TESTTMP'))
698 for c in testtmp), '$TESTTMP'))
696 else:
699 else:
697 r.append((re.escape(testtmp), '$TESTTMP'))
700 r.append((re.escape(testtmp), '$TESTTMP'))
698
701
699 return r, port
702 return r, port
700
703
701 def _getenv(self, testtmp, port):
704 def _getenv(self, testtmp, port):
702 env = os.environ.copy()
705 env = os.environ.copy()
703 env['TESTTMP'] = testtmp
706 env['TESTTMP'] = testtmp
704 env['HOME'] = testtmp
707 env['HOME'] = testtmp
705 env["HGPORT"] = str(port)
708 env["HGPORT"] = str(port)
706 env["HGPORT1"] = str(port + 1)
709 env["HGPORT1"] = str(port + 1)
707 env["HGPORT2"] = str(port + 2)
710 env["HGPORT2"] = str(port + 2)
708 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
711 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
709 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
712 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
710 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
713 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
711 env["HGMERGE"] = "internal:merge"
714 env["HGMERGE"] = "internal:merge"
712 env["HGUSER"] = "test"
715 env["HGUSER"] = "test"
713 env["HGENCODING"] = "ascii"
716 env["HGENCODING"] = "ascii"
714 env["HGENCODINGMODE"] = "strict"
717 env["HGENCODINGMODE"] = "strict"
715
718
716 # Reset some environment variables to well-known values so that
719 # Reset some environment variables to well-known values so that
717 # the tests produce repeatable output.
720 # the tests produce repeatable output.
718 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
721 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
719 env['TZ'] = 'GMT'
722 env['TZ'] = 'GMT'
720 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
723 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
721 env['COLUMNS'] = '80'
724 env['COLUMNS'] = '80'
722 env['TERM'] = 'xterm'
725 env['TERM'] = 'xterm'
723
726
724 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
727 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
725 'NO_PROXY').split():
728 'NO_PROXY').split():
726 if k in env:
729 if k in env:
727 del env[k]
730 del env[k]
728
731
729 # unset env related to hooks
732 # unset env related to hooks
730 for k in env.keys():
733 for k in env.keys():
731 if k.startswith('HG_'):
734 if k.startswith('HG_'):
732 del env[k]
735 del env[k]
733
736
734 return env
737 return env
735
738
736 def success(self):
739 def success(self):
737 return '.', self._test, ''
740 return '.', self._test, ''
738
741
739 def fail(self, msg, ret):
742 def fail(self, msg, ret):
740 warned = ret is False
743 warned = ret is False
741 if not self._options.nodiff:
744 if not self._options.nodiff:
742 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
745 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
743 msg))
746 msg))
744 if (not ret and self._options.interactive and
747 if (not ret and self._options.interactive and
745 os.path.exists(self._errpath)):
748 os.path.exists(self._errpath)):
746 iolock.acquire()
749 iolock.acquire()
747 print 'Accept this change? [n] ',
750 print 'Accept this change? [n] ',
748 answer = sys.stdin.readline().strip()
751 answer = sys.stdin.readline().strip()
749 iolock.release()
752 iolock.release()
750 if answer.lower() in ('y', 'yes').split():
753 if answer.lower() in ('y', 'yes').split():
751 if self._test.endswith('.t'):
754 if self._test.endswith('.t'):
752 rename(self._errpath, self._testpath)
755 rename(self._errpath, self._testpath)
753 else:
756 else:
754 rename(self._errpath, '%s.out' % self._testpath)
757 rename(self._errpath, '%s.out' % self._testpath)
755
758
756 return '.', self._test, ''
759 return '.', self._test, ''
757
760
758 return warned and '~' or '!', self._test, msg
761 return warned and '~' or '!', self._test, msg
759
762
760 def skip(self, msg):
763 def skip(self, msg):
761 if self._options.verbose:
764 if self._options.verbose:
762 log("\nSkipping %s: %s" % (self._path, msg))
765 log("\nSkipping %s: %s" % (self._path, msg))
763
766
764 return 's', self._test, msg
767 return 's', self._test, msg
765
768
769 def ignore(self, msg):
770 return 'i', self._test, msg
771
766 class TestResult(object):
772 class TestResult(object):
767 """Holds the result of a test execution."""
773 """Holds the result of a test execution."""
768
774
769 def __init__(self):
775 def __init__(self):
770 self.ret = None
776 self.ret = None
771 self.out = None
777 self.out = None
772 self.duration = None
778 self.duration = None
773 self.exception = None
779 self.exception = None
774 self.refout = None
780 self.refout = None
775 self.skipped = False
781 self.skipped = False
776
782
777 class PythonTest(Test):
783 class PythonTest(Test):
778 """A Python-based test."""
784 """A Python-based test."""
779 def _run(self, testtmp, replacements, env):
785 def _run(self, testtmp, replacements, env):
780 py3kswitch = self._options.py3k_warnings and ' -3' or ''
786 py3kswitch = self._options.py3k_warnings and ' -3' or ''
781 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
787 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
782 vlog("# Running", cmd)
788 vlog("# Running", cmd)
783 if os.name == 'nt':
789 if os.name == 'nt':
784 replacements.append((r'\r\n', '\n'))
790 replacements.append((r'\r\n', '\n'))
785 return run(cmd, testtmp, self._options, replacements, env)
791 return run(cmd, testtmp, self._options, replacements, env)
786
792
787
793
788 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
794 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
789 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
795 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
790 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
796 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
791 escapemap.update({'\\': '\\\\', '\r': r'\r'})
797 escapemap.update({'\\': '\\\\', '\r': r'\r'})
792 def escapef(m):
798 def escapef(m):
793 return escapemap[m.group(0)]
799 return escapemap[m.group(0)]
794 def stringescape(s):
800 def stringescape(s):
795 return escapesub(escapef, s)
801 return escapesub(escapef, s)
796
802
797 class TTest(Test):
803 class TTest(Test):
798 """A "t test" is a test backed by a .t file."""
804 """A "t test" is a test backed by a .t file."""
799
805
800 def _run(self, testtmp, replacements, env):
806 def _run(self, testtmp, replacements, env):
801 f = open(self._path)
807 f = open(self._path)
802 lines = f.readlines()
808 lines = f.readlines()
803 f.close()
809 f.close()
804
810
805 salt, script, after, expected = self._parsetest(lines, testtmp)
811 salt, script, after, expected = self._parsetest(lines, testtmp)
806
812
807 # Write out the generated script.
813 # Write out the generated script.
808 fname = '%s.sh' % testtmp
814 fname = '%s.sh' % testtmp
809 f = open(fname, 'w')
815 f = open(fname, 'w')
810 for l in script:
816 for l in script:
811 f.write(l)
817 f.write(l)
812 f.close()
818 f.close()
813
819
814 cmd = '%s "%s"' % (self._options.shell, fname)
820 cmd = '%s "%s"' % (self._options.shell, fname)
815 vlog("# Running", cmd)
821 vlog("# Running", cmd)
816
822
817 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
823 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
818 # Do not merge output if skipped. Return hghave message instead.
824 # Do not merge output if skipped. Return hghave message instead.
819 # Similarly, with --debug, output is None.
825 # Similarly, with --debug, output is None.
820 if exitcode == SKIPPED_STATUS or output is None:
826 if exitcode == SKIPPED_STATUS or output is None:
821 return exitcode, output
827 return exitcode, output
822
828
823 return self._processoutput(exitcode, output, salt, after, expected)
829 return self._processoutput(exitcode, output, salt, after, expected)
824
830
825 def _hghave(self, reqs, testtmp):
831 def _hghave(self, reqs, testtmp):
826 # TODO do something smarter when all other uses of hghave are gone.
832 # TODO do something smarter when all other uses of hghave are gone.
827 tdir = TESTDIR.replace('\\', '/')
833 tdir = TESTDIR.replace('\\', '/')
828 proc = Popen4('%s -c "%s/hghave %s"' %
834 proc = Popen4('%s -c "%s/hghave %s"' %
829 (self._options.shell, tdir, ' '.join(reqs)),
835 (self._options.shell, tdir, ' '.join(reqs)),
830 testtmp, 0)
836 testtmp, 0)
831 stdout, stderr = proc.communicate()
837 stdout, stderr = proc.communicate()
832 ret = proc.wait()
838 ret = proc.wait()
833 if wifexited(ret):
839 if wifexited(ret):
834 ret = os.WEXITSTATUS(ret)
840 ret = os.WEXITSTATUS(ret)
835 if ret == 2:
841 if ret == 2:
836 print stdout
842 print stdout
837 sys.exit(1)
843 sys.exit(1)
838
844
839 return ret == 0
845 return ret == 0
840
846
841 def _parsetest(self, lines, testtmp):
847 def _parsetest(self, lines, testtmp):
842 # We generate a shell script which outputs unique markers to line
848 # We generate a shell script which outputs unique markers to line
843 # up script results with our source. These markers include input
849 # up script results with our source. These markers include input
844 # line number and the last return code.
850 # line number and the last return code.
845 salt = "SALT" + str(time.time())
851 salt = "SALT" + str(time.time())
846 def addsalt(line, inpython):
852 def addsalt(line, inpython):
847 if inpython:
853 if inpython:
848 script.append('%s %d 0\n' % (salt, line))
854 script.append('%s %d 0\n' % (salt, line))
849 else:
855 else:
850 script.append('echo %s %s $?\n' % (salt, line))
856 script.append('echo %s %s $?\n' % (salt, line))
851
857
852 script = []
858 script = []
853
859
854 # After we run the shell script, we re-unify the script output
860 # After we run the shell script, we re-unify the script output
855 # with non-active parts of the source, with synchronization by our
861 # with non-active parts of the source, with synchronization by our
856 # SALT line number markers. The after table contains the non-active
862 # SALT line number markers. The after table contains the non-active
857 # components, ordered by line number.
863 # components, ordered by line number.
858 after = {}
864 after = {}
859
865
860 # Expected shell script output.
866 # Expected shell script output.
861 expected = {}
867 expected = {}
862
868
863 pos = prepos = -1
869 pos = prepos = -1
864
870
865 # True or False when in a true or false conditional section
871 # True or False when in a true or false conditional section
866 skipping = None
872 skipping = None
867
873
868 # We keep track of whether or not we're in a Python block so we
874 # We keep track of whether or not we're in a Python block so we
869 # can generate the surrounding doctest magic.
875 # can generate the surrounding doctest magic.
870 inpython = False
876 inpython = False
871
877
872 if self._options.debug:
878 if self._options.debug:
873 script.append('set -x\n')
879 script.append('set -x\n')
874 if os.getenv('MSYSTEM'):
880 if os.getenv('MSYSTEM'):
875 script.append('alias pwd="pwd -W"\n')
881 script.append('alias pwd="pwd -W"\n')
876
882
877 for n, l in enumerate(lines):
883 for n, l in enumerate(lines):
878 if not l.endswith('\n'):
884 if not l.endswith('\n'):
879 l += '\n'
885 l += '\n'
880 if l.startswith('#if'):
886 if l.startswith('#if'):
881 lsplit = l.split()
887 lsplit = l.split()
882 if len(lsplit) < 2 or lsplit[0] != '#if':
888 if len(lsplit) < 2 or lsplit[0] != '#if':
883 after.setdefault(pos, []).append(' !!! invalid #if\n')
889 after.setdefault(pos, []).append(' !!! invalid #if\n')
884 if skipping is not None:
890 if skipping is not None:
885 after.setdefault(pos, []).append(' !!! nested #if\n')
891 after.setdefault(pos, []).append(' !!! nested #if\n')
886 skipping = not self._hghave(lsplit[1:], testtmp)
892 skipping = not self._hghave(lsplit[1:], testtmp)
887 after.setdefault(pos, []).append(l)
893 after.setdefault(pos, []).append(l)
888 elif l.startswith('#else'):
894 elif l.startswith('#else'):
889 if skipping is None:
895 if skipping is None:
890 after.setdefault(pos, []).append(' !!! missing #if\n')
896 after.setdefault(pos, []).append(' !!! missing #if\n')
891 skipping = not skipping
897 skipping = not skipping
892 after.setdefault(pos, []).append(l)
898 after.setdefault(pos, []).append(l)
893 elif l.startswith('#endif'):
899 elif l.startswith('#endif'):
894 if skipping is None:
900 if skipping is None:
895 after.setdefault(pos, []).append(' !!! missing #if\n')
901 after.setdefault(pos, []).append(' !!! missing #if\n')
896 skipping = None
902 skipping = None
897 after.setdefault(pos, []).append(l)
903 after.setdefault(pos, []).append(l)
898 elif skipping:
904 elif skipping:
899 after.setdefault(pos, []).append(l)
905 after.setdefault(pos, []).append(l)
900 elif l.startswith(' >>> '): # python inlines
906 elif l.startswith(' >>> '): # python inlines
901 after.setdefault(pos, []).append(l)
907 after.setdefault(pos, []).append(l)
902 prepos = pos
908 prepos = pos
903 pos = n
909 pos = n
904 if not inpython:
910 if not inpython:
905 # We've just entered a Python block. Add the header.
911 # We've just entered a Python block. Add the header.
906 inpython = True
912 inpython = True
907 addsalt(prepos, False) # Make sure we report the exit code.
913 addsalt(prepos, False) # Make sure we report the exit code.
908 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
914 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
909 addsalt(n, True)
915 addsalt(n, True)
910 script.append(l[2:])
916 script.append(l[2:])
911 elif l.startswith(' ... '): # python inlines
917 elif l.startswith(' ... '): # python inlines
912 after.setdefault(prepos, []).append(l)
918 after.setdefault(prepos, []).append(l)
913 script.append(l[2:])
919 script.append(l[2:])
914 elif l.startswith(' $ '): # commands
920 elif l.startswith(' $ '): # commands
915 if inpython:
921 if inpython:
916 script.append('EOF\n')
922 script.append('EOF\n')
917 inpython = False
923 inpython = False
918 after.setdefault(pos, []).append(l)
924 after.setdefault(pos, []).append(l)
919 prepos = pos
925 prepos = pos
920 pos = n
926 pos = n
921 addsalt(n, False)
927 addsalt(n, False)
922 cmd = l[4:].split()
928 cmd = l[4:].split()
923 if len(cmd) == 2 and cmd[0] == 'cd':
929 if len(cmd) == 2 and cmd[0] == 'cd':
924 l = ' $ cd %s || exit 1\n' % cmd[1]
930 l = ' $ cd %s || exit 1\n' % cmd[1]
925 script.append(l[4:])
931 script.append(l[4:])
926 elif l.startswith(' > '): # continuations
932 elif l.startswith(' > '): # continuations
927 after.setdefault(prepos, []).append(l)
933 after.setdefault(prepos, []).append(l)
928 script.append(l[4:])
934 script.append(l[4:])
929 elif l.startswith(' '): # results
935 elif l.startswith(' '): # results
930 # Queue up a list of expected results.
936 # Queue up a list of expected results.
931 expected.setdefault(pos, []).append(l[2:])
937 expected.setdefault(pos, []).append(l[2:])
932 else:
938 else:
933 if inpython:
939 if inpython:
934 script.append('EOF\n')
940 script.append('EOF\n')
935 inpython = False
941 inpython = False
936 # Non-command/result. Queue up for merged output.
942 # Non-command/result. Queue up for merged output.
937 after.setdefault(pos, []).append(l)
943 after.setdefault(pos, []).append(l)
938
944
939 if inpython:
945 if inpython:
940 script.append('EOF\n')
946 script.append('EOF\n')
941 if skipping is not None:
947 if skipping is not None:
942 after.setdefault(pos, []).append(' !!! missing #endif\n')
948 after.setdefault(pos, []).append(' !!! missing #endif\n')
943 addsalt(n + 1, False)
949 addsalt(n + 1, False)
944
950
945 return salt, script, after, expected
951 return salt, script, after, expected
946
952
947 def _processoutput(self, exitcode, output, salt, after, expected):
953 def _processoutput(self, exitcode, output, salt, after, expected):
948 # Merge the script output back into a unified test.
954 # Merge the script output back into a unified test.
949 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
955 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
950 if exitcode != 0:
956 if exitcode != 0:
951 warnonly = 3
957 warnonly = 3
952
958
953 pos = -1
959 pos = -1
954 postout = []
960 postout = []
955 for l in output:
961 for l in output:
956 lout, lcmd = l, None
962 lout, lcmd = l, None
957 if salt in l:
963 if salt in l:
958 lout, lcmd = l.split(salt, 1)
964 lout, lcmd = l.split(salt, 1)
959
965
960 if lout:
966 if lout:
961 if not lout.endswith('\n'):
967 if not lout.endswith('\n'):
962 lout += ' (no-eol)\n'
968 lout += ' (no-eol)\n'
963
969
964 # Find the expected output at the current position.
970 # Find the expected output at the current position.
965 el = None
971 el = None
966 if expected.get(pos, None):
972 if expected.get(pos, None):
967 el = expected[pos].pop(0)
973 el = expected[pos].pop(0)
968
974
969 r = TTest.linematch(el, lout)
975 r = TTest.linematch(el, lout)
970 if isinstance(r, str):
976 if isinstance(r, str):
971 if r == '+glob':
977 if r == '+glob':
972 lout = el[:-1] + ' (glob)\n'
978 lout = el[:-1] + ' (glob)\n'
973 r = '' # Warn only this line.
979 r = '' # Warn only this line.
974 elif r == '-glob':
980 elif r == '-glob':
975 lout = ''.join(el.rsplit(' (glob)', 1))
981 lout = ''.join(el.rsplit(' (glob)', 1))
976 r = '' # Warn only this line.
982 r = '' # Warn only this line.
977 else:
983 else:
978 log('\ninfo, unknown linematch result: %r\n' % r)
984 log('\ninfo, unknown linematch result: %r\n' % r)
979 r = False
985 r = False
980 if r:
986 if r:
981 postout.append(' ' + el)
987 postout.append(' ' + el)
982 else:
988 else:
983 if needescape(lout):
989 if needescape(lout):
984 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
990 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
985 postout.append(' ' + lout) # Let diff deal with it.
991 postout.append(' ' + lout) # Let diff deal with it.
986 if r != '': # If line failed.
992 if r != '': # If line failed.
987 warnonly = 3 # for sure not
993 warnonly = 3 # for sure not
988 elif warnonly == 1: # Is "not yet" and line is warn only.
994 elif warnonly == 1: # Is "not yet" and line is warn only.
989 warnonly = 2 # Yes do warn.
995 warnonly = 2 # Yes do warn.
990
996
991 if lcmd:
997 if lcmd:
992 # Add on last return code.
998 # Add on last return code.
993 ret = int(lcmd.split()[1])
999 ret = int(lcmd.split()[1])
994 if ret != 0:
1000 if ret != 0:
995 postout.append(' [%s]\n' % ret)
1001 postout.append(' [%s]\n' % ret)
996 if pos in after:
1002 if pos in after:
997 # Merge in non-active test bits.
1003 # Merge in non-active test bits.
998 postout += after.pop(pos)
1004 postout += after.pop(pos)
999 pos = int(lcmd.split()[0])
1005 pos = int(lcmd.split()[0])
1000
1006
1001 if pos in after:
1007 if pos in after:
1002 postout += after.pop(pos)
1008 postout += after.pop(pos)
1003
1009
1004 if warnonly == 2:
1010 if warnonly == 2:
1005 exitcode = False # Set exitcode to warned.
1011 exitcode = False # Set exitcode to warned.
1006
1012
1007 return exitcode, postout
1013 return exitcode, postout
1008
1014
1009 @staticmethod
1015 @staticmethod
1010 def rematch(el, l):
1016 def rematch(el, l):
1011 try:
1017 try:
1012 # use \Z to ensure that the regex matches to the end of the string
1018 # use \Z to ensure that the regex matches to the end of the string
1013 if os.name == 'nt':
1019 if os.name == 'nt':
1014 return re.match(el + r'\r?\n\Z', l)
1020 return re.match(el + r'\r?\n\Z', l)
1015 return re.match(el + r'\n\Z', l)
1021 return re.match(el + r'\n\Z', l)
1016 except re.error:
1022 except re.error:
1017 # el is an invalid regex
1023 # el is an invalid regex
1018 return False
1024 return False
1019
1025
1020 @staticmethod
1026 @staticmethod
1021 def globmatch(el, l):
1027 def globmatch(el, l):
1022 # The only supported special characters are * and ? plus / which also
1028 # The only supported special characters are * and ? plus / which also
1023 # matches \ on windows. Escaping of these characters is supported.
1029 # matches \ on windows. Escaping of these characters is supported.
1024 if el + '\n' == l:
1030 if el + '\n' == l:
1025 if os.altsep:
1031 if os.altsep:
1026 # matching on "/" is not needed for this line
1032 # matching on "/" is not needed for this line
1027 return '-glob'
1033 return '-glob'
1028 return True
1034 return True
1029 i, n = 0, len(el)
1035 i, n = 0, len(el)
1030 res = ''
1036 res = ''
1031 while i < n:
1037 while i < n:
1032 c = el[i]
1038 c = el[i]
1033 i += 1
1039 i += 1
1034 if c == '\\' and el[i] in '*?\\/':
1040 if c == '\\' and el[i] in '*?\\/':
1035 res += el[i - 1:i + 1]
1041 res += el[i - 1:i + 1]
1036 i += 1
1042 i += 1
1037 elif c == '*':
1043 elif c == '*':
1038 res += '.*'
1044 res += '.*'
1039 elif c == '?':
1045 elif c == '?':
1040 res += '.'
1046 res += '.'
1041 elif c == '/' and os.altsep:
1047 elif c == '/' and os.altsep:
1042 res += '[/\\\\]'
1048 res += '[/\\\\]'
1043 else:
1049 else:
1044 res += re.escape(c)
1050 res += re.escape(c)
1045 return TTest.rematch(res, l)
1051 return TTest.rematch(res, l)
1046
1052
1047 @staticmethod
1053 @staticmethod
1048 def linematch(el, l):
1054 def linematch(el, l):
1049 if el == l: # perfect match (fast)
1055 if el == l: # perfect match (fast)
1050 return True
1056 return True
1051 if el:
1057 if el:
1052 if el.endswith(" (esc)\n"):
1058 if el.endswith(" (esc)\n"):
1053 el = el[:-7].decode('string-escape') + '\n'
1059 el = el[:-7].decode('string-escape') + '\n'
1054 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1060 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1055 return True
1061 return True
1056 if el.endswith(" (re)\n"):
1062 if el.endswith(" (re)\n"):
1057 return TTest.rematch(el[:-6], l)
1063 return TTest.rematch(el[:-6], l)
1058 if el.endswith(" (glob)\n"):
1064 if el.endswith(" (glob)\n"):
1059 return TTest.globmatch(el[:-8], l)
1065 return TTest.globmatch(el[:-8], l)
1060 if os.altsep and l.replace('\\', '/') == el:
1066 if os.altsep and l.replace('\\', '/') == el:
1061 return '+glob'
1067 return '+glob'
1062 return False
1068 return False
1063
1069
1064 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1070 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1065 def run(cmd, wd, options, replacements, env):
1071 def run(cmd, wd, options, replacements, env):
1066 """Run command in a sub-process, capturing the output (stdout and stderr).
1072 """Run command in a sub-process, capturing the output (stdout and stderr).
1067 Return a tuple (exitcode, output). output is None in debug mode."""
1073 Return a tuple (exitcode, output). output is None in debug mode."""
1068 # TODO: Use subprocess.Popen if we're running on Python 2.4
1074 # TODO: Use subprocess.Popen if we're running on Python 2.4
1069 if options.debug:
1075 if options.debug:
1070 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1076 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1071 ret = proc.wait()
1077 ret = proc.wait()
1072 return (ret, None)
1078 return (ret, None)
1073
1079
1074 proc = Popen4(cmd, wd, options.timeout, env)
1080 proc = Popen4(cmd, wd, options.timeout, env)
1075 def cleanup():
1081 def cleanup():
1076 terminate(proc)
1082 terminate(proc)
1077 ret = proc.wait()
1083 ret = proc.wait()
1078 if ret == 0:
1084 if ret == 0:
1079 ret = signal.SIGTERM << 8
1085 ret = signal.SIGTERM << 8
1080 killdaemons(env['DAEMON_PIDS'])
1086 killdaemons(env['DAEMON_PIDS'])
1081 return ret
1087 return ret
1082
1088
1083 output = ''
1089 output = ''
1084 proc.tochild.close()
1090 proc.tochild.close()
1085
1091
1086 try:
1092 try:
1087 output = proc.fromchild.read()
1093 output = proc.fromchild.read()
1088 except KeyboardInterrupt:
1094 except KeyboardInterrupt:
1089 vlog('# Handling keyboard interrupt')
1095 vlog('# Handling keyboard interrupt')
1090 cleanup()
1096 cleanup()
1091 raise
1097 raise
1092
1098
1093 ret = proc.wait()
1099 ret = proc.wait()
1094 if wifexited(ret):
1100 if wifexited(ret):
1095 ret = os.WEXITSTATUS(ret)
1101 ret = os.WEXITSTATUS(ret)
1096
1102
1097 if proc.timeout:
1103 if proc.timeout:
1098 ret = 'timeout'
1104 ret = 'timeout'
1099
1105
1100 if ret:
1106 if ret:
1101 killdaemons(env['DAEMON_PIDS'])
1107 killdaemons(env['DAEMON_PIDS'])
1102
1108
1103 if abort:
1109 if abort:
1104 raise KeyboardInterrupt()
1110 raise KeyboardInterrupt()
1105
1111
1106 for s, r in replacements:
1112 for s, r in replacements:
1107 output = re.sub(s, r, output)
1113 output = re.sub(s, r, output)
1108 return ret, output.splitlines(True)
1114 return ret, output.splitlines(True)
1109
1115
1110 def runone(options, test, count):
1116 def runone(options, test, count):
1111 '''returns a result element: (code, test, msg)'''
1117 '''returns a result element: (code, test, msg)'''
1112
1118
1113 def skip(msg):
1119 def skip(msg):
1114 if options.verbose:
1120 if options.verbose:
1115 log("\nSkipping %s: %s" % (testpath, msg))
1121 log("\nSkipping %s: %s" % (testpath, msg))
1116 return 's', test, msg
1122 return 's', test, msg
1117
1123
1118 def ignore(msg):
1119 return 'i', test, msg
1120
1121 testpath = os.path.join(TESTDIR, test)
1124 testpath = os.path.join(TESTDIR, test)
1122 err = os.path.join(TESTDIR, test + ".err")
1125 err = os.path.join(TESTDIR, test + ".err")
1123 lctest = test.lower()
1126 lctest = test.lower()
1124
1127
1125 if not (options.whitelisted and test in options.whitelisted):
1128 if not (options.whitelisted and test in options.whitelisted):
1126 if options.retest and not os.path.exists(test + ".err"):
1127 return ignore("not retesting")
1128
1129 if options.keywords:
1129 if options.keywords:
1130 fp = open(test)
1130 fp = open(test)
1131 t = fp.read().lower() + test.lower()
1131 t = fp.read().lower() + test.lower()
1132 fp.close()
1132 fp.close()
1133 for k in options.keywords.lower().split():
1133 for k in options.keywords.lower().split():
1134 if k in t:
1134 if k in t:
1135 break
1135 break
1136 else:
1136 else:
1137 return ignore("doesn't match keyword")
1137 return ignore("doesn't match keyword")
1138
1138
1139 if not os.path.basename(lctest).startswith("test-"):
1139 if not os.path.basename(lctest).startswith("test-"):
1140 return skip("not a test file")
1140 return skip("not a test file")
1141 for ext, cls, out in testtypes:
1141 for ext, cls, out in testtypes:
1142 if lctest.endswith(ext):
1142 if lctest.endswith(ext):
1143 runner = cls
1143 runner = cls
1144 ref = os.path.join(TESTDIR, test + out)
1144 ref = os.path.join(TESTDIR, test + out)
1145 break
1145 break
1146 else:
1146 else:
1147 return skip("unknown test type")
1147 return skip("unknown test type")
1148
1148
1149 vlog("# Test", test)
1149 vlog("# Test", test)
1150
1150
1151 t = runner(test, testpath, options, count, ref, err)
1151 t = runner(test, testpath, options, count, ref, err)
1152 res = TestResult()
1152 res = TestResult()
1153 result = t.run(res)
1153 result = t.run(res)
1154
1154
1155 if res.exception:
1155 if res.exception:
1156 return t.fail('Exception during execution: %s' % res.exception, 255)
1156 return t.fail('Exception during execution: %s' % res.exception, 255)
1157
1157
1158 ret = res.ret
1158 ret = res.ret
1159 out = res.out
1159 out = res.out
1160
1160
1161 times.append((test, res.duration))
1161 times.append((test, res.duration))
1162 vlog("# Ret was:", ret)
1162 vlog("# Ret was:", ret)
1163
1163
1164 skipped = res.skipped
1164 skipped = res.skipped
1165 refout = res.refout
1165 refout = res.refout
1166
1166
1167 if (ret != 0 or out != refout) and not skipped and not options.debug:
1167 if (ret != 0 or out != refout) and not skipped and not options.debug:
1168 # Save errors to a file for diagnosis
1168 # Save errors to a file for diagnosis
1169 f = open(err, "wb")
1169 f = open(err, "wb")
1170 for line in out:
1170 for line in out:
1171 f.write(line)
1171 f.write(line)
1172 f.close()
1172 f.close()
1173
1173
1174 if not options.verbose:
1174 if not options.verbose:
1175 iolock.acquire()
1175 iolock.acquire()
1176 sys.stdout.write(result[0])
1176 sys.stdout.write(result[0])
1177 sys.stdout.flush()
1177 sys.stdout.flush()
1178 iolock.release()
1178 iolock.release()
1179
1179
1180 t.cleanup()
1180 t.cleanup()
1181
1181
1182 return result
1182 return result
1183
1183
1184 _hgpath = None
1184 _hgpath = None
1185
1185
1186 def _gethgpath():
1186 def _gethgpath():
1187 """Return the path to the mercurial package that is actually found by
1187 """Return the path to the mercurial package that is actually found by
1188 the current Python interpreter."""
1188 the current Python interpreter."""
1189 global _hgpath
1189 global _hgpath
1190 if _hgpath is not None:
1190 if _hgpath is not None:
1191 return _hgpath
1191 return _hgpath
1192
1192
1193 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1193 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1194 pipe = os.popen(cmd % PYTHON)
1194 pipe = os.popen(cmd % PYTHON)
1195 try:
1195 try:
1196 _hgpath = pipe.read().strip()
1196 _hgpath = pipe.read().strip()
1197 finally:
1197 finally:
1198 pipe.close()
1198 pipe.close()
1199 return _hgpath
1199 return _hgpath
1200
1200
1201 def _checkhglib(verb):
1201 def _checkhglib(verb):
1202 """Ensure that the 'mercurial' package imported by python is
1202 """Ensure that the 'mercurial' package imported by python is
1203 the one we expect it to be. If not, print a warning to stderr."""
1203 the one we expect it to be. If not, print a warning to stderr."""
1204 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1204 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1205 actualhg = _gethgpath()
1205 actualhg = _gethgpath()
1206 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1206 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1207 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1207 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1208 ' (expected %s)\n'
1208 ' (expected %s)\n'
1209 % (verb, actualhg, expecthg))
1209 % (verb, actualhg, expecthg))
1210
1210
1211 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1211 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1212 times = []
1212 times = []
1213 iolock = threading.Lock()
1213 iolock = threading.Lock()
1214 abort = False
1214 abort = False
1215
1215
1216 def scheduletests(options, tests):
1216 def scheduletests(options, tests):
1217 jobs = options.jobs
1217 jobs = options.jobs
1218 done = queue.Queue()
1218 done = queue.Queue()
1219 running = 0
1219 running = 0
1220 count = 0
1220 count = 0
1221 global abort
1221 global abort
1222
1222
1223 def job(test, count):
1223 def job(test, count):
1224 try:
1224 try:
1225 done.put(runone(options, test, count))
1225 done.put(runone(options, test, count))
1226 except KeyboardInterrupt:
1226 except KeyboardInterrupt:
1227 pass
1227 pass
1228 except: # re-raises
1228 except: # re-raises
1229 done.put(('!', test, 'run-test raised an error, see traceback'))
1229 done.put(('!', test, 'run-test raised an error, see traceback'))
1230 raise
1230 raise
1231
1231
1232 try:
1232 try:
1233 while tests or running:
1233 while tests or running:
1234 if not done.empty() or running == jobs or not tests:
1234 if not done.empty() or running == jobs or not tests:
1235 try:
1235 try:
1236 code, test, msg = done.get(True, 1)
1236 code, test, msg = done.get(True, 1)
1237 results[code].append((test, msg))
1237 results[code].append((test, msg))
1238 if options.first and code not in '.si':
1238 if options.first and code not in '.si':
1239 break
1239 break
1240 except queue.Empty:
1240 except queue.Empty:
1241 continue
1241 continue
1242 running -= 1
1242 running -= 1
1243 if tests and not running == jobs:
1243 if tests and not running == jobs:
1244 test = tests.pop(0)
1244 test = tests.pop(0)
1245 if options.loop:
1245 if options.loop:
1246 tests.append(test)
1246 tests.append(test)
1247 t = threading.Thread(target=job, name=test, args=(test, count))
1247 t = threading.Thread(target=job, name=test, args=(test, count))
1248 t.start()
1248 t.start()
1249 running += 1
1249 running += 1
1250 count += 1
1250 count += 1
1251 except KeyboardInterrupt:
1251 except KeyboardInterrupt:
1252 abort = True
1252 abort = True
1253
1253
1254 def runtests(options, tests):
1254 def runtests(options, tests):
1255 try:
1255 try:
1256 if INST:
1256 if INST:
1257 installhg(options)
1257 installhg(options)
1258 _checkhglib("Testing")
1258 _checkhglib("Testing")
1259 else:
1259 else:
1260 usecorrectpython()
1260 usecorrectpython()
1261
1261
1262 if options.restart:
1262 if options.restart:
1263 orig = list(tests)
1263 orig = list(tests)
1264 while tests:
1264 while tests:
1265 if os.path.exists(tests[0] + ".err"):
1265 if os.path.exists(tests[0] + ".err"):
1266 break
1266 break
1267 tests.pop(0)
1267 tests.pop(0)
1268 if not tests:
1268 if not tests:
1269 print "running all tests"
1269 print "running all tests"
1270 tests = orig
1270 tests = orig
1271
1271
1272 scheduletests(options, tests)
1272 scheduletests(options, tests)
1273
1273
1274 failed = len(results['!'])
1274 failed = len(results['!'])
1275 warned = len(results['~'])
1275 warned = len(results['~'])
1276 tested = len(results['.']) + failed + warned
1276 tested = len(results['.']) + failed + warned
1277 skipped = len(results['s'])
1277 skipped = len(results['s'])
1278 ignored = len(results['i'])
1278 ignored = len(results['i'])
1279
1279
1280 print
1280 print
1281 if not options.noskips:
1281 if not options.noskips:
1282 for s in results['s']:
1282 for s in results['s']:
1283 print "Skipped %s: %s" % s
1283 print "Skipped %s: %s" % s
1284 for s in results['~']:
1284 for s in results['~']:
1285 print "Warned %s: %s" % s
1285 print "Warned %s: %s" % s
1286 for s in results['!']:
1286 for s in results['!']:
1287 print "Failed %s: %s" % s
1287 print "Failed %s: %s" % s
1288 _checkhglib("Tested")
1288 _checkhglib("Tested")
1289 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1289 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1290 tested, skipped + ignored, warned, failed)
1290 tested, skipped + ignored, warned, failed)
1291 if results['!']:
1291 if results['!']:
1292 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1292 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1293 if options.time:
1293 if options.time:
1294 outputtimes(options)
1294 outputtimes(options)
1295
1295
1296 if options.anycoverage:
1296 if options.anycoverage:
1297 outputcoverage(options)
1297 outputcoverage(options)
1298 except KeyboardInterrupt:
1298 except KeyboardInterrupt:
1299 failed = True
1299 failed = True
1300 print "\ninterrupted!"
1300 print "\ninterrupted!"
1301
1301
1302 if failed:
1302 if failed:
1303 return 1
1303 return 1
1304 if warned:
1304 if warned:
1305 return 80
1305 return 80
1306
1306
1307 testtypes = [('.py', PythonTest, '.out'),
1307 testtypes = [('.py', PythonTest, '.out'),
1308 ('.t', TTest, '')]
1308 ('.t', TTest, '')]
1309
1309
1310 def main(args, parser=None):
1310 def main(args, parser=None):
1311 parser = parser or getparser()
1311 parser = parser or getparser()
1312 (options, args) = parseargs(args, parser)
1312 (options, args) = parseargs(args, parser)
1313 os.umask(022)
1313 os.umask(022)
1314
1314
1315 checktools()
1315 checktools()
1316
1316
1317 if not args:
1317 if not args:
1318 if options.changed:
1318 if options.changed:
1319 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1319 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1320 None, 0)
1320 None, 0)
1321 stdout, stderr = proc.communicate()
1321 stdout, stderr = proc.communicate()
1322 args = stdout.strip('\0').split('\0')
1322 args = stdout.strip('\0').split('\0')
1323 else:
1323 else:
1324 args = os.listdir(".")
1324 args = os.listdir(".")
1325
1325
1326 tests = [t for t in args
1326 tests = [t for t in args
1327 if os.path.basename(t).startswith("test-")
1327 if os.path.basename(t).startswith("test-")
1328 and (t.endswith(".py") or t.endswith(".t"))]
1328 and (t.endswith(".py") or t.endswith(".t"))]
1329
1329
1330 if options.random:
1330 if options.random:
1331 random.shuffle(tests)
1331 random.shuffle(tests)
1332 else:
1332 else:
1333 # keywords for slow tests
1333 # keywords for slow tests
1334 slow = 'svn gendoc check-code-hg'.split()
1334 slow = 'svn gendoc check-code-hg'.split()
1335 def sortkey(f):
1335 def sortkey(f):
1336 # run largest tests first, as they tend to take the longest
1336 # run largest tests first, as they tend to take the longest
1337 try:
1337 try:
1338 val = -os.stat(f).st_size
1338 val = -os.stat(f).st_size
1339 except OSError, e:
1339 except OSError, e:
1340 if e.errno != errno.ENOENT:
1340 if e.errno != errno.ENOENT:
1341 raise
1341 raise
1342 return -1e9 # file does not exist, tell early
1342 return -1e9 # file does not exist, tell early
1343 for kw in slow:
1343 for kw in slow:
1344 if kw in f:
1344 if kw in f:
1345 val *= 10
1345 val *= 10
1346 return val
1346 return val
1347 tests.sort(key=sortkey)
1347 tests.sort(key=sortkey)
1348
1348
1349 if 'PYTHONHASHSEED' not in os.environ:
1349 if 'PYTHONHASHSEED' not in os.environ:
1350 # use a random python hash seed all the time
1350 # use a random python hash seed all the time
1351 # we do the randomness ourself to know what seed is used
1351 # we do the randomness ourself to know what seed is used
1352 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1352 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1353
1353
1354 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1354 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1355 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1355 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1356 if options.tmpdir:
1356 if options.tmpdir:
1357 options.keep_tmpdir = True
1357 options.keep_tmpdir = True
1358 tmpdir = options.tmpdir
1358 tmpdir = options.tmpdir
1359 if os.path.exists(tmpdir):
1359 if os.path.exists(tmpdir):
1360 # Meaning of tmpdir has changed since 1.3: we used to create
1360 # Meaning of tmpdir has changed since 1.3: we used to create
1361 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1361 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1362 # tmpdir already exists.
1362 # tmpdir already exists.
1363 print "error: temp dir %r already exists" % tmpdir
1363 print "error: temp dir %r already exists" % tmpdir
1364 return 1
1364 return 1
1365
1365
1366 # Automatically removing tmpdir sounds convenient, but could
1366 # Automatically removing tmpdir sounds convenient, but could
1367 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1367 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1368 # or "--tmpdir=$HOME".
1368 # or "--tmpdir=$HOME".
1369 #vlog("# Removing temp dir", tmpdir)
1369 #vlog("# Removing temp dir", tmpdir)
1370 #shutil.rmtree(tmpdir)
1370 #shutil.rmtree(tmpdir)
1371 os.makedirs(tmpdir)
1371 os.makedirs(tmpdir)
1372 else:
1372 else:
1373 d = None
1373 d = None
1374 if os.name == 'nt':
1374 if os.name == 'nt':
1375 # without this, we get the default temp dir location, but
1375 # without this, we get the default temp dir location, but
1376 # in all lowercase, which causes troubles with paths (issue3490)
1376 # in all lowercase, which causes troubles with paths (issue3490)
1377 d = os.getenv('TMP')
1377 d = os.getenv('TMP')
1378 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1378 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1379 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1379 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1380
1380
1381 if options.with_hg:
1381 if options.with_hg:
1382 INST = None
1382 INST = None
1383 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1383 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1384 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1384 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1385 os.makedirs(TMPBINDIR)
1385 os.makedirs(TMPBINDIR)
1386
1386
1387 # This looks redundant with how Python initializes sys.path from
1387 # This looks redundant with how Python initializes sys.path from
1388 # the location of the script being executed. Needed because the
1388 # the location of the script being executed. Needed because the
1389 # "hg" specified by --with-hg is not the only Python script
1389 # "hg" specified by --with-hg is not the only Python script
1390 # executed in the test suite that needs to import 'mercurial'
1390 # executed in the test suite that needs to import 'mercurial'
1391 # ... which means it's not really redundant at all.
1391 # ... which means it's not really redundant at all.
1392 PYTHONDIR = BINDIR
1392 PYTHONDIR = BINDIR
1393 else:
1393 else:
1394 INST = os.path.join(HGTMP, "install")
1394 INST = os.path.join(HGTMP, "install")
1395 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1395 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1396 TMPBINDIR = BINDIR
1396 TMPBINDIR = BINDIR
1397 PYTHONDIR = os.path.join(INST, "lib", "python")
1397 PYTHONDIR = os.path.join(INST, "lib", "python")
1398
1398
1399 os.environ["BINDIR"] = BINDIR
1399 os.environ["BINDIR"] = BINDIR
1400 os.environ["PYTHON"] = PYTHON
1400 os.environ["PYTHON"] = PYTHON
1401
1401
1402 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1402 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1403 if TMPBINDIR != BINDIR:
1403 if TMPBINDIR != BINDIR:
1404 path = [TMPBINDIR] + path
1404 path = [TMPBINDIR] + path
1405 os.environ["PATH"] = os.pathsep.join(path)
1405 os.environ["PATH"] = os.pathsep.join(path)
1406
1406
1407 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1407 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1408 # can run .../tests/run-tests.py test-foo where test-foo
1408 # can run .../tests/run-tests.py test-foo where test-foo
1409 # adds an extension to HGRC. Also include run-test.py directory to import
1409 # adds an extension to HGRC. Also include run-test.py directory to import
1410 # modules like heredoctest.
1410 # modules like heredoctest.
1411 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1411 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1412 # We have to augment PYTHONPATH, rather than simply replacing
1412 # We have to augment PYTHONPATH, rather than simply replacing
1413 # it, in case external libraries are only available via current
1413 # it, in case external libraries are only available via current
1414 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1414 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1415 # are in /opt/subversion.)
1415 # are in /opt/subversion.)
1416 oldpypath = os.environ.get(IMPL_PATH)
1416 oldpypath = os.environ.get(IMPL_PATH)
1417 if oldpypath:
1417 if oldpypath:
1418 pypath.append(oldpypath)
1418 pypath.append(oldpypath)
1419 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1419 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1420
1420
1421 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1421 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1422
1422
1423 vlog("# Using TESTDIR", TESTDIR)
1423 vlog("# Using TESTDIR", TESTDIR)
1424 vlog("# Using HGTMP", HGTMP)
1424 vlog("# Using HGTMP", HGTMP)
1425 vlog("# Using PATH", os.environ["PATH"])
1425 vlog("# Using PATH", os.environ["PATH"])
1426 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1426 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1427
1427
1428 try:
1428 try:
1429 return runtests(options, tests) or 0
1429 return runtests(options, tests) or 0
1430 finally:
1430 finally:
1431 time.sleep(.1)
1431 time.sleep(.1)
1432 cleanup(options)
1432 cleanup(options)
1433
1433
1434 if __name__ == '__main__':
1434 if __name__ == '__main__':
1435 sys.exit(main(sys.argv[1:]))
1435 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now