##// END OF EJS Templates
tests: add some comments to the unified test code
Matt Mackall -
r15413:8e60433e default
parent child Browse files
Show More
@@ -1,1230 +1,1249 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 # Modifying this script is tricky because it has many modes:
11 11 # - serial (default) vs parallel (-jN, N > 1)
12 12 # - no coverage (default) vs coverage (-c, -C, -s)
13 13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 14 # - tests are a mix of shell scripts and Python scripts
15 15 #
16 16 # If you change this script, it is recommended that you ensure you
17 17 # haven't broken it by running it in various modes with a representative
18 18 # sample of test scripts. For example:
19 19 #
20 20 # 1) serial, no coverage, temp install:
21 21 # ./run-tests.py test-s*
22 22 # 2) serial, no coverage, local hg:
23 23 # ./run-tests.py --local test-s*
24 24 # 3) serial, coverage, temp install:
25 25 # ./run-tests.py -c test-s*
26 26 # 4) serial, coverage, local hg:
27 27 # ./run-tests.py -c --local test-s* # unsupported
28 28 # 5) parallel, no coverage, temp install:
29 29 # ./run-tests.py -j2 test-s*
30 30 # 6) parallel, no coverage, local hg:
31 31 # ./run-tests.py -j2 --local test-s*
32 32 # 7) parallel, coverage, temp install:
33 33 # ./run-tests.py -j2 -c test-s* # currently broken
34 34 # 8) parallel, coverage, local install:
35 35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 36 # 9) parallel, custom tmp dir:
37 37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 38 #
39 39 # (You could use any subset of the tests: test-s* happens to match
40 40 # enough that it's worth doing parallel runs, few enough that it
41 41 # completes fairly quickly, includes both shell and Python scripts, and
42 42 # includes some scripts that run daemon processes.)
43 43
44 44 from distutils import version
45 45 import difflib
46 46 import errno
47 47 import optparse
48 48 import os
49 49 import shutil
50 50 import subprocess
51 51 import signal
52 52 import sys
53 53 import tempfile
54 54 import time
55 55 import re
56 56 import threading
57 57
58 58 processlock = threading.Lock()
59 59
60 60 closefds = os.name == 'posix'
61 61 def Popen4(cmd, wd, timeout):
62 62 processlock.acquire()
63 63 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd,
64 64 close_fds=closefds,
65 65 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
66 66 stderr=subprocess.STDOUT)
67 67 processlock.release()
68 68
69 69 p.fromchild = p.stdout
70 70 p.tochild = p.stdin
71 71 p.childerr = p.stderr
72 72
73 73 p.timeout = False
74 74 if timeout:
75 75 def t():
76 76 start = time.time()
77 77 while time.time() - start < timeout and p.returncode is None:
78 78 time.sleep(1)
79 79 p.timeout = True
80 80 if p.returncode is None:
81 81 terminate(p)
82 82 threading.Thread(target=t).start()
83 83
84 84 return p
85 85
86 86 # reserved exit code to skip test (used by hghave)
87 87 SKIPPED_STATUS = 80
88 88 SKIPPED_PREFIX = 'skipped: '
89 89 FAILED_PREFIX = 'hghave check failed: '
90 90 PYTHON = sys.executable
91 91 IMPL_PATH = 'PYTHONPATH'
92 92 if 'java' in sys.platform:
93 93 IMPL_PATH = 'JYTHONPATH'
94 94
95 95 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
96 96
97 97 defaults = {
98 98 'jobs': ('HGTEST_JOBS', 1),
99 99 'timeout': ('HGTEST_TIMEOUT', 180),
100 100 'port': ('HGTEST_PORT', 20059),
101 101 'shell': ('HGTEST_SHELL', '/bin/sh'),
102 102 }
103 103
104 104 def parselistfiles(files, listtype, warn=True):
105 105 entries = dict()
106 106 for filename in files:
107 107 try:
108 108 path = os.path.expanduser(os.path.expandvars(filename))
109 109 f = open(path, "r")
110 110 except IOError, err:
111 111 if err.errno != errno.ENOENT:
112 112 raise
113 113 if warn:
114 114 print "warning: no such %s file: %s" % (listtype, filename)
115 115 continue
116 116
117 117 for line in f.readlines():
118 118 line = line.split('#', 1)[0].strip()
119 119 if line:
120 120 entries[line] = filename
121 121
122 122 f.close()
123 123 return entries
124 124
125 125 def parseargs():
126 126 parser = optparse.OptionParser("%prog [options] [tests]")
127 127
128 128 # keep these sorted
129 129 parser.add_option("--blacklist", action="append",
130 130 help="skip tests listed in the specified blacklist file")
131 131 parser.add_option("--whitelist", action="append",
132 132 help="always run tests listed in the specified whitelist file")
133 133 parser.add_option("-C", "--annotate", action="store_true",
134 134 help="output files annotated with coverage")
135 135 parser.add_option("--child", type="int",
136 136 help="run as child process, summary to given fd")
137 137 parser.add_option("-c", "--cover", action="store_true",
138 138 help="print a test coverage report")
139 139 parser.add_option("-d", "--debug", action="store_true",
140 140 help="debug mode: write output of test scripts to console"
141 141 " rather than capturing and diff'ing it (disables timeout)")
142 142 parser.add_option("-f", "--first", action="store_true",
143 143 help="exit on the first test failure")
144 144 parser.add_option("--inotify", action="store_true",
145 145 help="enable inotify extension when running tests")
146 146 parser.add_option("-i", "--interactive", action="store_true",
147 147 help="prompt to accept changed output")
148 148 parser.add_option("-j", "--jobs", type="int",
149 149 help="number of jobs to run in parallel"
150 150 " (default: $%s or %d)" % defaults['jobs'])
151 151 parser.add_option("--keep-tmpdir", action="store_true",
152 152 help="keep temporary directory after running tests")
153 153 parser.add_option("-k", "--keywords",
154 154 help="run tests matching keywords")
155 155 parser.add_option("-l", "--local", action="store_true",
156 156 help="shortcut for --with-hg=<testdir>/../hg")
157 157 parser.add_option("-n", "--nodiff", action="store_true",
158 158 help="skip showing test changes")
159 159 parser.add_option("-p", "--port", type="int",
160 160 help="port on which servers should listen"
161 161 " (default: $%s or %d)" % defaults['port'])
162 162 parser.add_option("--pure", action="store_true",
163 163 help="use pure Python code instead of C extensions")
164 164 parser.add_option("-R", "--restart", action="store_true",
165 165 help="restart at last error")
166 166 parser.add_option("-r", "--retest", action="store_true",
167 167 help="retest failed tests")
168 168 parser.add_option("-S", "--noskips", action="store_true",
169 169 help="don't report skip tests verbosely")
170 170 parser.add_option("--shell", type="string",
171 171 help="shell to use (default: $%s or %s)" % defaults['shell'])
172 172 parser.add_option("-t", "--timeout", type="int",
173 173 help="kill errant tests after TIMEOUT seconds"
174 174 " (default: $%s or %d)" % defaults['timeout'])
175 175 parser.add_option("--tmpdir", type="string",
176 176 help="run tests in the given temporary directory"
177 177 " (implies --keep-tmpdir)")
178 178 parser.add_option("-v", "--verbose", action="store_true",
179 179 help="output verbose messages")
180 180 parser.add_option("--view", type="string",
181 181 help="external diff viewer")
182 182 parser.add_option("--with-hg", type="string",
183 183 metavar="HG",
184 184 help="test using specified hg script rather than a "
185 185 "temporary installation")
186 186 parser.add_option("-3", "--py3k-warnings", action="store_true",
187 187 help="enable Py3k warnings on Python 2.6+")
188 188 parser.add_option('--extra-config-opt', action="append",
189 189 help='set the given config opt in the test hgrc')
190 190
191 191 for option, (envvar, default) in defaults.items():
192 192 defaults[option] = type(default)(os.environ.get(envvar, default))
193 193 parser.set_defaults(**defaults)
194 194 (options, args) = parser.parse_args()
195 195
196 196 # jython is always pure
197 197 if 'java' in sys.platform or '__pypy__' in sys.modules:
198 198 options.pure = True
199 199
200 200 if options.with_hg:
201 201 if not (os.path.isfile(options.with_hg) and
202 202 os.access(options.with_hg, os.X_OK)):
203 203 parser.error('--with-hg must specify an executable hg script')
204 204 if not os.path.basename(options.with_hg) == 'hg':
205 205 sys.stderr.write('warning: --with-hg should specify an hg script\n')
206 206 if options.local:
207 207 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
208 208 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
209 209 if not os.access(hgbin, os.X_OK):
210 210 parser.error('--local specified, but %r not found or not executable'
211 211 % hgbin)
212 212 options.with_hg = hgbin
213 213
214 214 options.anycoverage = options.cover or options.annotate
215 215 if options.anycoverage:
216 216 try:
217 217 import coverage
218 218 covver = version.StrictVersion(coverage.__version__).version
219 219 if covver < (3, 3):
220 220 parser.error('coverage options require coverage 3.3 or later')
221 221 except ImportError:
222 222 parser.error('coverage options now require the coverage package')
223 223
224 224 if options.anycoverage and options.local:
225 225 # this needs some path mangling somewhere, I guess
226 226 parser.error("sorry, coverage options do not work when --local "
227 227 "is specified")
228 228
229 229 global vlog
230 230 if options.verbose:
231 231 if options.jobs > 1 or options.child is not None:
232 232 pid = "[%d]" % os.getpid()
233 233 else:
234 234 pid = None
235 235 def vlog(*msg):
236 236 iolock.acquire()
237 237 if pid:
238 238 print pid,
239 239 for m in msg:
240 240 print m,
241 241 print
242 242 sys.stdout.flush()
243 243 iolock.release()
244 244 else:
245 245 vlog = lambda *msg: None
246 246
247 247 if options.tmpdir:
248 248 options.tmpdir = os.path.expanduser(options.tmpdir)
249 249
250 250 if options.jobs < 1:
251 251 parser.error('--jobs must be positive')
252 252 if options.interactive and options.jobs > 1:
253 253 print '(--interactive overrides --jobs)'
254 254 options.jobs = 1
255 255 if options.interactive and options.debug:
256 256 parser.error("-i/--interactive and -d/--debug are incompatible")
257 257 if options.debug:
258 258 if options.timeout != defaults['timeout']:
259 259 sys.stderr.write(
260 260 'warning: --timeout option ignored with --debug\n')
261 261 options.timeout = 0
262 262 if options.py3k_warnings:
263 263 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
264 264 parser.error('--py3k-warnings can only be used on Python 2.6+')
265 265 if options.blacklist:
266 266 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
267 267 if options.whitelist:
268 268 options.whitelisted = parselistfiles(options.whitelist, 'whitelist',
269 269 warn=options.child is None)
270 270 else:
271 271 options.whitelisted = {}
272 272
273 273 return (options, args)
274 274
275 275 def rename(src, dst):
276 276 """Like os.rename(), trade atomicity and opened files friendliness
277 277 for existing destination support.
278 278 """
279 279 shutil.copy(src, dst)
280 280 os.remove(src)
281 281
282 282 def splitnewlines(text):
283 283 '''like str.splitlines, but only split on newlines.
284 284 keep line endings.'''
285 285 i = 0
286 286 lines = []
287 287 while True:
288 288 n = text.find('\n', i)
289 289 if n == -1:
290 290 last = text[i:]
291 291 if last:
292 292 lines.append(last)
293 293 return lines
294 294 lines.append(text[i:n + 1])
295 295 i = n + 1
296 296
297 297 def parsehghaveoutput(lines):
298 298 '''Parse hghave log lines.
299 299 Return tuple of lists (missing, failed):
300 300 * the missing/unknown features
301 301 * the features for which existence check failed'''
302 302 missing = []
303 303 failed = []
304 304 for line in lines:
305 305 if line.startswith(SKIPPED_PREFIX):
306 306 line = line.splitlines()[0]
307 307 missing.append(line[len(SKIPPED_PREFIX):])
308 308 elif line.startswith(FAILED_PREFIX):
309 309 line = line.splitlines()[0]
310 310 failed.append(line[len(FAILED_PREFIX):])
311 311
312 312 return missing, failed
313 313
314 314 def showdiff(expected, output, ref, err):
315 315 print
316 316 for line in difflib.unified_diff(expected, output, ref, err):
317 317 sys.stdout.write(line)
318 318
319 319 def findprogram(program):
320 320 """Search PATH for a executable program"""
321 321 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
322 322 name = os.path.join(p, program)
323 323 if os.name == 'nt' or os.access(name, os.X_OK):
324 324 return name
325 325 return None
326 326
327 327 def checktools():
328 328 # Before we go any further, check for pre-requisite tools
329 329 # stuff from coreutils (cat, rm, etc) are not tested
330 330 for p in requiredtools:
331 331 if os.name == 'nt':
332 332 p += '.exe'
333 333 found = findprogram(p)
334 334 if found:
335 335 vlog("# Found prerequisite", p, "at", found)
336 336 else:
337 337 print "WARNING: Did not find prerequisite tool: "+p
338 338
339 339 def terminate(proc):
340 340 """Terminate subprocess (with fallback for Python versions < 2.6)"""
341 341 vlog('# Terminating process %d' % proc.pid)
342 342 try:
343 343 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
344 344 except OSError:
345 345 pass
346 346
347 347 def killdaemons():
348 348 # Kill off any leftover daemon processes
349 349 try:
350 350 fp = open(DAEMON_PIDS)
351 351 for line in fp:
352 352 try:
353 353 pid = int(line)
354 354 except ValueError:
355 355 continue
356 356 try:
357 357 os.kill(pid, 0)
358 358 vlog('# Killing daemon process %d' % pid)
359 359 os.kill(pid, signal.SIGTERM)
360 360 time.sleep(0.25)
361 361 os.kill(pid, 0)
362 362 vlog('# Daemon process %d is stuck - really killing it' % pid)
363 363 os.kill(pid, signal.SIGKILL)
364 364 except OSError, err:
365 365 if err.errno != errno.ESRCH:
366 366 raise
367 367 fp.close()
368 368 os.unlink(DAEMON_PIDS)
369 369 except IOError:
370 370 pass
371 371
372 372 def cleanup(options):
373 373 if not options.keep_tmpdir:
374 374 vlog("# Cleaning up HGTMP", HGTMP)
375 375 shutil.rmtree(HGTMP, True)
376 376
377 377 def usecorrectpython():
378 378 # some tests run python interpreter. they must use same
379 379 # interpreter we use or bad things will happen.
380 380 exedir, exename = os.path.split(sys.executable)
381 381 if exename in ('python', 'python.exe'):
382 382 path = findprogram(exename)
383 383 if os.path.dirname(path) == exedir:
384 384 return
385 385 else:
386 386 exename = 'python'
387 387 vlog('# Making python executable in test path use correct Python')
388 388 mypython = os.path.join(BINDIR, exename)
389 389 try:
390 390 os.symlink(sys.executable, mypython)
391 391 except AttributeError:
392 392 # windows fallback
393 393 shutil.copyfile(sys.executable, mypython)
394 394 shutil.copymode(sys.executable, mypython)
395 395
396 396 def installhg(options):
397 397 vlog("# Performing temporary installation of HG")
398 398 installerrs = os.path.join("tests", "install.err")
399 399 pure = options.pure and "--pure" or ""
400 400
401 401 # Run installer in hg root
402 402 script = os.path.realpath(sys.argv[0])
403 403 hgroot = os.path.dirname(os.path.dirname(script))
404 404 os.chdir(hgroot)
405 405 nohome = '--home=""'
406 406 if os.name == 'nt':
407 407 # The --home="" trick works only on OS where os.sep == '/'
408 408 # because of a distutils convert_path() fast-path. Avoid it at
409 409 # least on Windows for now, deal with .pydistutils.cfg bugs
410 410 # when they happen.
411 411 nohome = ''
412 412 cmd = ('%s setup.py %s clean --all'
413 413 ' build --build-base="%s"'
414 414 ' install --force --prefix="%s" --install-lib="%s"'
415 415 ' --install-scripts="%s" %s >%s 2>&1'
416 416 % (sys.executable, pure, os.path.join(HGTMP, "build"),
417 417 INST, PYTHONDIR, BINDIR, nohome, installerrs))
418 418 vlog("# Running", cmd)
419 419 if os.system(cmd) == 0:
420 420 if not options.verbose:
421 421 os.remove(installerrs)
422 422 else:
423 423 f = open(installerrs)
424 424 for line in f:
425 425 print line,
426 426 f.close()
427 427 sys.exit(1)
428 428 os.chdir(TESTDIR)
429 429
430 430 usecorrectpython()
431 431
432 432 vlog("# Installing dummy diffstat")
433 433 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
434 434 f.write('#!' + sys.executable + '\n'
435 435 'import sys\n'
436 436 'files = 0\n'
437 437 'for line in sys.stdin:\n'
438 438 ' if line.startswith("diff "):\n'
439 439 ' files += 1\n'
440 440 'sys.stdout.write("files patched: %d\\n" % files)\n')
441 441 f.close()
442 442 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
443 443
444 444 if options.py3k_warnings and not options.anycoverage:
445 445 vlog("# Updating hg command to enable Py3k Warnings switch")
446 446 f = open(os.path.join(BINDIR, 'hg'), 'r')
447 447 lines = [line.rstrip() for line in f]
448 448 lines[0] += ' -3'
449 449 f.close()
450 450 f = open(os.path.join(BINDIR, 'hg'), 'w')
451 451 for line in lines:
452 452 f.write(line + '\n')
453 453 f.close()
454 454
455 455 hgbat = os.path.join(BINDIR, 'hg.bat')
456 456 if os.path.isfile(hgbat):
457 457 # hg.bat expects to be put in bin/scripts while run-tests.py
458 458 # installation layout put it in bin/ directly. Fix it
459 459 f = open(hgbat, 'rb')
460 460 data = f.read()
461 461 f.close()
462 462 if '"%~dp0..\python" "%~dp0hg" %*' in data:
463 463 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
464 464 '"%~dp0python" "%~dp0hg" %*')
465 465 f = open(hgbat, 'wb')
466 466 f.write(data)
467 467 f.close()
468 468 else:
469 469 print 'WARNING: cannot fix hg.bat reference to python.exe'
470 470
471 471 if options.anycoverage:
472 472 custom = os.path.join(TESTDIR, 'sitecustomize.py')
473 473 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
474 474 vlog('# Installing coverage trigger to %s' % target)
475 475 shutil.copyfile(custom, target)
476 476 rc = os.path.join(TESTDIR, '.coveragerc')
477 477 vlog('# Installing coverage rc to %s' % rc)
478 478 os.environ['COVERAGE_PROCESS_START'] = rc
479 479 fn = os.path.join(INST, '..', '.coverage')
480 480 os.environ['COVERAGE_FILE'] = fn
481 481
482 482 def outputcoverage(options):
483 483
484 484 vlog('# Producing coverage report')
485 485 os.chdir(PYTHONDIR)
486 486
487 487 def covrun(*args):
488 488 cmd = 'coverage %s' % ' '.join(args)
489 489 vlog('# Running: %s' % cmd)
490 490 os.system(cmd)
491 491
492 492 if options.child:
493 493 return
494 494
495 495 covrun('-c')
496 496 omit = ','.join([BINDIR, TESTDIR])
497 497 covrun('-i', '-r', '"--omit=%s"' % omit) # report
498 498 if options.annotate:
499 499 adir = os.path.join(TESTDIR, 'annotated')
500 500 if not os.path.isdir(adir):
501 501 os.mkdir(adir)
502 502 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
503 503
504 504 def pytest(test, wd, options, replacements):
505 505 py3kswitch = options.py3k_warnings and ' -3' or ''
506 506 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
507 507 vlog("# Running", cmd)
508 508 return run(cmd, wd, options, replacements)
509 509
510 510 def shtest(test, wd, options, replacements):
511 511 cmd = '"%s"' % test
512 512 vlog("# Running", cmd)
513 513 return run(cmd, wd, options, replacements)
514 514
515 515 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
516 516 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
517 517 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
518 518 escapemap.update({'\\': '\\\\', '\r': r'\r'})
519 519 def escapef(m):
520 520 return escapemap[m.group(0)]
521 521 def stringescape(s):
522 522 return escapesub(escapef, s)
523 523
524 524 def tsttest(test, wd, options, replacements):
525 525 t = open(test)
526 526 out = []
527 527 script = []
528
529 # We generate a shell script which outputs unique markers to line
530 # up script results with our source. These markers include input
531 # line number and the last return code
528 532 salt = "SALT" + str(time.time())
533 def addsalt(line):
534 script.append('echo %s %s $?\n' % (salt, line))
529 535
536 # After we run the shell script, we re-unify the script output
537 # with non-active parts of the source, with synchronization by our
538 # SALT line number markers. The after table contains the
539 # non-active components, ordered by line number
540 after = {}
530 541 pos = prepos = -1
531 after = {}
542
543 # Expected shellscript output
532 544 expected = {}
545
546 # We keep track of whether or not we're in a Python block so we
547 # can generate the surrounding doctest magic
533 548 inpython = False
549
534 550 for n, l in enumerate(t):
535 551 if not l.endswith('\n'):
536 552 l += '\n'
537 if l.startswith(' >>> '):
553 if l.startswith(' >>> '): # python inlines
538 554 if not inpython:
539 555 # we've just entered a Python block, add the header
540 556 inpython = True
541 script.append('echo %s %s $?\n' % (salt, n))
557 addsalt(n)
542 558 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
543 559 prepos = pos
544 560 pos = n
545 561 after.setdefault(prepos, []).append(l)
546 562 script.append(l[2:])
547 563 elif l.startswith(' $ '): # commands
548 564 if inpython:
549 565 script.append("EOF\n")
550 566 inpython = False
551 567 after.setdefault(pos, []).append(l)
552 568 prepos = pos
553 569 pos = n
554 script.append('echo %s %s $?\n' % (salt, n))
570 addsalt(n)
555 571 script.append(l[4:])
556 572 elif l.startswith(' > '): # continuations
557 573 after.setdefault(prepos, []).append(l)
558 574 script.append(l[4:])
559 575 elif l.startswith(' '): # results
560 576 if inpython:
561 577 script.append(l[2:])
562 578 after.setdefault(prepos, []).append(l)
563 579 else:
564 580 # queue up a list of expected results
565 581 expected.setdefault(pos, []).append(l[2:])
566 582 else:
567 583 if inpython:
568 584 script.append("EOF\n")
569 585 inpython = False
570 586 # non-command/result - queue up for merged output
571 587 after.setdefault(pos, []).append(l)
572 588
573 if inpython:
574 script.append("EOF\n")
575
576 589 t.close()
577 590
578 script.append('echo %s %s $?\n' % (salt, n + 1))
591 if inpython:
592 script.append("EOF\n")
593 addsalt(n + 1)
579 594
580 595 fd, name = tempfile.mkstemp(suffix='hg-tst')
581
582 596 try:
583 597 for l in script:
584 598 os.write(fd, l)
585 599 os.close(fd)
586 600
587 601 cmd = '"%s" "%s"' % (options.shell, name)
588 602 vlog("# Running", cmd)
589 603 exitcode, output = run(cmd, wd, options, replacements)
590 604 # do not merge output if skipped, return hghave message instead
591 605 # similarly, with --debug, output is None
592 606 if exitcode == SKIPPED_STATUS or output is None:
593 607 return exitcode, output
594 608 finally:
595 609 os.remove(name)
596 610
597 611 def rematch(el, l):
598 612 try:
599 613 # ensure that the regex matches to the end of the string
600 614 return re.match(el + r'\Z', l)
601 615 except re.error:
602 616 # el is an invalid regex
603 617 return False
604 618
605 619 def globmatch(el, l):
606 620 # The only supported special characters are * and ?. Escaping is
607 621 # supported.
608 622 i, n = 0, len(el)
609 623 res = ''
610 624 while i < n:
611 625 c = el[i]
612 626 i += 1
613 627 if c == '\\' and el[i] in '*?\\':
614 628 res += el[i - 1:i + 1]
615 629 i += 1
616 630 elif c == '*':
617 631 res += '.*'
618 632 elif c == '?':
619 633 res += '.'
620 634 else:
621 635 res += re.escape(c)
622 636 return rematch(res, l)
623 637
638 # Merge the script output back into a unified test
639
624 640 pos = -1
625 641 postout = []
626 642 ret = 0
627 643 for n, l in enumerate(output):
628 644 lout, lcmd = l, None
629 645 if salt in l:
630 646 lout, lcmd = l.split(salt, 1)
631 647
632 648 if lout:
633 649 if lcmd:
650 # output block had no trailing newline, clean up
634 651 lout += ' (no-eol)\n'
635 652
653 # find the expected output at the current position
636 654 el = None
637 655 if pos in expected and expected[pos]:
638 656 el = expected[pos].pop(0)
639 657
640 658 if el == lout: # perfect match (fast)
641 659 postout.append(" " + lout)
642 660 elif (el and
643 661 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
644 662 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
645 663 or el.endswith(" (esc)\n") and
646 664 el.decode('string-escape') == l)):
647 665 postout.append(" " + el) # fallback regex/glob/esc match
648 666 else:
649 667 if needescape(lout):
650 668 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
651 669 postout.append(" " + lout) # let diff deal with it
652 670
653 671 if lcmd:
654 672 # add on last return code
655 673 ret = int(lcmd.split()[1])
656 674 if ret != 0:
657 675 postout.append(" [%s]\n" % ret)
658 676 if pos in after:
677 # merge in non-active test bits
659 678 postout += after.pop(pos)
660 679 pos = int(lcmd.split()[0])
661 680
662 681 if pos in after:
663 682 postout += after.pop(pos)
664 683
665 684 return exitcode, postout
666 685
667 686 wifexited = getattr(os, "WIFEXITED", lambda x: False)
668 687 def run(cmd, wd, options, replacements):
669 688 """Run command in a sub-process, capturing the output (stdout and stderr).
670 689 Return a tuple (exitcode, output). output is None in debug mode."""
671 690 # TODO: Use subprocess.Popen if we're running on Python 2.4
672 691 if options.debug:
673 692 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
674 693 ret = proc.wait()
675 694 return (ret, None)
676 695
677 696 proc = Popen4(cmd, wd, options.timeout)
678 697 def cleanup():
679 698 terminate(proc)
680 699 ret = proc.wait()
681 700 if ret == 0:
682 701 ret = signal.SIGTERM << 8
683 702 killdaemons()
684 703 return ret
685 704
686 705 output = ''
687 706 proc.tochild.close()
688 707
689 708 try:
690 709 output = proc.fromchild.read()
691 710 except KeyboardInterrupt:
692 711 vlog('# Handling keyboard interrupt')
693 712 cleanup()
694 713 raise
695 714
696 715 ret = proc.wait()
697 716 if wifexited(ret):
698 717 ret = os.WEXITSTATUS(ret)
699 718
700 719 if proc.timeout:
701 720 ret = 'timeout'
702 721
703 722 if ret:
704 723 killdaemons()
705 724
706 725 for s, r in replacements:
707 726 output = re.sub(s, r, output)
708 727 return ret, splitnewlines(output)
709 728
710 729 def runone(options, test):
711 730 '''tristate output:
712 731 None -> skipped
713 732 True -> passed
714 733 False -> failed'''
715 734
716 735 global results, resultslock, iolock
717 736
718 737 testpath = os.path.join(TESTDIR, test)
719 738
720 739 def result(l, e):
721 740 resultslock.acquire()
722 741 results[l].append(e)
723 742 resultslock.release()
724 743
725 744 def skip(msg):
726 745 if not options.verbose:
727 746 result('s', (test, msg))
728 747 else:
729 748 iolock.acquire()
730 749 print "\nSkipping %s: %s" % (testpath, msg)
731 750 iolock.release()
732 751 return None
733 752
734 753 def fail(msg, ret):
735 754 if not options.nodiff:
736 755 iolock.acquire()
737 756 print "\nERROR: %s %s" % (testpath, msg)
738 757 iolock.release()
739 758 if (not ret and options.interactive
740 759 and os.path.exists(testpath + ".err")):
741 760 iolock.acquire()
742 761 print "Accept this change? [n] ",
743 762 answer = sys.stdin.readline().strip()
744 763 iolock.release()
745 764 if answer.lower() in "y yes".split():
746 765 if test.endswith(".t"):
747 766 rename(testpath + ".err", testpath)
748 767 else:
749 768 rename(testpath + ".err", testpath + ".out")
750 769 result('p', test)
751 770 return
752 771 result('f', (test, msg))
753 772
754 773 def success():
755 774 result('p', test)
756 775
757 776 def ignore(msg):
758 777 result('i', (test, msg))
759 778
760 779 if (os.path.basename(test).startswith("test-") and '~' not in test and
761 780 ('.' not in test or test.endswith('.py') or
762 781 test.endswith('.bat') or test.endswith('.t'))):
763 782 if not os.path.exists(test):
764 783 skip("doesn't exist")
765 784 return None
766 785 else:
767 786 vlog('# Test file', test, 'not supported, ignoring')
768 787 return None # not a supported test, don't record
769 788
770 789 if not (options.whitelisted and test in options.whitelisted):
771 790 if options.blacklist and test in options.blacklist:
772 791 skip("blacklisted")
773 792 return None
774 793
775 794 if options.retest and not os.path.exists(test + ".err"):
776 795 ignore("not retesting")
777 796 return None
778 797
779 798 if options.keywords:
780 799 fp = open(test)
781 800 t = fp.read().lower() + test.lower()
782 801 fp.close()
783 802 for k in options.keywords.lower().split():
784 803 if k in t:
785 804 break
786 805 else:
787 806 ignore("doesn't match keyword")
788 807 return None
789 808
790 809 vlog("# Test", test)
791 810
792 811 # create a fresh hgrc
793 812 hgrc = open(HGRCPATH, 'w+')
794 813 hgrc.write('[ui]\n')
795 814 hgrc.write('slash = True\n')
796 815 hgrc.write('[defaults]\n')
797 816 hgrc.write('backout = -d "0 0"\n')
798 817 hgrc.write('commit = -d "0 0"\n')
799 818 hgrc.write('tag = -d "0 0"\n')
800 819 if options.inotify:
801 820 hgrc.write('[extensions]\n')
802 821 hgrc.write('inotify=\n')
803 822 hgrc.write('[inotify]\n')
804 823 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
805 824 hgrc.write('appendpid=True\n')
806 825 if options.extra_config_opt:
807 826 for opt in options.extra_config_opt:
808 827 section, key = opt.split('.', 1)
809 828 assert '=' in key, ('extra config opt %s must '
810 829 'have an = for assignment' % opt)
811 830 hgrc.write('[%s]\n%s\n' % (section, key))
812 831 hgrc.close()
813 832
814 833 ref = os.path.join(TESTDIR, test+".out")
815 834 err = os.path.join(TESTDIR, test+".err")
816 835 if os.path.exists(err):
817 836 os.remove(err) # Remove any previous output files
818 837 try:
819 838 tf = open(testpath)
820 839 firstline = tf.readline().rstrip()
821 840 tf.close()
822 841 except:
823 842 firstline = ''
824 843 lctest = test.lower()
825 844
826 845 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
827 846 runner = pytest
828 847 elif lctest.endswith('.t'):
829 848 runner = tsttest
830 849 ref = testpath
831 850 else:
832 851 # do not try to run non-executable programs
833 852 if not os.access(testpath, os.X_OK):
834 853 return skip("not executable")
835 854 runner = shtest
836 855
837 856 # Make a tmp subdirectory to work in
838 857 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
839 858 os.path.join(HGTMP, os.path.basename(test))
840 859
841 860 os.mkdir(testtmp)
842 861 ret, out = runner(testpath, testtmp, options, [
843 862 (re.escape(testtmp), '$TESTTMP'),
844 863 (r':%s\b' % options.port, ':$HGPORT'),
845 864 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
846 865 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
847 866 ])
848 867 vlog("# Ret was:", ret)
849 868
850 869 mark = '.'
851 870
852 871 skipped = (ret == SKIPPED_STATUS)
853 872
854 873 # If we're not in --debug mode and reference output file exists,
855 874 # check test output against it.
856 875 if options.debug:
857 876 refout = None # to match "out is None"
858 877 elif os.path.exists(ref):
859 878 f = open(ref, "r")
860 879 refout = list(splitnewlines(f.read()))
861 880 f.close()
862 881 else:
863 882 refout = []
864 883
865 884 if (ret != 0 or out != refout) and not skipped and not options.debug:
866 885 # Save errors to a file for diagnosis
867 886 f = open(err, "wb")
868 887 for line in out:
869 888 f.write(line)
870 889 f.close()
871 890
872 891 if skipped:
873 892 mark = 's'
874 893 if out is None: # debug mode: nothing to parse
875 894 missing = ['unknown']
876 895 failed = None
877 896 else:
878 897 missing, failed = parsehghaveoutput(out)
879 898 if not missing:
880 899 missing = ['irrelevant']
881 900 if failed:
882 901 fail("hghave failed checking for %s" % failed[-1], ret)
883 902 skipped = False
884 903 else:
885 904 skip(missing[-1])
886 905 elif ret == 'timeout':
887 906 mark = 't'
888 907 fail("timed out", ret)
889 908 elif out != refout:
890 909 mark = '!'
891 910 if not options.nodiff:
892 911 iolock.acquire()
893 912 if options.view:
894 913 os.system("%s %s %s" % (options.view, ref, err))
895 914 else:
896 915 showdiff(refout, out, ref, err)
897 916 iolock.release()
898 917 if ret:
899 918 fail("output changed and returned error code %d" % ret, ret)
900 919 else:
901 920 fail("output changed", ret)
902 921 ret = 1
903 922 elif ret:
904 923 mark = '!'
905 924 fail("returned error code %d" % ret, ret)
906 925 else:
907 926 success()
908 927
909 928 if not options.verbose:
910 929 iolock.acquire()
911 930 sys.stdout.write(mark)
912 931 sys.stdout.flush()
913 932 iolock.release()
914 933
915 934 killdaemons()
916 935
917 936 if not options.keep_tmpdir:
918 937 shutil.rmtree(testtmp, True)
919 938 if skipped:
920 939 return None
921 940 return ret == 0
922 941
923 942 _hgpath = None
924 943
925 944 def _gethgpath():
926 945 """Return the path to the mercurial package that is actually found by
927 946 the current Python interpreter."""
928 947 global _hgpath
929 948 if _hgpath is not None:
930 949 return _hgpath
931 950
932 951 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
933 952 pipe = os.popen(cmd % PYTHON)
934 953 try:
935 954 _hgpath = pipe.read().strip()
936 955 finally:
937 956 pipe.close()
938 957 return _hgpath
939 958
940 959 def _checkhglib(verb):
941 960 """Ensure that the 'mercurial' package imported by python is
942 961 the one we expect it to be. If not, print a warning to stderr."""
943 962 expecthg = os.path.join(PYTHONDIR, 'mercurial')
944 963 actualhg = _gethgpath()
945 964 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
946 965 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
947 966 ' (expected %s)\n'
948 967 % (verb, actualhg, expecthg))
949 968
950 969 def runchildren(options, tests):
951 970 if INST:
952 971 installhg(options)
953 972 _checkhglib("Testing")
954 973
955 974 optcopy = dict(options.__dict__)
956 975 optcopy['jobs'] = 1
957 976
958 977 # Because whitelist has to override keyword matches, we have to
959 978 # actually load the whitelist in the children as well, so we allow
960 979 # the list of whitelist files to pass through and be parsed in the
961 980 # children, but not the dict of whitelisted tests resulting from
962 981 # the parse, used here to override blacklisted tests.
963 982 whitelist = optcopy['whitelisted'] or []
964 983 del optcopy['whitelisted']
965 984
966 985 blacklist = optcopy['blacklist'] or []
967 986 del optcopy['blacklist']
968 987 blacklisted = []
969 988
970 989 if optcopy['with_hg'] is None:
971 990 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
972 991 optcopy.pop('anycoverage', None)
973 992
974 993 opts = []
975 994 for opt, value in optcopy.iteritems():
976 995 name = '--' + opt.replace('_', '-')
977 996 if value is True:
978 997 opts.append(name)
979 998 elif isinstance(value, list):
980 999 for v in value:
981 1000 opts.append(name + '=' + str(v))
982 1001 elif value is not None:
983 1002 opts.append(name + '=' + str(value))
984 1003
985 1004 tests.reverse()
986 1005 jobs = [[] for j in xrange(options.jobs)]
987 1006 while tests:
988 1007 for job in jobs:
989 1008 if not tests:
990 1009 break
991 1010 test = tests.pop()
992 1011 if test not in whitelist and test in blacklist:
993 1012 blacklisted.append(test)
994 1013 else:
995 1014 job.append(test)
996 1015 fps = {}
997 1016
998 1017 for j, job in enumerate(jobs):
999 1018 if not job:
1000 1019 continue
1001 1020 rfd, wfd = os.pipe()
1002 1021 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1003 1022 childtmp = os.path.join(HGTMP, 'child%d' % j)
1004 1023 childopts += ['--tmpdir', childtmp]
1005 1024 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1006 1025 vlog(' '.join(cmdline))
1007 1026 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1008 1027 os.close(wfd)
1009 1028 signal.signal(signal.SIGINT, signal.SIG_IGN)
1010 1029 failures = 0
1011 1030 tested, skipped, failed = 0, 0, 0
1012 1031 skips = []
1013 1032 fails = []
1014 1033 while fps:
1015 1034 pid, status = os.wait()
1016 1035 fp = fps.pop(pid)
1017 1036 l = fp.read().splitlines()
1018 1037 try:
1019 1038 test, skip, fail = map(int, l[:3])
1020 1039 except ValueError:
1021 1040 test, skip, fail = 0, 0, 0
1022 1041 split = -fail or len(l)
1023 1042 for s in l[3:split]:
1024 1043 skips.append(s.split(" ", 1))
1025 1044 for s in l[split:]:
1026 1045 fails.append(s.split(" ", 1))
1027 1046 tested += test
1028 1047 skipped += skip
1029 1048 failed += fail
1030 1049 vlog('pid %d exited, status %d' % (pid, status))
1031 1050 failures |= status
1032 1051 print
1033 1052 skipped += len(blacklisted)
1034 1053 if not options.noskips:
1035 1054 for s in skips:
1036 1055 print "Skipped %s: %s" % (s[0], s[1])
1037 1056 for s in blacklisted:
1038 1057 print "Skipped %s: blacklisted" % s
1039 1058 for s in fails:
1040 1059 print "Failed %s: %s" % (s[0], s[1])
1041 1060
1042 1061 _checkhglib("Tested")
1043 1062 print "# Ran %d tests, %d skipped, %d failed." % (
1044 1063 tested, skipped, failed)
1045 1064
1046 1065 if options.anycoverage:
1047 1066 outputcoverage(options)
1048 1067 sys.exit(failures != 0)
1049 1068
1050 1069 results = dict(p=[], f=[], s=[], i=[])
1051 1070 resultslock = threading.Lock()
1052 1071 iolock = threading.Lock()
1053 1072
1054 1073 def runqueue(options, tests, results):
1055 1074 for test in tests:
1056 1075 ret = runone(options, test)
1057 1076 if options.first and ret is not None and not ret:
1058 1077 break
1059 1078
1060 1079 def runtests(options, tests):
1061 1080 global DAEMON_PIDS, HGRCPATH
1062 1081 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1063 1082 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1064 1083
1065 1084 try:
1066 1085 if INST:
1067 1086 installhg(options)
1068 1087 _checkhglib("Testing")
1069 1088
1070 1089 if options.restart:
1071 1090 orig = list(tests)
1072 1091 while tests:
1073 1092 if os.path.exists(tests[0] + ".err"):
1074 1093 break
1075 1094 tests.pop(0)
1076 1095 if not tests:
1077 1096 print "running all tests"
1078 1097 tests = orig
1079 1098
1080 1099 runqueue(options, tests, results)
1081 1100
1082 1101 failed = len(results['f'])
1083 1102 tested = len(results['p']) + failed
1084 1103 skipped = len(results['s'])
1085 1104 ignored = len(results['i'])
1086 1105
1087 1106 if options.child:
1088 1107 fp = os.fdopen(options.child, 'w')
1089 1108 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1090 1109 for s in results['s']:
1091 1110 fp.write("%s %s\n" % s)
1092 1111 for s in results['f']:
1093 1112 fp.write("%s %s\n" % s)
1094 1113 fp.close()
1095 1114 else:
1096 1115 print
1097 1116 for s in results['s']:
1098 1117 print "Skipped %s: %s" % s
1099 1118 for s in results['f']:
1100 1119 print "Failed %s: %s" % s
1101 1120 _checkhglib("Tested")
1102 1121 print "# Ran %d tests, %d skipped, %d failed." % (
1103 1122 tested, skipped + ignored, failed)
1104 1123
1105 1124 if options.anycoverage:
1106 1125 outputcoverage(options)
1107 1126 except KeyboardInterrupt:
1108 1127 failed = True
1109 1128 print "\ninterrupted!"
1110 1129
1111 1130 if failed:
1112 1131 sys.exit(1)
1113 1132
1114 1133 def main():
1115 1134 (options, args) = parseargs()
1116 1135 if not options.child:
1117 1136 os.umask(022)
1118 1137
1119 1138 checktools()
1120 1139
1121 1140 if len(args) == 0:
1122 1141 args = os.listdir(".")
1123 1142 args.sort()
1124 1143
1125 1144 tests = args
1126 1145
1127 1146 # Reset some environment variables to well-known values so that
1128 1147 # the tests produce repeatable output.
1129 1148 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1130 1149 os.environ['TZ'] = 'GMT'
1131 1150 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1132 1151 os.environ['CDPATH'] = ''
1133 1152 os.environ['COLUMNS'] = '80'
1134 1153 os.environ['GREP_OPTIONS'] = ''
1135 1154 os.environ['http_proxy'] = ''
1136 1155 os.environ['no_proxy'] = ''
1137 1156 os.environ['NO_PROXY'] = ''
1138 1157
1139 1158 # unset env related to hooks
1140 1159 for k in os.environ.keys():
1141 1160 if k.startswith('HG_'):
1142 1161 # can't remove on solaris
1143 1162 os.environ[k] = ''
1144 1163 del os.environ[k]
1145 1164
1146 1165 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1147 1166 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1148 1167 if options.tmpdir:
1149 1168 options.keep_tmpdir = True
1150 1169 tmpdir = options.tmpdir
1151 1170 if os.path.exists(tmpdir):
1152 1171 # Meaning of tmpdir has changed since 1.3: we used to create
1153 1172 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1154 1173 # tmpdir already exists.
1155 1174 sys.exit("error: temp dir %r already exists" % tmpdir)
1156 1175
1157 1176 # Automatically removing tmpdir sounds convenient, but could
1158 1177 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1159 1178 # or "--tmpdir=$HOME".
1160 1179 #vlog("# Removing temp dir", tmpdir)
1161 1180 #shutil.rmtree(tmpdir)
1162 1181 os.makedirs(tmpdir)
1163 1182 else:
1164 1183 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1165 1184 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1166 1185 DAEMON_PIDS = None
1167 1186 HGRCPATH = None
1168 1187
1169 1188 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1170 1189 os.environ["HGMERGE"] = "internal:merge"
1171 1190 os.environ["HGUSER"] = "test"
1172 1191 os.environ["HGENCODING"] = "ascii"
1173 1192 os.environ["HGENCODINGMODE"] = "strict"
1174 1193 os.environ["HGPORT"] = str(options.port)
1175 1194 os.environ["HGPORT1"] = str(options.port + 1)
1176 1195 os.environ["HGPORT2"] = str(options.port + 2)
1177 1196
1178 1197 if options.with_hg:
1179 1198 INST = None
1180 1199 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1181 1200
1182 1201 # This looks redundant with how Python initializes sys.path from
1183 1202 # the location of the script being executed. Needed because the
1184 1203 # "hg" specified by --with-hg is not the only Python script
1185 1204 # executed in the test suite that needs to import 'mercurial'
1186 1205 # ... which means it's not really redundant at all.
1187 1206 PYTHONDIR = BINDIR
1188 1207 else:
1189 1208 INST = os.path.join(HGTMP, "install")
1190 1209 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1191 1210 PYTHONDIR = os.path.join(INST, "lib", "python")
1192 1211
1193 1212 os.environ["BINDIR"] = BINDIR
1194 1213 os.environ["PYTHON"] = PYTHON
1195 1214
1196 1215 if not options.child:
1197 1216 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1198 1217 os.environ["PATH"] = os.pathsep.join(path)
1199 1218
1200 1219 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1201 1220 # can run .../tests/run-tests.py test-foo where test-foo
1202 1221 # adds an extension to HGRC
1203 1222 pypath = [PYTHONDIR, TESTDIR]
1204 1223 # We have to augment PYTHONPATH, rather than simply replacing
1205 1224 # it, in case external libraries are only available via current
1206 1225 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1207 1226 # are in /opt/subversion.)
1208 1227 oldpypath = os.environ.get(IMPL_PATH)
1209 1228 if oldpypath:
1210 1229 pypath.append(oldpypath)
1211 1230 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1212 1231
1213 1232 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1214 1233
1215 1234 vlog("# Using TESTDIR", TESTDIR)
1216 1235 vlog("# Using HGTMP", HGTMP)
1217 1236 vlog("# Using PATH", os.environ["PATH"])
1218 1237 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1219 1238
1220 1239 try:
1221 1240 if len(tests) > 1 and options.jobs > 1:
1222 1241 runchildren(options, tests)
1223 1242 else:
1224 1243 runtests(options, tests)
1225 1244 finally:
1226 1245 time.sleep(1)
1227 1246 cleanup(options)
1228 1247
1229 1248 if __name__ == '__main__':
1230 1249 main()
General Comments 0
You need to be logged in to leave comments. Login now