##// END OF EJS Templates
run-tests: move test name filter to Test.run()
Gregory Szorc -
r21332:60ce874f default
parent child Browse files
Show More
@@ -1,1434 +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):
595 if options.retest and not os.path.exists('%s.err' % self._test):
596 return self.ignore('not retesting')
596 return self.ignore('not retesting')
597
597
598 if options.keywords:
598 if options.keywords:
599 f = open(self._test)
599 f = open(self._test)
600 t = f.read().lower() + self._test.lower()
600 t = f.read().lower() + self._test.lower()
601 f.close()
601 f.close()
602 for k in options.keywords.lower().split():
602 for k in options.keywords.lower().split():
603 if k in t:
603 if k in t:
604 break
604 break
605 else:
605 else:
606 return self.ignore("doesn't match keyword")
606 return self.ignore("doesn't match keyword")
607
607
608 if not os.path.basename(self._test.lower()).startswith('test-'):
609 return self.skip('not a test file')
610
608 # Remove any previous output files.
611 # Remove any previous output files.
609 if os.path.exists(self._errpath):
612 if os.path.exists(self._errpath):
610 os.remove(self._errpath)
613 os.remove(self._errpath)
611
614
612 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
615 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
613 os.mkdir(testtmp)
616 os.mkdir(testtmp)
614 replacements, port = self._getreplacements(testtmp)
617 replacements, port = self._getreplacements(testtmp)
615 env = self._getenv(testtmp, port)
618 env = self._getenv(testtmp, port)
616 self._daemonpids.append(env['DAEMON_PIDS'])
619 self._daemonpids.append(env['DAEMON_PIDS'])
617 createhgrc(env['HGRCPATH'], options)
620 createhgrc(env['HGRCPATH'], options)
618
621
619 starttime = time.time()
622 starttime = time.time()
620
623
621 def updateduration():
624 def updateduration():
622 result.duration = time.time() - starttime
625 result.duration = time.time() - starttime
623
626
624 try:
627 try:
625 ret, out = self._run(testtmp, replacements, env)
628 ret, out = self._run(testtmp, replacements, env)
626 updateduration()
629 updateduration()
627 result.ret = ret
630 result.ret = ret
628 result.out = out
631 result.out = out
629 except KeyboardInterrupt:
632 except KeyboardInterrupt:
630 updateduration()
633 updateduration()
631 log('INTERRUPTED: %s (after %d seconds)' % (self._test,
634 log('INTERRUPTED: %s (after %d seconds)' % (self._test,
632 result.duration))
635 result.duration))
633 raise
636 raise
634 except Exception, e:
637 except Exception, e:
635 updateduration()
638 updateduration()
636 result.exception = e
639 result.exception = e
637
640
638 killdaemons(env['DAEMON_PIDS'])
641 killdaemons(env['DAEMON_PIDS'])
639
642
640 result.refout = self._refout
643 result.refout = self._refout
641
644
642 if not options.keep_tmpdir:
645 if not options.keep_tmpdir:
643 shutil.rmtree(testtmp)
646 shutil.rmtree(testtmp)
644
647
645 def describe(ret):
648 def describe(ret):
646 if ret < 0:
649 if ret < 0:
647 return 'killed by signal: %d' % -ret
650 return 'killed by signal: %d' % -ret
648 return 'returned error code %d' % ret
651 return 'returned error code %d' % ret
649
652
650 if ret == SKIPPED_STATUS:
653 if ret == SKIPPED_STATUS:
651 if out is None: # Debug mode, nothing to parse.
654 if out is None: # Debug mode, nothing to parse.
652 missing = ['unknown']
655 missing = ['unknown']
653 failed = None
656 failed = None
654 else:
657 else:
655 missing, failed = parsehghaveoutput(out)
658 missing, failed = parsehghaveoutput(out)
656
659
657 if not missing:
660 if not missing:
658 missing = ['irrelevant']
661 missing = ['irrelevant']
659
662
660 if failed:
663 if failed:
661 return self.fail('hg have failed checking for %s' % failed[-1],
664 return self.fail('hg have failed checking for %s' % failed[-1],
662 ret)
665 ret)
663 else:
666 else:
664 result.skipped = True
667 result.skipped = True
665 return self.skip(missing[-1])
668 return self.skip(missing[-1])
666 elif ret == 'timeout':
669 elif ret == 'timeout':
667 return self.fail('timed out', ret)
670 return self.fail('timed out', ret)
668 elif out != self._refout:
671 elif out != self._refout:
669 info = {}
672 info = {}
670 if not options.nodiff:
673 if not options.nodiff:
671 iolock.acquire()
674 iolock.acquire()
672 if options.view:
675 if options.view:
673 os.system("%s %s %s" % (options.view, self._refpath,
676 os.system("%s %s %s" % (options.view, self._refpath,
674 self._errpath))
677 self._errpath))
675 else:
678 else:
676 info = showdiff(self._refout, out, self._refpath,
679 info = showdiff(self._refout, out, self._refpath,
677 self._errpath)
680 self._errpath)
678 iolock.release()
681 iolock.release()
679 msg = ''
682 msg = ''
680 if info.get('servefail'):
683 if info.get('servefail'):
681 msg += 'serve failed and '
684 msg += 'serve failed and '
682 if ret:
685 if ret:
683 msg += 'output changed and ' + describe(ret)
686 msg += 'output changed and ' + describe(ret)
684 else:
687 else:
685 msg += 'output changed'
688 msg += 'output changed'
686
689
687 return self.fail(msg, ret)
690 return self.fail(msg, ret)
688 elif ret:
691 elif ret:
689 return self.fail(describe(ret), ret)
692 return self.fail(describe(ret), ret)
690 else:
693 else:
691 return self.success()
694 return self.success()
692
695
693 def _run(self, testtmp, replacements, env):
696 def _run(self, testtmp, replacements, env):
694 raise NotImplemented('Subclasses must implement Test.run()')
697 raise NotImplemented('Subclasses must implement Test.run()')
695
698
696 def _getreplacements(self, testtmp):
699 def _getreplacements(self, testtmp):
697 port = self._options.port + self._count * 3
700 port = self._options.port + self._count * 3
698 r = [
701 r = [
699 (r':%s\b' % port, ':$HGPORT'),
702 (r':%s\b' % port, ':$HGPORT'),
700 (r':%s\b' % (port + 1), ':$HGPORT1'),
703 (r':%s\b' % (port + 1), ':$HGPORT1'),
701 (r':%s\b' % (port + 2), ':$HGPORT2'),
704 (r':%s\b' % (port + 2), ':$HGPORT2'),
702 ]
705 ]
703
706
704 if os.name == 'nt':
707 if os.name == 'nt':
705 r.append(
708 r.append(
706 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
709 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
707 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
710 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
708 for c in testtmp), '$TESTTMP'))
711 for c in testtmp), '$TESTTMP'))
709 else:
712 else:
710 r.append((re.escape(testtmp), '$TESTTMP'))
713 r.append((re.escape(testtmp), '$TESTTMP'))
711
714
712 return r, port
715 return r, port
713
716
714 def _getenv(self, testtmp, port):
717 def _getenv(self, testtmp, port):
715 env = os.environ.copy()
718 env = os.environ.copy()
716 env['TESTTMP'] = testtmp
719 env['TESTTMP'] = testtmp
717 env['HOME'] = testtmp
720 env['HOME'] = testtmp
718 env["HGPORT"] = str(port)
721 env["HGPORT"] = str(port)
719 env["HGPORT1"] = str(port + 1)
722 env["HGPORT1"] = str(port + 1)
720 env["HGPORT2"] = str(port + 2)
723 env["HGPORT2"] = str(port + 2)
721 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
724 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
722 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
725 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
723 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
726 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
724 env["HGMERGE"] = "internal:merge"
727 env["HGMERGE"] = "internal:merge"
725 env["HGUSER"] = "test"
728 env["HGUSER"] = "test"
726 env["HGENCODING"] = "ascii"
729 env["HGENCODING"] = "ascii"
727 env["HGENCODINGMODE"] = "strict"
730 env["HGENCODINGMODE"] = "strict"
728
731
729 # Reset some environment variables to well-known values so that
732 # Reset some environment variables to well-known values so that
730 # the tests produce repeatable output.
733 # the tests produce repeatable output.
731 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
734 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
732 env['TZ'] = 'GMT'
735 env['TZ'] = 'GMT'
733 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
736 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
734 env['COLUMNS'] = '80'
737 env['COLUMNS'] = '80'
735 env['TERM'] = 'xterm'
738 env['TERM'] = 'xterm'
736
739
737 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
740 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
738 'NO_PROXY').split():
741 'NO_PROXY').split():
739 if k in env:
742 if k in env:
740 del env[k]
743 del env[k]
741
744
742 # unset env related to hooks
745 # unset env related to hooks
743 for k in env.keys():
746 for k in env.keys():
744 if k.startswith('HG_'):
747 if k.startswith('HG_'):
745 del env[k]
748 del env[k]
746
749
747 return env
750 return env
748
751
749 def success(self):
752 def success(self):
750 return '.', self._test, ''
753 return '.', self._test, ''
751
754
752 def fail(self, msg, ret):
755 def fail(self, msg, ret):
753 warned = ret is False
756 warned = ret is False
754 if not self._options.nodiff:
757 if not self._options.nodiff:
755 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
758 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
756 msg))
759 msg))
757 if (not ret and self._options.interactive and
760 if (not ret and self._options.interactive and
758 os.path.exists(self._errpath)):
761 os.path.exists(self._errpath)):
759 iolock.acquire()
762 iolock.acquire()
760 print 'Accept this change? [n] ',
763 print 'Accept this change? [n] ',
761 answer = sys.stdin.readline().strip()
764 answer = sys.stdin.readline().strip()
762 iolock.release()
765 iolock.release()
763 if answer.lower() in ('y', 'yes').split():
766 if answer.lower() in ('y', 'yes').split():
764 if self._test.endswith('.t'):
767 if self._test.endswith('.t'):
765 rename(self._errpath, self._testpath)
768 rename(self._errpath, self._testpath)
766 else:
769 else:
767 rename(self._errpath, '%s.out' % self._testpath)
770 rename(self._errpath, '%s.out' % self._testpath)
768
771
769 return '.', self._test, ''
772 return '.', self._test, ''
770
773
771 return warned and '~' or '!', self._test, msg
774 return warned and '~' or '!', self._test, msg
772
775
773 def skip(self, msg):
776 def skip(self, msg):
774 if self._options.verbose:
777 if self._options.verbose:
775 log("\nSkipping %s: %s" % (self._path, msg))
778 log("\nSkipping %s: %s" % (self._path, msg))
776
779
777 return 's', self._test, msg
780 return 's', self._test, msg
778
781
779 def ignore(self, msg):
782 def ignore(self, msg):
780 return 'i', self._test, msg
783 return 'i', self._test, msg
781
784
782 class TestResult(object):
785 class TestResult(object):
783 """Holds the result of a test execution."""
786 """Holds the result of a test execution."""
784
787
785 def __init__(self):
788 def __init__(self):
786 self.ret = None
789 self.ret = None
787 self.out = None
790 self.out = None
788 self.duration = None
791 self.duration = None
789 self.exception = None
792 self.exception = None
790 self.refout = None
793 self.refout = None
791 self.skipped = False
794 self.skipped = False
792
795
793 class PythonTest(Test):
796 class PythonTest(Test):
794 """A Python-based test."""
797 """A Python-based test."""
795 def _run(self, testtmp, replacements, env):
798 def _run(self, testtmp, replacements, env):
796 py3kswitch = self._options.py3k_warnings and ' -3' or ''
799 py3kswitch = self._options.py3k_warnings and ' -3' or ''
797 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
800 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
798 vlog("# Running", cmd)
801 vlog("# Running", cmd)
799 if os.name == 'nt':
802 if os.name == 'nt':
800 replacements.append((r'\r\n', '\n'))
803 replacements.append((r'\r\n', '\n'))
801 return run(cmd, testtmp, self._options, replacements, env)
804 return run(cmd, testtmp, self._options, replacements, env)
802
805
803
806
804 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
807 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
805 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
808 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
806 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
809 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
807 escapemap.update({'\\': '\\\\', '\r': r'\r'})
810 escapemap.update({'\\': '\\\\', '\r': r'\r'})
808 def escapef(m):
811 def escapef(m):
809 return escapemap[m.group(0)]
812 return escapemap[m.group(0)]
810 def stringescape(s):
813 def stringescape(s):
811 return escapesub(escapef, s)
814 return escapesub(escapef, s)
812
815
813 class TTest(Test):
816 class TTest(Test):
814 """A "t test" is a test backed by a .t file."""
817 """A "t test" is a test backed by a .t file."""
815
818
816 def _run(self, testtmp, replacements, env):
819 def _run(self, testtmp, replacements, env):
817 f = open(self._path)
820 f = open(self._path)
818 lines = f.readlines()
821 lines = f.readlines()
819 f.close()
822 f.close()
820
823
821 salt, script, after, expected = self._parsetest(lines, testtmp)
824 salt, script, after, expected = self._parsetest(lines, testtmp)
822
825
823 # Write out the generated script.
826 # Write out the generated script.
824 fname = '%s.sh' % testtmp
827 fname = '%s.sh' % testtmp
825 f = open(fname, 'w')
828 f = open(fname, 'w')
826 for l in script:
829 for l in script:
827 f.write(l)
830 f.write(l)
828 f.close()
831 f.close()
829
832
830 cmd = '%s "%s"' % (self._options.shell, fname)
833 cmd = '%s "%s"' % (self._options.shell, fname)
831 vlog("# Running", cmd)
834 vlog("# Running", cmd)
832
835
833 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
836 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
834 # Do not merge output if skipped. Return hghave message instead.
837 # Do not merge output if skipped. Return hghave message instead.
835 # Similarly, with --debug, output is None.
838 # Similarly, with --debug, output is None.
836 if exitcode == SKIPPED_STATUS or output is None:
839 if exitcode == SKIPPED_STATUS or output is None:
837 return exitcode, output
840 return exitcode, output
838
841
839 return self._processoutput(exitcode, output, salt, after, expected)
842 return self._processoutput(exitcode, output, salt, after, expected)
840
843
841 def _hghave(self, reqs, testtmp):
844 def _hghave(self, reqs, testtmp):
842 # TODO do something smarter when all other uses of hghave are gone.
845 # TODO do something smarter when all other uses of hghave are gone.
843 tdir = TESTDIR.replace('\\', '/')
846 tdir = TESTDIR.replace('\\', '/')
844 proc = Popen4('%s -c "%s/hghave %s"' %
847 proc = Popen4('%s -c "%s/hghave %s"' %
845 (self._options.shell, tdir, ' '.join(reqs)),
848 (self._options.shell, tdir, ' '.join(reqs)),
846 testtmp, 0)
849 testtmp, 0)
847 stdout, stderr = proc.communicate()
850 stdout, stderr = proc.communicate()
848 ret = proc.wait()
851 ret = proc.wait()
849 if wifexited(ret):
852 if wifexited(ret):
850 ret = os.WEXITSTATUS(ret)
853 ret = os.WEXITSTATUS(ret)
851 if ret == 2:
854 if ret == 2:
852 print stdout
855 print stdout
853 sys.exit(1)
856 sys.exit(1)
854
857
855 return ret == 0
858 return ret == 0
856
859
857 def _parsetest(self, lines, testtmp):
860 def _parsetest(self, lines, testtmp):
858 # We generate a shell script which outputs unique markers to line
861 # We generate a shell script which outputs unique markers to line
859 # up script results with our source. These markers include input
862 # up script results with our source. These markers include input
860 # line number and the last return code.
863 # line number and the last return code.
861 salt = "SALT" + str(time.time())
864 salt = "SALT" + str(time.time())
862 def addsalt(line, inpython):
865 def addsalt(line, inpython):
863 if inpython:
866 if inpython:
864 script.append('%s %d 0\n' % (salt, line))
867 script.append('%s %d 0\n' % (salt, line))
865 else:
868 else:
866 script.append('echo %s %s $?\n' % (salt, line))
869 script.append('echo %s %s $?\n' % (salt, line))
867
870
868 script = []
871 script = []
869
872
870 # After we run the shell script, we re-unify the script output
873 # After we run the shell script, we re-unify the script output
871 # with non-active parts of the source, with synchronization by our
874 # with non-active parts of the source, with synchronization by our
872 # SALT line number markers. The after table contains the non-active
875 # SALT line number markers. The after table contains the non-active
873 # components, ordered by line number.
876 # components, ordered by line number.
874 after = {}
877 after = {}
875
878
876 # Expected shell script output.
879 # Expected shell script output.
877 expected = {}
880 expected = {}
878
881
879 pos = prepos = -1
882 pos = prepos = -1
880
883
881 # True or False when in a true or false conditional section
884 # True or False when in a true or false conditional section
882 skipping = None
885 skipping = None
883
886
884 # We keep track of whether or not we're in a Python block so we
887 # We keep track of whether or not we're in a Python block so we
885 # can generate the surrounding doctest magic.
888 # can generate the surrounding doctest magic.
886 inpython = False
889 inpython = False
887
890
888 if self._options.debug:
891 if self._options.debug:
889 script.append('set -x\n')
892 script.append('set -x\n')
890 if os.getenv('MSYSTEM'):
893 if os.getenv('MSYSTEM'):
891 script.append('alias pwd="pwd -W"\n')
894 script.append('alias pwd="pwd -W"\n')
892
895
893 for n, l in enumerate(lines):
896 for n, l in enumerate(lines):
894 if not l.endswith('\n'):
897 if not l.endswith('\n'):
895 l += '\n'
898 l += '\n'
896 if l.startswith('#if'):
899 if l.startswith('#if'):
897 lsplit = l.split()
900 lsplit = l.split()
898 if len(lsplit) < 2 or lsplit[0] != '#if':
901 if len(lsplit) < 2 or lsplit[0] != '#if':
899 after.setdefault(pos, []).append(' !!! invalid #if\n')
902 after.setdefault(pos, []).append(' !!! invalid #if\n')
900 if skipping is not None:
903 if skipping is not None:
901 after.setdefault(pos, []).append(' !!! nested #if\n')
904 after.setdefault(pos, []).append(' !!! nested #if\n')
902 skipping = not self._hghave(lsplit[1:], testtmp)
905 skipping = not self._hghave(lsplit[1:], testtmp)
903 after.setdefault(pos, []).append(l)
906 after.setdefault(pos, []).append(l)
904 elif l.startswith('#else'):
907 elif l.startswith('#else'):
905 if skipping is None:
908 if skipping is None:
906 after.setdefault(pos, []).append(' !!! missing #if\n')
909 after.setdefault(pos, []).append(' !!! missing #if\n')
907 skipping = not skipping
910 skipping = not skipping
908 after.setdefault(pos, []).append(l)
911 after.setdefault(pos, []).append(l)
909 elif l.startswith('#endif'):
912 elif l.startswith('#endif'):
910 if skipping is None:
913 if skipping is None:
911 after.setdefault(pos, []).append(' !!! missing #if\n')
914 after.setdefault(pos, []).append(' !!! missing #if\n')
912 skipping = None
915 skipping = None
913 after.setdefault(pos, []).append(l)
916 after.setdefault(pos, []).append(l)
914 elif skipping:
917 elif skipping:
915 after.setdefault(pos, []).append(l)
918 after.setdefault(pos, []).append(l)
916 elif l.startswith(' >>> '): # python inlines
919 elif l.startswith(' >>> '): # python inlines
917 after.setdefault(pos, []).append(l)
920 after.setdefault(pos, []).append(l)
918 prepos = pos
921 prepos = pos
919 pos = n
922 pos = n
920 if not inpython:
923 if not inpython:
921 # We've just entered a Python block. Add the header.
924 # We've just entered a Python block. Add the header.
922 inpython = True
925 inpython = True
923 addsalt(prepos, False) # Make sure we report the exit code.
926 addsalt(prepos, False) # Make sure we report the exit code.
924 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
927 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
925 addsalt(n, True)
928 addsalt(n, True)
926 script.append(l[2:])
929 script.append(l[2:])
927 elif l.startswith(' ... '): # python inlines
930 elif l.startswith(' ... '): # python inlines
928 after.setdefault(prepos, []).append(l)
931 after.setdefault(prepos, []).append(l)
929 script.append(l[2:])
932 script.append(l[2:])
930 elif l.startswith(' $ '): # commands
933 elif l.startswith(' $ '): # commands
931 if inpython:
934 if inpython:
932 script.append('EOF\n')
935 script.append('EOF\n')
933 inpython = False
936 inpython = False
934 after.setdefault(pos, []).append(l)
937 after.setdefault(pos, []).append(l)
935 prepos = pos
938 prepos = pos
936 pos = n
939 pos = n
937 addsalt(n, False)
940 addsalt(n, False)
938 cmd = l[4:].split()
941 cmd = l[4:].split()
939 if len(cmd) == 2 and cmd[0] == 'cd':
942 if len(cmd) == 2 and cmd[0] == 'cd':
940 l = ' $ cd %s || exit 1\n' % cmd[1]
943 l = ' $ cd %s || exit 1\n' % cmd[1]
941 script.append(l[4:])
944 script.append(l[4:])
942 elif l.startswith(' > '): # continuations
945 elif l.startswith(' > '): # continuations
943 after.setdefault(prepos, []).append(l)
946 after.setdefault(prepos, []).append(l)
944 script.append(l[4:])
947 script.append(l[4:])
945 elif l.startswith(' '): # results
948 elif l.startswith(' '): # results
946 # Queue up a list of expected results.
949 # Queue up a list of expected results.
947 expected.setdefault(pos, []).append(l[2:])
950 expected.setdefault(pos, []).append(l[2:])
948 else:
951 else:
949 if inpython:
952 if inpython:
950 script.append('EOF\n')
953 script.append('EOF\n')
951 inpython = False
954 inpython = False
952 # Non-command/result. Queue up for merged output.
955 # Non-command/result. Queue up for merged output.
953 after.setdefault(pos, []).append(l)
956 after.setdefault(pos, []).append(l)
954
957
955 if inpython:
958 if inpython:
956 script.append('EOF\n')
959 script.append('EOF\n')
957 if skipping is not None:
960 if skipping is not None:
958 after.setdefault(pos, []).append(' !!! missing #endif\n')
961 after.setdefault(pos, []).append(' !!! missing #endif\n')
959 addsalt(n + 1, False)
962 addsalt(n + 1, False)
960
963
961 return salt, script, after, expected
964 return salt, script, after, expected
962
965
963 def _processoutput(self, exitcode, output, salt, after, expected):
966 def _processoutput(self, exitcode, output, salt, after, expected):
964 # Merge the script output back into a unified test.
967 # Merge the script output back into a unified test.
965 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
968 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
966 if exitcode != 0:
969 if exitcode != 0:
967 warnonly = 3
970 warnonly = 3
968
971
969 pos = -1
972 pos = -1
970 postout = []
973 postout = []
971 for l in output:
974 for l in output:
972 lout, lcmd = l, None
975 lout, lcmd = l, None
973 if salt in l:
976 if salt in l:
974 lout, lcmd = l.split(salt, 1)
977 lout, lcmd = l.split(salt, 1)
975
978
976 if lout:
979 if lout:
977 if not lout.endswith('\n'):
980 if not lout.endswith('\n'):
978 lout += ' (no-eol)\n'
981 lout += ' (no-eol)\n'
979
982
980 # Find the expected output at the current position.
983 # Find the expected output at the current position.
981 el = None
984 el = None
982 if expected.get(pos, None):
985 if expected.get(pos, None):
983 el = expected[pos].pop(0)
986 el = expected[pos].pop(0)
984
987
985 r = TTest.linematch(el, lout)
988 r = TTest.linematch(el, lout)
986 if isinstance(r, str):
989 if isinstance(r, str):
987 if r == '+glob':
990 if r == '+glob':
988 lout = el[:-1] + ' (glob)\n'
991 lout = el[:-1] + ' (glob)\n'
989 r = '' # Warn only this line.
992 r = '' # Warn only this line.
990 elif r == '-glob':
993 elif r == '-glob':
991 lout = ''.join(el.rsplit(' (glob)', 1))
994 lout = ''.join(el.rsplit(' (glob)', 1))
992 r = '' # Warn only this line.
995 r = '' # Warn only this line.
993 else:
996 else:
994 log('\ninfo, unknown linematch result: %r\n' % r)
997 log('\ninfo, unknown linematch result: %r\n' % r)
995 r = False
998 r = False
996 if r:
999 if r:
997 postout.append(' ' + el)
1000 postout.append(' ' + el)
998 else:
1001 else:
999 if needescape(lout):
1002 if needescape(lout):
1000 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
1003 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
1001 postout.append(' ' + lout) # Let diff deal with it.
1004 postout.append(' ' + lout) # Let diff deal with it.
1002 if r != '': # If line failed.
1005 if r != '': # If line failed.
1003 warnonly = 3 # for sure not
1006 warnonly = 3 # for sure not
1004 elif warnonly == 1: # Is "not yet" and line is warn only.
1007 elif warnonly == 1: # Is "not yet" and line is warn only.
1005 warnonly = 2 # Yes do warn.
1008 warnonly = 2 # Yes do warn.
1006
1009
1007 if lcmd:
1010 if lcmd:
1008 # Add on last return code.
1011 # Add on last return code.
1009 ret = int(lcmd.split()[1])
1012 ret = int(lcmd.split()[1])
1010 if ret != 0:
1013 if ret != 0:
1011 postout.append(' [%s]\n' % ret)
1014 postout.append(' [%s]\n' % ret)
1012 if pos in after:
1015 if pos in after:
1013 # Merge in non-active test bits.
1016 # Merge in non-active test bits.
1014 postout += after.pop(pos)
1017 postout += after.pop(pos)
1015 pos = int(lcmd.split()[0])
1018 pos = int(lcmd.split()[0])
1016
1019
1017 if pos in after:
1020 if pos in after:
1018 postout += after.pop(pos)
1021 postout += after.pop(pos)
1019
1022
1020 if warnonly == 2:
1023 if warnonly == 2:
1021 exitcode = False # Set exitcode to warned.
1024 exitcode = False # Set exitcode to warned.
1022
1025
1023 return exitcode, postout
1026 return exitcode, postout
1024
1027
1025 @staticmethod
1028 @staticmethod
1026 def rematch(el, l):
1029 def rematch(el, l):
1027 try:
1030 try:
1028 # use \Z to ensure that the regex matches to the end of the string
1031 # use \Z to ensure that the regex matches to the end of the string
1029 if os.name == 'nt':
1032 if os.name == 'nt':
1030 return re.match(el + r'\r?\n\Z', l)
1033 return re.match(el + r'\r?\n\Z', l)
1031 return re.match(el + r'\n\Z', l)
1034 return re.match(el + r'\n\Z', l)
1032 except re.error:
1035 except re.error:
1033 # el is an invalid regex
1036 # el is an invalid regex
1034 return False
1037 return False
1035
1038
1036 @staticmethod
1039 @staticmethod
1037 def globmatch(el, l):
1040 def globmatch(el, l):
1038 # The only supported special characters are * and ? plus / which also
1041 # The only supported special characters are * and ? plus / which also
1039 # matches \ on windows. Escaping of these characters is supported.
1042 # matches \ on windows. Escaping of these characters is supported.
1040 if el + '\n' == l:
1043 if el + '\n' == l:
1041 if os.altsep:
1044 if os.altsep:
1042 # matching on "/" is not needed for this line
1045 # matching on "/" is not needed for this line
1043 return '-glob'
1046 return '-glob'
1044 return True
1047 return True
1045 i, n = 0, len(el)
1048 i, n = 0, len(el)
1046 res = ''
1049 res = ''
1047 while i < n:
1050 while i < n:
1048 c = el[i]
1051 c = el[i]
1049 i += 1
1052 i += 1
1050 if c == '\\' and el[i] in '*?\\/':
1053 if c == '\\' and el[i] in '*?\\/':
1051 res += el[i - 1:i + 1]
1054 res += el[i - 1:i + 1]
1052 i += 1
1055 i += 1
1053 elif c == '*':
1056 elif c == '*':
1054 res += '.*'
1057 res += '.*'
1055 elif c == '?':
1058 elif c == '?':
1056 res += '.'
1059 res += '.'
1057 elif c == '/' and os.altsep:
1060 elif c == '/' and os.altsep:
1058 res += '[/\\\\]'
1061 res += '[/\\\\]'
1059 else:
1062 else:
1060 res += re.escape(c)
1063 res += re.escape(c)
1061 return TTest.rematch(res, l)
1064 return TTest.rematch(res, l)
1062
1065
1063 @staticmethod
1066 @staticmethod
1064 def linematch(el, l):
1067 def linematch(el, l):
1065 if el == l: # perfect match (fast)
1068 if el == l: # perfect match (fast)
1066 return True
1069 return True
1067 if el:
1070 if el:
1068 if el.endswith(" (esc)\n"):
1071 if el.endswith(" (esc)\n"):
1069 el = el[:-7].decode('string-escape') + '\n'
1072 el = el[:-7].decode('string-escape') + '\n'
1070 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1073 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1071 return True
1074 return True
1072 if el.endswith(" (re)\n"):
1075 if el.endswith(" (re)\n"):
1073 return TTest.rematch(el[:-6], l)
1076 return TTest.rematch(el[:-6], l)
1074 if el.endswith(" (glob)\n"):
1077 if el.endswith(" (glob)\n"):
1075 return TTest.globmatch(el[:-8], l)
1078 return TTest.globmatch(el[:-8], l)
1076 if os.altsep and l.replace('\\', '/') == el:
1079 if os.altsep and l.replace('\\', '/') == el:
1077 return '+glob'
1080 return '+glob'
1078 return False
1081 return False
1079
1082
1080 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1083 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1081 def run(cmd, wd, options, replacements, env):
1084 def run(cmd, wd, options, replacements, env):
1082 """Run command in a sub-process, capturing the output (stdout and stderr).
1085 """Run command in a sub-process, capturing the output (stdout and stderr).
1083 Return a tuple (exitcode, output). output is None in debug mode."""
1086 Return a tuple (exitcode, output). output is None in debug mode."""
1084 # TODO: Use subprocess.Popen if we're running on Python 2.4
1087 # TODO: Use subprocess.Popen if we're running on Python 2.4
1085 if options.debug:
1088 if options.debug:
1086 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1089 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1087 ret = proc.wait()
1090 ret = proc.wait()
1088 return (ret, None)
1091 return (ret, None)
1089
1092
1090 proc = Popen4(cmd, wd, options.timeout, env)
1093 proc = Popen4(cmd, wd, options.timeout, env)
1091 def cleanup():
1094 def cleanup():
1092 terminate(proc)
1095 terminate(proc)
1093 ret = proc.wait()
1096 ret = proc.wait()
1094 if ret == 0:
1097 if ret == 0:
1095 ret = signal.SIGTERM << 8
1098 ret = signal.SIGTERM << 8
1096 killdaemons(env['DAEMON_PIDS'])
1099 killdaemons(env['DAEMON_PIDS'])
1097 return ret
1100 return ret
1098
1101
1099 output = ''
1102 output = ''
1100 proc.tochild.close()
1103 proc.tochild.close()
1101
1104
1102 try:
1105 try:
1103 output = proc.fromchild.read()
1106 output = proc.fromchild.read()
1104 except KeyboardInterrupt:
1107 except KeyboardInterrupt:
1105 vlog('# Handling keyboard interrupt')
1108 vlog('# Handling keyboard interrupt')
1106 cleanup()
1109 cleanup()
1107 raise
1110 raise
1108
1111
1109 ret = proc.wait()
1112 ret = proc.wait()
1110 if wifexited(ret):
1113 if wifexited(ret):
1111 ret = os.WEXITSTATUS(ret)
1114 ret = os.WEXITSTATUS(ret)
1112
1115
1113 if proc.timeout:
1116 if proc.timeout:
1114 ret = 'timeout'
1117 ret = 'timeout'
1115
1118
1116 if ret:
1119 if ret:
1117 killdaemons(env['DAEMON_PIDS'])
1120 killdaemons(env['DAEMON_PIDS'])
1118
1121
1119 if abort:
1122 if abort:
1120 raise KeyboardInterrupt()
1123 raise KeyboardInterrupt()
1121
1124
1122 for s, r in replacements:
1125 for s, r in replacements:
1123 output = re.sub(s, r, output)
1126 output = re.sub(s, r, output)
1124 return ret, output.splitlines(True)
1127 return ret, output.splitlines(True)
1125
1128
1126 def runone(options, test, count):
1129 def runone(options, test, count):
1127 '''returns a result element: (code, test, msg)'''
1130 '''returns a result element: (code, test, msg)'''
1128
1131
1129 def skip(msg):
1132 def skip(msg):
1130 if options.verbose:
1133 if options.verbose:
1131 log("\nSkipping %s: %s" % (testpath, msg))
1134 log("\nSkipping %s: %s" % (testpath, msg))
1132 return 's', test, msg
1135 return 's', test, msg
1133
1136
1134 testpath = os.path.join(TESTDIR, test)
1137 testpath = os.path.join(TESTDIR, test)
1135 err = os.path.join(TESTDIR, test + ".err")
1138 err = os.path.join(TESTDIR, test + ".err")
1136 lctest = test.lower()
1139 lctest = test.lower()
1137
1140
1138 if not os.path.basename(lctest).startswith("test-"):
1139 return skip("not a test file")
1140 for ext, cls, out in testtypes:
1141 for ext, cls, out in testtypes:
1141 if lctest.endswith(ext):
1142 if lctest.endswith(ext):
1142 runner = cls
1143 runner = cls
1143 ref = os.path.join(TESTDIR, test + out)
1144 ref = os.path.join(TESTDIR, test + out)
1144 break
1145 break
1145 else:
1146 else:
1146 return skip("unknown test type")
1147 return skip("unknown test type")
1147
1148
1148 vlog("# Test", test)
1149 vlog("# Test", test)
1149
1150
1150 t = runner(test, testpath, options, count, ref, err)
1151 t = runner(test, testpath, options, count, ref, err)
1151 res = TestResult()
1152 res = TestResult()
1152 result = t.run(res)
1153 result = t.run(res)
1153
1154
1154 if res.exception:
1155 if res.exception:
1155 return t.fail('Exception during execution: %s' % res.exception, 255)
1156 return t.fail('Exception during execution: %s' % res.exception, 255)
1156
1157
1157 ret = res.ret
1158 ret = res.ret
1158 out = res.out
1159 out = res.out
1159
1160
1160 times.append((test, res.duration))
1161 times.append((test, res.duration))
1161 vlog("# Ret was:", ret)
1162 vlog("# Ret was:", ret)
1162
1163
1163 skipped = res.skipped
1164 skipped = res.skipped
1164 refout = res.refout
1165 refout = res.refout
1165
1166
1166 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:
1167 # Save errors to a file for diagnosis
1168 # Save errors to a file for diagnosis
1168 f = open(err, "wb")
1169 f = open(err, "wb")
1169 for line in out:
1170 for line in out:
1170 f.write(line)
1171 f.write(line)
1171 f.close()
1172 f.close()
1172
1173
1173 if not options.verbose:
1174 if not options.verbose:
1174 iolock.acquire()
1175 iolock.acquire()
1175 sys.stdout.write(result[0])
1176 sys.stdout.write(result[0])
1176 sys.stdout.flush()
1177 sys.stdout.flush()
1177 iolock.release()
1178 iolock.release()
1178
1179
1179 t.cleanup()
1180 t.cleanup()
1180
1181
1181 return result
1182 return result
1182
1183
1183 _hgpath = None
1184 _hgpath = None
1184
1185
1185 def _gethgpath():
1186 def _gethgpath():
1186 """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
1187 the current Python interpreter."""
1188 the current Python interpreter."""
1188 global _hgpath
1189 global _hgpath
1189 if _hgpath is not None:
1190 if _hgpath is not None:
1190 return _hgpath
1191 return _hgpath
1191
1192
1192 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1193 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1193 pipe = os.popen(cmd % PYTHON)
1194 pipe = os.popen(cmd % PYTHON)
1194 try:
1195 try:
1195 _hgpath = pipe.read().strip()
1196 _hgpath = pipe.read().strip()
1196 finally:
1197 finally:
1197 pipe.close()
1198 pipe.close()
1198 return _hgpath
1199 return _hgpath
1199
1200
1200 def _checkhglib(verb):
1201 def _checkhglib(verb):
1201 """Ensure that the 'mercurial' package imported by python is
1202 """Ensure that the 'mercurial' package imported by python is
1202 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."""
1203 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1204 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1204 actualhg = _gethgpath()
1205 actualhg = _gethgpath()
1205 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1206 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1206 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1207 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1207 ' (expected %s)\n'
1208 ' (expected %s)\n'
1208 % (verb, actualhg, expecthg))
1209 % (verb, actualhg, expecthg))
1209
1210
1210 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1211 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1211 times = []
1212 times = []
1212 iolock = threading.Lock()
1213 iolock = threading.Lock()
1213 abort = False
1214 abort = False
1214
1215
1215 def scheduletests(options, tests):
1216 def scheduletests(options, tests):
1216 jobs = options.jobs
1217 jobs = options.jobs
1217 done = queue.Queue()
1218 done = queue.Queue()
1218 running = 0
1219 running = 0
1219 count = 0
1220 count = 0
1220 global abort
1221 global abort
1221
1222
1222 def job(test, count):
1223 def job(test, count):
1223 try:
1224 try:
1224 done.put(runone(options, test, count))
1225 done.put(runone(options, test, count))
1225 except KeyboardInterrupt:
1226 except KeyboardInterrupt:
1226 pass
1227 pass
1227 except: # re-raises
1228 except: # re-raises
1228 done.put(('!', test, 'run-test raised an error, see traceback'))
1229 done.put(('!', test, 'run-test raised an error, see traceback'))
1229 raise
1230 raise
1230
1231
1231 try:
1232 try:
1232 while tests or running:
1233 while tests or running:
1233 if not done.empty() or running == jobs or not tests:
1234 if not done.empty() or running == jobs or not tests:
1234 try:
1235 try:
1235 code, test, msg = done.get(True, 1)
1236 code, test, msg = done.get(True, 1)
1236 results[code].append((test, msg))
1237 results[code].append((test, msg))
1237 if options.first and code not in '.si':
1238 if options.first and code not in '.si':
1238 break
1239 break
1239 except queue.Empty:
1240 except queue.Empty:
1240 continue
1241 continue
1241 running -= 1
1242 running -= 1
1242 if tests and not running == jobs:
1243 if tests and not running == jobs:
1243 test = tests.pop(0)
1244 test = tests.pop(0)
1244 if options.loop:
1245 if options.loop:
1245 tests.append(test)
1246 tests.append(test)
1246 t = threading.Thread(target=job, name=test, args=(test, count))
1247 t = threading.Thread(target=job, name=test, args=(test, count))
1247 t.start()
1248 t.start()
1248 running += 1
1249 running += 1
1249 count += 1
1250 count += 1
1250 except KeyboardInterrupt:
1251 except KeyboardInterrupt:
1251 abort = True
1252 abort = True
1252
1253
1253 def runtests(options, tests):
1254 def runtests(options, tests):
1254 try:
1255 try:
1255 if INST:
1256 if INST:
1256 installhg(options)
1257 installhg(options)
1257 _checkhglib("Testing")
1258 _checkhglib("Testing")
1258 else:
1259 else:
1259 usecorrectpython()
1260 usecorrectpython()
1260
1261
1261 if options.restart:
1262 if options.restart:
1262 orig = list(tests)
1263 orig = list(tests)
1263 while tests:
1264 while tests:
1264 if os.path.exists(tests[0] + ".err"):
1265 if os.path.exists(tests[0] + ".err"):
1265 break
1266 break
1266 tests.pop(0)
1267 tests.pop(0)
1267 if not tests:
1268 if not tests:
1268 print "running all tests"
1269 print "running all tests"
1269 tests = orig
1270 tests = orig
1270
1271
1271 scheduletests(options, tests)
1272 scheduletests(options, tests)
1272
1273
1273 failed = len(results['!'])
1274 failed = len(results['!'])
1274 warned = len(results['~'])
1275 warned = len(results['~'])
1275 tested = len(results['.']) + failed + warned
1276 tested = len(results['.']) + failed + warned
1276 skipped = len(results['s'])
1277 skipped = len(results['s'])
1277 ignored = len(results['i'])
1278 ignored = len(results['i'])
1278
1279
1279 print
1280 print
1280 if not options.noskips:
1281 if not options.noskips:
1281 for s in results['s']:
1282 for s in results['s']:
1282 print "Skipped %s: %s" % s
1283 print "Skipped %s: %s" % s
1283 for s in results['~']:
1284 for s in results['~']:
1284 print "Warned %s: %s" % s
1285 print "Warned %s: %s" % s
1285 for s in results['!']:
1286 for s in results['!']:
1286 print "Failed %s: %s" % s
1287 print "Failed %s: %s" % s
1287 _checkhglib("Tested")
1288 _checkhglib("Tested")
1288 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1289 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1289 tested, skipped + ignored, warned, failed)
1290 tested, skipped + ignored, warned, failed)
1290 if results['!']:
1291 if results['!']:
1291 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1292 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1292 if options.time:
1293 if options.time:
1293 outputtimes(options)
1294 outputtimes(options)
1294
1295
1295 if options.anycoverage:
1296 if options.anycoverage:
1296 outputcoverage(options)
1297 outputcoverage(options)
1297 except KeyboardInterrupt:
1298 except KeyboardInterrupt:
1298 failed = True
1299 failed = True
1299 print "\ninterrupted!"
1300 print "\ninterrupted!"
1300
1301
1301 if failed:
1302 if failed:
1302 return 1
1303 return 1
1303 if warned:
1304 if warned:
1304 return 80
1305 return 80
1305
1306
1306 testtypes = [('.py', PythonTest, '.out'),
1307 testtypes = [('.py', PythonTest, '.out'),
1307 ('.t', TTest, '')]
1308 ('.t', TTest, '')]
1308
1309
1309 def main(args, parser=None):
1310 def main(args, parser=None):
1310 parser = parser or getparser()
1311 parser = parser or getparser()
1311 (options, args) = parseargs(args, parser)
1312 (options, args) = parseargs(args, parser)
1312 os.umask(022)
1313 os.umask(022)
1313
1314
1314 checktools()
1315 checktools()
1315
1316
1316 if not args:
1317 if not args:
1317 if options.changed:
1318 if options.changed:
1318 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1319 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1319 None, 0)
1320 None, 0)
1320 stdout, stderr = proc.communicate()
1321 stdout, stderr = proc.communicate()
1321 args = stdout.strip('\0').split('\0')
1322 args = stdout.strip('\0').split('\0')
1322 else:
1323 else:
1323 args = os.listdir(".")
1324 args = os.listdir(".")
1324
1325
1325 tests = [t for t in args
1326 tests = [t for t in args
1326 if os.path.basename(t).startswith("test-")
1327 if os.path.basename(t).startswith("test-")
1327 and (t.endswith(".py") or t.endswith(".t"))]
1328 and (t.endswith(".py") or t.endswith(".t"))]
1328
1329
1329 if options.random:
1330 if options.random:
1330 random.shuffle(tests)
1331 random.shuffle(tests)
1331 else:
1332 else:
1332 # keywords for slow tests
1333 # keywords for slow tests
1333 slow = 'svn gendoc check-code-hg'.split()
1334 slow = 'svn gendoc check-code-hg'.split()
1334 def sortkey(f):
1335 def sortkey(f):
1335 # run largest tests first, as they tend to take the longest
1336 # run largest tests first, as they tend to take the longest
1336 try:
1337 try:
1337 val = -os.stat(f).st_size
1338 val = -os.stat(f).st_size
1338 except OSError, e:
1339 except OSError, e:
1339 if e.errno != errno.ENOENT:
1340 if e.errno != errno.ENOENT:
1340 raise
1341 raise
1341 return -1e9 # file does not exist, tell early
1342 return -1e9 # file does not exist, tell early
1342 for kw in slow:
1343 for kw in slow:
1343 if kw in f:
1344 if kw in f:
1344 val *= 10
1345 val *= 10
1345 return val
1346 return val
1346 tests.sort(key=sortkey)
1347 tests.sort(key=sortkey)
1347
1348
1348 if 'PYTHONHASHSEED' not in os.environ:
1349 if 'PYTHONHASHSEED' not in os.environ:
1349 # use a random python hash seed all the time
1350 # use a random python hash seed all the time
1350 # we do the randomness ourself to know what seed is used
1351 # we do the randomness ourself to know what seed is used
1351 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1352 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1352
1353
1353 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1354 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1354 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1355 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1355 if options.tmpdir:
1356 if options.tmpdir:
1356 options.keep_tmpdir = True
1357 options.keep_tmpdir = True
1357 tmpdir = options.tmpdir
1358 tmpdir = options.tmpdir
1358 if os.path.exists(tmpdir):
1359 if os.path.exists(tmpdir):
1359 # 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
1360 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1361 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1361 # tmpdir already exists.
1362 # tmpdir already exists.
1362 print "error: temp dir %r already exists" % tmpdir
1363 print "error: temp dir %r already exists" % tmpdir
1363 return 1
1364 return 1
1364
1365
1365 # Automatically removing tmpdir sounds convenient, but could
1366 # Automatically removing tmpdir sounds convenient, but could
1366 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1367 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1367 # or "--tmpdir=$HOME".
1368 # or "--tmpdir=$HOME".
1368 #vlog("# Removing temp dir", tmpdir)
1369 #vlog("# Removing temp dir", tmpdir)
1369 #shutil.rmtree(tmpdir)
1370 #shutil.rmtree(tmpdir)
1370 os.makedirs(tmpdir)
1371 os.makedirs(tmpdir)
1371 else:
1372 else:
1372 d = None
1373 d = None
1373 if os.name == 'nt':
1374 if os.name == 'nt':
1374 # without this, we get the default temp dir location, but
1375 # without this, we get the default temp dir location, but
1375 # in all lowercase, which causes troubles with paths (issue3490)
1376 # in all lowercase, which causes troubles with paths (issue3490)
1376 d = os.getenv('TMP')
1377 d = os.getenv('TMP')
1377 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1378 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1378 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1379 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1379
1380
1380 if options.with_hg:
1381 if options.with_hg:
1381 INST = None
1382 INST = None
1382 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1383 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1383 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1384 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1384 os.makedirs(TMPBINDIR)
1385 os.makedirs(TMPBINDIR)
1385
1386
1386 # This looks redundant with how Python initializes sys.path from
1387 # This looks redundant with how Python initializes sys.path from
1387 # the location of the script being executed. Needed because the
1388 # the location of the script being executed. Needed because the
1388 # "hg" specified by --with-hg is not the only Python script
1389 # "hg" specified by --with-hg is not the only Python script
1389 # executed in the test suite that needs to import 'mercurial'
1390 # executed in the test suite that needs to import 'mercurial'
1390 # ... which means it's not really redundant at all.
1391 # ... which means it's not really redundant at all.
1391 PYTHONDIR = BINDIR
1392 PYTHONDIR = BINDIR
1392 else:
1393 else:
1393 INST = os.path.join(HGTMP, "install")
1394 INST = os.path.join(HGTMP, "install")
1394 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1395 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1395 TMPBINDIR = BINDIR
1396 TMPBINDIR = BINDIR
1396 PYTHONDIR = os.path.join(INST, "lib", "python")
1397 PYTHONDIR = os.path.join(INST, "lib", "python")
1397
1398
1398 os.environ["BINDIR"] = BINDIR
1399 os.environ["BINDIR"] = BINDIR
1399 os.environ["PYTHON"] = PYTHON
1400 os.environ["PYTHON"] = PYTHON
1400
1401
1401 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1402 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1402 if TMPBINDIR != BINDIR:
1403 if TMPBINDIR != BINDIR:
1403 path = [TMPBINDIR] + path
1404 path = [TMPBINDIR] + path
1404 os.environ["PATH"] = os.pathsep.join(path)
1405 os.environ["PATH"] = os.pathsep.join(path)
1405
1406
1406 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1407 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1407 # can run .../tests/run-tests.py test-foo where test-foo
1408 # can run .../tests/run-tests.py test-foo where test-foo
1408 # 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
1409 # modules like heredoctest.
1410 # modules like heredoctest.
1410 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1411 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1411 # We have to augment PYTHONPATH, rather than simply replacing
1412 # We have to augment PYTHONPATH, rather than simply replacing
1412 # it, in case external libraries are only available via current
1413 # it, in case external libraries are only available via current
1413 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1414 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1414 # are in /opt/subversion.)
1415 # are in /opt/subversion.)
1415 oldpypath = os.environ.get(IMPL_PATH)
1416 oldpypath = os.environ.get(IMPL_PATH)
1416 if oldpypath:
1417 if oldpypath:
1417 pypath.append(oldpypath)
1418 pypath.append(oldpypath)
1418 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1419 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1419
1420
1420 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1421 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1421
1422
1422 vlog("# Using TESTDIR", TESTDIR)
1423 vlog("# Using TESTDIR", TESTDIR)
1423 vlog("# Using HGTMP", HGTMP)
1424 vlog("# Using HGTMP", HGTMP)
1424 vlog("# Using PATH", os.environ["PATH"])
1425 vlog("# Using PATH", os.environ["PATH"])
1425 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1426 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1426
1427
1427 try:
1428 try:
1428 return runtests(options, tests) or 0
1429 return runtests(options, tests) or 0
1429 finally:
1430 finally:
1430 time.sleep(.1)
1431 time.sleep(.1)
1431 cleanup(options)
1432 cleanup(options)
1432
1433
1433 if __name__ == '__main__':
1434 if __name__ == '__main__':
1434 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