##// END OF EJS Templates
run-tests: pull out unified matching funcs
Matt Mackall -
r15414:2a62d7c8 default
parent child Browse files
Show More
@@ -1,1249 +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 def rematch(el, l):
525 try:
526 # ensure that the regex matches to the end of the string
527 return re.match(el + r'\Z', l)
528 except re.error:
529 # el is an invalid regex
530 return False
531
532 def globmatch(el, l):
533 # The only supported special characters are * and ?. Escaping is
534 # supported.
535 i, n = 0, len(el)
536 res = ''
537 while i < n:
538 c = el[i]
539 i += 1
540 if c == '\\' and el[i] in '*?\\':
541 res += el[i - 1:i + 1]
542 i += 1
543 elif c == '*':
544 res += '.*'
545 elif c == '?':
546 res += '.'
547 else:
548 res += re.escape(c)
549 return rematch(res, l)
550
524 551 def tsttest(test, wd, options, replacements):
525 552 t = open(test)
526 553 out = []
527 554 script = []
528 555
529 556 # We generate a shell script which outputs unique markers to line
530 557 # up script results with our source. These markers include input
531 558 # line number and the last return code
532 559 salt = "SALT" + str(time.time())
533 560 def addsalt(line):
534 561 script.append('echo %s %s $?\n' % (salt, line))
535 562
536 563 # After we run the shell script, we re-unify the script output
537 564 # with non-active parts of the source, with synchronization by our
538 565 # SALT line number markers. The after table contains the
539 566 # non-active components, ordered by line number
540 567 after = {}
541 568 pos = prepos = -1
542 569
543 570 # Expected shellscript output
544 571 expected = {}
545 572
546 573 # We keep track of whether or not we're in a Python block so we
547 574 # can generate the surrounding doctest magic
548 575 inpython = False
549 576
550 577 for n, l in enumerate(t):
551 578 if not l.endswith('\n'):
552 579 l += '\n'
553 580 if l.startswith(' >>> '): # python inlines
554 581 if not inpython:
555 582 # we've just entered a Python block, add the header
556 583 inpython = True
557 584 addsalt(n)
558 585 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
559 586 prepos = pos
560 587 pos = n
561 588 after.setdefault(prepos, []).append(l)
562 589 script.append(l[2:])
563 590 elif l.startswith(' $ '): # commands
564 591 if inpython:
565 592 script.append("EOF\n")
566 593 inpython = False
567 594 after.setdefault(pos, []).append(l)
568 595 prepos = pos
569 596 pos = n
570 597 addsalt(n)
571 598 script.append(l[4:])
572 599 elif l.startswith(' > '): # continuations
573 600 after.setdefault(prepos, []).append(l)
574 601 script.append(l[4:])
575 602 elif l.startswith(' '): # results
576 603 if inpython:
577 604 script.append(l[2:])
578 605 after.setdefault(prepos, []).append(l)
579 606 else:
580 607 # queue up a list of expected results
581 608 expected.setdefault(pos, []).append(l[2:])
582 609 else:
583 610 if inpython:
584 611 script.append("EOF\n")
585 612 inpython = False
586 613 # non-command/result - queue up for merged output
587 614 after.setdefault(pos, []).append(l)
588 615
589 616 t.close()
590 617
591 618 if inpython:
592 619 script.append("EOF\n")
593 620 addsalt(n + 1)
594 621
595 622 fd, name = tempfile.mkstemp(suffix='hg-tst')
596 623 try:
597 624 for l in script:
598 625 os.write(fd, l)
599 626 os.close(fd)
600 627
601 628 cmd = '"%s" "%s"' % (options.shell, name)
602 629 vlog("# Running", cmd)
603 630 exitcode, output = run(cmd, wd, options, replacements)
604 631 # do not merge output if skipped, return hghave message instead
605 632 # similarly, with --debug, output is None
606 633 if exitcode == SKIPPED_STATUS or output is None:
607 634 return exitcode, output
608 635 finally:
609 636 os.remove(name)
610 637
611 def rematch(el, l):
612 try:
613 # ensure that the regex matches to the end of the string
614 return re.match(el + r'\Z', l)
615 except re.error:
616 # el is an invalid regex
617 return False
618
619 def globmatch(el, l):
620 # The only supported special characters are * and ?. Escaping is
621 # supported.
622 i, n = 0, len(el)
623 res = ''
624 while i < n:
625 c = el[i]
626 i += 1
627 if c == '\\' and el[i] in '*?\\':
628 res += el[i - 1:i + 1]
629 i += 1
630 elif c == '*':
631 res += '.*'
632 elif c == '?':
633 res += '.'
634 else:
635 res += re.escape(c)
636 return rematch(res, l)
637
638 638 # Merge the script output back into a unified test
639 639
640 640 pos = -1
641 641 postout = []
642 642 ret = 0
643 643 for n, l in enumerate(output):
644 644 lout, lcmd = l, None
645 645 if salt in l:
646 646 lout, lcmd = l.split(salt, 1)
647 647
648 648 if lout:
649 649 if lcmd:
650 650 # output block had no trailing newline, clean up
651 651 lout += ' (no-eol)\n'
652 652
653 653 # find the expected output at the current position
654 654 el = None
655 655 if pos in expected and expected[pos]:
656 656 el = expected[pos].pop(0)
657 657
658 658 if el == lout: # perfect match (fast)
659 659 postout.append(" " + lout)
660 660 elif (el and
661 661 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
662 662 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
663 663 or el.endswith(" (esc)\n") and
664 664 el.decode('string-escape') == l)):
665 665 postout.append(" " + el) # fallback regex/glob/esc match
666 666 else:
667 667 if needescape(lout):
668 668 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
669 669 postout.append(" " + lout) # let diff deal with it
670 670
671 671 if lcmd:
672 672 # add on last return code
673 673 ret = int(lcmd.split()[1])
674 674 if ret != 0:
675 675 postout.append(" [%s]\n" % ret)
676 676 if pos in after:
677 677 # merge in non-active test bits
678 678 postout += after.pop(pos)
679 679 pos = int(lcmd.split()[0])
680 680
681 681 if pos in after:
682 682 postout += after.pop(pos)
683 683
684 684 return exitcode, postout
685 685
686 686 wifexited = getattr(os, "WIFEXITED", lambda x: False)
687 687 def run(cmd, wd, options, replacements):
688 688 """Run command in a sub-process, capturing the output (stdout and stderr).
689 689 Return a tuple (exitcode, output). output is None in debug mode."""
690 690 # TODO: Use subprocess.Popen if we're running on Python 2.4
691 691 if options.debug:
692 692 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
693 693 ret = proc.wait()
694 694 return (ret, None)
695 695
696 696 proc = Popen4(cmd, wd, options.timeout)
697 697 def cleanup():
698 698 terminate(proc)
699 699 ret = proc.wait()
700 700 if ret == 0:
701 701 ret = signal.SIGTERM << 8
702 702 killdaemons()
703 703 return ret
704 704
705 705 output = ''
706 706 proc.tochild.close()
707 707
708 708 try:
709 709 output = proc.fromchild.read()
710 710 except KeyboardInterrupt:
711 711 vlog('# Handling keyboard interrupt')
712 712 cleanup()
713 713 raise
714 714
715 715 ret = proc.wait()
716 716 if wifexited(ret):
717 717 ret = os.WEXITSTATUS(ret)
718 718
719 719 if proc.timeout:
720 720 ret = 'timeout'
721 721
722 722 if ret:
723 723 killdaemons()
724 724
725 725 for s, r in replacements:
726 726 output = re.sub(s, r, output)
727 727 return ret, splitnewlines(output)
728 728
729 729 def runone(options, test):
730 730 '''tristate output:
731 731 None -> skipped
732 732 True -> passed
733 733 False -> failed'''
734 734
735 735 global results, resultslock, iolock
736 736
737 737 testpath = os.path.join(TESTDIR, test)
738 738
739 739 def result(l, e):
740 740 resultslock.acquire()
741 741 results[l].append(e)
742 742 resultslock.release()
743 743
744 744 def skip(msg):
745 745 if not options.verbose:
746 746 result('s', (test, msg))
747 747 else:
748 748 iolock.acquire()
749 749 print "\nSkipping %s: %s" % (testpath, msg)
750 750 iolock.release()
751 751 return None
752 752
753 753 def fail(msg, ret):
754 754 if not options.nodiff:
755 755 iolock.acquire()
756 756 print "\nERROR: %s %s" % (testpath, msg)
757 757 iolock.release()
758 758 if (not ret and options.interactive
759 759 and os.path.exists(testpath + ".err")):
760 760 iolock.acquire()
761 761 print "Accept this change? [n] ",
762 762 answer = sys.stdin.readline().strip()
763 763 iolock.release()
764 764 if answer.lower() in "y yes".split():
765 765 if test.endswith(".t"):
766 766 rename(testpath + ".err", testpath)
767 767 else:
768 768 rename(testpath + ".err", testpath + ".out")
769 769 result('p', test)
770 770 return
771 771 result('f', (test, msg))
772 772
773 773 def success():
774 774 result('p', test)
775 775
776 776 def ignore(msg):
777 777 result('i', (test, msg))
778 778
779 779 if (os.path.basename(test).startswith("test-") and '~' not in test and
780 780 ('.' not in test or test.endswith('.py') or
781 781 test.endswith('.bat') or test.endswith('.t'))):
782 782 if not os.path.exists(test):
783 783 skip("doesn't exist")
784 784 return None
785 785 else:
786 786 vlog('# Test file', test, 'not supported, ignoring')
787 787 return None # not a supported test, don't record
788 788
789 789 if not (options.whitelisted and test in options.whitelisted):
790 790 if options.blacklist and test in options.blacklist:
791 791 skip("blacklisted")
792 792 return None
793 793
794 794 if options.retest and not os.path.exists(test + ".err"):
795 795 ignore("not retesting")
796 796 return None
797 797
798 798 if options.keywords:
799 799 fp = open(test)
800 800 t = fp.read().lower() + test.lower()
801 801 fp.close()
802 802 for k in options.keywords.lower().split():
803 803 if k in t:
804 804 break
805 805 else:
806 806 ignore("doesn't match keyword")
807 807 return None
808 808
809 809 vlog("# Test", test)
810 810
811 811 # create a fresh hgrc
812 812 hgrc = open(HGRCPATH, 'w+')
813 813 hgrc.write('[ui]\n')
814 814 hgrc.write('slash = True\n')
815 815 hgrc.write('[defaults]\n')
816 816 hgrc.write('backout = -d "0 0"\n')
817 817 hgrc.write('commit = -d "0 0"\n')
818 818 hgrc.write('tag = -d "0 0"\n')
819 819 if options.inotify:
820 820 hgrc.write('[extensions]\n')
821 821 hgrc.write('inotify=\n')
822 822 hgrc.write('[inotify]\n')
823 823 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
824 824 hgrc.write('appendpid=True\n')
825 825 if options.extra_config_opt:
826 826 for opt in options.extra_config_opt:
827 827 section, key = opt.split('.', 1)
828 828 assert '=' in key, ('extra config opt %s must '
829 829 'have an = for assignment' % opt)
830 830 hgrc.write('[%s]\n%s\n' % (section, key))
831 831 hgrc.close()
832 832
833 833 ref = os.path.join(TESTDIR, test+".out")
834 834 err = os.path.join(TESTDIR, test+".err")
835 835 if os.path.exists(err):
836 836 os.remove(err) # Remove any previous output files
837 837 try:
838 838 tf = open(testpath)
839 839 firstline = tf.readline().rstrip()
840 840 tf.close()
841 841 except:
842 842 firstline = ''
843 843 lctest = test.lower()
844 844
845 845 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
846 846 runner = pytest
847 847 elif lctest.endswith('.t'):
848 848 runner = tsttest
849 849 ref = testpath
850 850 else:
851 851 # do not try to run non-executable programs
852 852 if not os.access(testpath, os.X_OK):
853 853 return skip("not executable")
854 854 runner = shtest
855 855
856 856 # Make a tmp subdirectory to work in
857 857 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
858 858 os.path.join(HGTMP, os.path.basename(test))
859 859
860 860 os.mkdir(testtmp)
861 861 ret, out = runner(testpath, testtmp, options, [
862 862 (re.escape(testtmp), '$TESTTMP'),
863 863 (r':%s\b' % options.port, ':$HGPORT'),
864 864 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
865 865 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
866 866 ])
867 867 vlog("# Ret was:", ret)
868 868
869 869 mark = '.'
870 870
871 871 skipped = (ret == SKIPPED_STATUS)
872 872
873 873 # If we're not in --debug mode and reference output file exists,
874 874 # check test output against it.
875 875 if options.debug:
876 876 refout = None # to match "out is None"
877 877 elif os.path.exists(ref):
878 878 f = open(ref, "r")
879 879 refout = list(splitnewlines(f.read()))
880 880 f.close()
881 881 else:
882 882 refout = []
883 883
884 884 if (ret != 0 or out != refout) and not skipped and not options.debug:
885 885 # Save errors to a file for diagnosis
886 886 f = open(err, "wb")
887 887 for line in out:
888 888 f.write(line)
889 889 f.close()
890 890
891 891 if skipped:
892 892 mark = 's'
893 893 if out is None: # debug mode: nothing to parse
894 894 missing = ['unknown']
895 895 failed = None
896 896 else:
897 897 missing, failed = parsehghaveoutput(out)
898 898 if not missing:
899 899 missing = ['irrelevant']
900 900 if failed:
901 901 fail("hghave failed checking for %s" % failed[-1], ret)
902 902 skipped = False
903 903 else:
904 904 skip(missing[-1])
905 905 elif ret == 'timeout':
906 906 mark = 't'
907 907 fail("timed out", ret)
908 908 elif out != refout:
909 909 mark = '!'
910 910 if not options.nodiff:
911 911 iolock.acquire()
912 912 if options.view:
913 913 os.system("%s %s %s" % (options.view, ref, err))
914 914 else:
915 915 showdiff(refout, out, ref, err)
916 916 iolock.release()
917 917 if ret:
918 918 fail("output changed and returned error code %d" % ret, ret)
919 919 else:
920 920 fail("output changed", ret)
921 921 ret = 1
922 922 elif ret:
923 923 mark = '!'
924 924 fail("returned error code %d" % ret, ret)
925 925 else:
926 926 success()
927 927
928 928 if not options.verbose:
929 929 iolock.acquire()
930 930 sys.stdout.write(mark)
931 931 sys.stdout.flush()
932 932 iolock.release()
933 933
934 934 killdaemons()
935 935
936 936 if not options.keep_tmpdir:
937 937 shutil.rmtree(testtmp, True)
938 938 if skipped:
939 939 return None
940 940 return ret == 0
941 941
942 942 _hgpath = None
943 943
944 944 def _gethgpath():
945 945 """Return the path to the mercurial package that is actually found by
946 946 the current Python interpreter."""
947 947 global _hgpath
948 948 if _hgpath is not None:
949 949 return _hgpath
950 950
951 951 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
952 952 pipe = os.popen(cmd % PYTHON)
953 953 try:
954 954 _hgpath = pipe.read().strip()
955 955 finally:
956 956 pipe.close()
957 957 return _hgpath
958 958
959 959 def _checkhglib(verb):
960 960 """Ensure that the 'mercurial' package imported by python is
961 961 the one we expect it to be. If not, print a warning to stderr."""
962 962 expecthg = os.path.join(PYTHONDIR, 'mercurial')
963 963 actualhg = _gethgpath()
964 964 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
965 965 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
966 966 ' (expected %s)\n'
967 967 % (verb, actualhg, expecthg))
968 968
969 969 def runchildren(options, tests):
970 970 if INST:
971 971 installhg(options)
972 972 _checkhglib("Testing")
973 973
974 974 optcopy = dict(options.__dict__)
975 975 optcopy['jobs'] = 1
976 976
977 977 # Because whitelist has to override keyword matches, we have to
978 978 # actually load the whitelist in the children as well, so we allow
979 979 # the list of whitelist files to pass through and be parsed in the
980 980 # children, but not the dict of whitelisted tests resulting from
981 981 # the parse, used here to override blacklisted tests.
982 982 whitelist = optcopy['whitelisted'] or []
983 983 del optcopy['whitelisted']
984 984
985 985 blacklist = optcopy['blacklist'] or []
986 986 del optcopy['blacklist']
987 987 blacklisted = []
988 988
989 989 if optcopy['with_hg'] is None:
990 990 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
991 991 optcopy.pop('anycoverage', None)
992 992
993 993 opts = []
994 994 for opt, value in optcopy.iteritems():
995 995 name = '--' + opt.replace('_', '-')
996 996 if value is True:
997 997 opts.append(name)
998 998 elif isinstance(value, list):
999 999 for v in value:
1000 1000 opts.append(name + '=' + str(v))
1001 1001 elif value is not None:
1002 1002 opts.append(name + '=' + str(value))
1003 1003
1004 1004 tests.reverse()
1005 1005 jobs = [[] for j in xrange(options.jobs)]
1006 1006 while tests:
1007 1007 for job in jobs:
1008 1008 if not tests:
1009 1009 break
1010 1010 test = tests.pop()
1011 1011 if test not in whitelist and test in blacklist:
1012 1012 blacklisted.append(test)
1013 1013 else:
1014 1014 job.append(test)
1015 1015 fps = {}
1016 1016
1017 1017 for j, job in enumerate(jobs):
1018 1018 if not job:
1019 1019 continue
1020 1020 rfd, wfd = os.pipe()
1021 1021 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1022 1022 childtmp = os.path.join(HGTMP, 'child%d' % j)
1023 1023 childopts += ['--tmpdir', childtmp]
1024 1024 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1025 1025 vlog(' '.join(cmdline))
1026 1026 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1027 1027 os.close(wfd)
1028 1028 signal.signal(signal.SIGINT, signal.SIG_IGN)
1029 1029 failures = 0
1030 1030 tested, skipped, failed = 0, 0, 0
1031 1031 skips = []
1032 1032 fails = []
1033 1033 while fps:
1034 1034 pid, status = os.wait()
1035 1035 fp = fps.pop(pid)
1036 1036 l = fp.read().splitlines()
1037 1037 try:
1038 1038 test, skip, fail = map(int, l[:3])
1039 1039 except ValueError:
1040 1040 test, skip, fail = 0, 0, 0
1041 1041 split = -fail or len(l)
1042 1042 for s in l[3:split]:
1043 1043 skips.append(s.split(" ", 1))
1044 1044 for s in l[split:]:
1045 1045 fails.append(s.split(" ", 1))
1046 1046 tested += test
1047 1047 skipped += skip
1048 1048 failed += fail
1049 1049 vlog('pid %d exited, status %d' % (pid, status))
1050 1050 failures |= status
1051 1051 print
1052 1052 skipped += len(blacklisted)
1053 1053 if not options.noskips:
1054 1054 for s in skips:
1055 1055 print "Skipped %s: %s" % (s[0], s[1])
1056 1056 for s in blacklisted:
1057 1057 print "Skipped %s: blacklisted" % s
1058 1058 for s in fails:
1059 1059 print "Failed %s: %s" % (s[0], s[1])
1060 1060
1061 1061 _checkhglib("Tested")
1062 1062 print "# Ran %d tests, %d skipped, %d failed." % (
1063 1063 tested, skipped, failed)
1064 1064
1065 1065 if options.anycoverage:
1066 1066 outputcoverage(options)
1067 1067 sys.exit(failures != 0)
1068 1068
1069 1069 results = dict(p=[], f=[], s=[], i=[])
1070 1070 resultslock = threading.Lock()
1071 1071 iolock = threading.Lock()
1072 1072
1073 1073 def runqueue(options, tests, results):
1074 1074 for test in tests:
1075 1075 ret = runone(options, test)
1076 1076 if options.first and ret is not None and not ret:
1077 1077 break
1078 1078
1079 1079 def runtests(options, tests):
1080 1080 global DAEMON_PIDS, HGRCPATH
1081 1081 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1082 1082 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1083 1083
1084 1084 try:
1085 1085 if INST:
1086 1086 installhg(options)
1087 1087 _checkhglib("Testing")
1088 1088
1089 1089 if options.restart:
1090 1090 orig = list(tests)
1091 1091 while tests:
1092 1092 if os.path.exists(tests[0] + ".err"):
1093 1093 break
1094 1094 tests.pop(0)
1095 1095 if not tests:
1096 1096 print "running all tests"
1097 1097 tests = orig
1098 1098
1099 1099 runqueue(options, tests, results)
1100 1100
1101 1101 failed = len(results['f'])
1102 1102 tested = len(results['p']) + failed
1103 1103 skipped = len(results['s'])
1104 1104 ignored = len(results['i'])
1105 1105
1106 1106 if options.child:
1107 1107 fp = os.fdopen(options.child, 'w')
1108 1108 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1109 1109 for s in results['s']:
1110 1110 fp.write("%s %s\n" % s)
1111 1111 for s in results['f']:
1112 1112 fp.write("%s %s\n" % s)
1113 1113 fp.close()
1114 1114 else:
1115 1115 print
1116 1116 for s in results['s']:
1117 1117 print "Skipped %s: %s" % s
1118 1118 for s in results['f']:
1119 1119 print "Failed %s: %s" % s
1120 1120 _checkhglib("Tested")
1121 1121 print "# Ran %d tests, %d skipped, %d failed." % (
1122 1122 tested, skipped + ignored, failed)
1123 1123
1124 1124 if options.anycoverage:
1125 1125 outputcoverage(options)
1126 1126 except KeyboardInterrupt:
1127 1127 failed = True
1128 1128 print "\ninterrupted!"
1129 1129
1130 1130 if failed:
1131 1131 sys.exit(1)
1132 1132
1133 1133 def main():
1134 1134 (options, args) = parseargs()
1135 1135 if not options.child:
1136 1136 os.umask(022)
1137 1137
1138 1138 checktools()
1139 1139
1140 1140 if len(args) == 0:
1141 1141 args = os.listdir(".")
1142 1142 args.sort()
1143 1143
1144 1144 tests = args
1145 1145
1146 1146 # Reset some environment variables to well-known values so that
1147 1147 # the tests produce repeatable output.
1148 1148 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1149 1149 os.environ['TZ'] = 'GMT'
1150 1150 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1151 1151 os.environ['CDPATH'] = ''
1152 1152 os.environ['COLUMNS'] = '80'
1153 1153 os.environ['GREP_OPTIONS'] = ''
1154 1154 os.environ['http_proxy'] = ''
1155 1155 os.environ['no_proxy'] = ''
1156 1156 os.environ['NO_PROXY'] = ''
1157 1157
1158 1158 # unset env related to hooks
1159 1159 for k in os.environ.keys():
1160 1160 if k.startswith('HG_'):
1161 1161 # can't remove on solaris
1162 1162 os.environ[k] = ''
1163 1163 del os.environ[k]
1164 1164
1165 1165 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1166 1166 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1167 1167 if options.tmpdir:
1168 1168 options.keep_tmpdir = True
1169 1169 tmpdir = options.tmpdir
1170 1170 if os.path.exists(tmpdir):
1171 1171 # Meaning of tmpdir has changed since 1.3: we used to create
1172 1172 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1173 1173 # tmpdir already exists.
1174 1174 sys.exit("error: temp dir %r already exists" % tmpdir)
1175 1175
1176 1176 # Automatically removing tmpdir sounds convenient, but could
1177 1177 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1178 1178 # or "--tmpdir=$HOME".
1179 1179 #vlog("# Removing temp dir", tmpdir)
1180 1180 #shutil.rmtree(tmpdir)
1181 1181 os.makedirs(tmpdir)
1182 1182 else:
1183 1183 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1184 1184 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1185 1185 DAEMON_PIDS = None
1186 1186 HGRCPATH = None
1187 1187
1188 1188 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1189 1189 os.environ["HGMERGE"] = "internal:merge"
1190 1190 os.environ["HGUSER"] = "test"
1191 1191 os.environ["HGENCODING"] = "ascii"
1192 1192 os.environ["HGENCODINGMODE"] = "strict"
1193 1193 os.environ["HGPORT"] = str(options.port)
1194 1194 os.environ["HGPORT1"] = str(options.port + 1)
1195 1195 os.environ["HGPORT2"] = str(options.port + 2)
1196 1196
1197 1197 if options.with_hg:
1198 1198 INST = None
1199 1199 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1200 1200
1201 1201 # This looks redundant with how Python initializes sys.path from
1202 1202 # the location of the script being executed. Needed because the
1203 1203 # "hg" specified by --with-hg is not the only Python script
1204 1204 # executed in the test suite that needs to import 'mercurial'
1205 1205 # ... which means it's not really redundant at all.
1206 1206 PYTHONDIR = BINDIR
1207 1207 else:
1208 1208 INST = os.path.join(HGTMP, "install")
1209 1209 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1210 1210 PYTHONDIR = os.path.join(INST, "lib", "python")
1211 1211
1212 1212 os.environ["BINDIR"] = BINDIR
1213 1213 os.environ["PYTHON"] = PYTHON
1214 1214
1215 1215 if not options.child:
1216 1216 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1217 1217 os.environ["PATH"] = os.pathsep.join(path)
1218 1218
1219 1219 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1220 1220 # can run .../tests/run-tests.py test-foo where test-foo
1221 1221 # adds an extension to HGRC
1222 1222 pypath = [PYTHONDIR, TESTDIR]
1223 1223 # We have to augment PYTHONPATH, rather than simply replacing
1224 1224 # it, in case external libraries are only available via current
1225 1225 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1226 1226 # are in /opt/subversion.)
1227 1227 oldpypath = os.environ.get(IMPL_PATH)
1228 1228 if oldpypath:
1229 1229 pypath.append(oldpypath)
1230 1230 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1231 1231
1232 1232 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1233 1233
1234 1234 vlog("# Using TESTDIR", TESTDIR)
1235 1235 vlog("# Using HGTMP", HGTMP)
1236 1236 vlog("# Using PATH", os.environ["PATH"])
1237 1237 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1238 1238
1239 1239 try:
1240 1240 if len(tests) > 1 and options.jobs > 1:
1241 1241 runchildren(options, tests)
1242 1242 else:
1243 1243 runtests(options, tests)
1244 1244 finally:
1245 1245 time.sleep(1)
1246 1246 cleanup(options)
1247 1247
1248 1248 if __name__ == '__main__':
1249 1249 main()
General Comments 0
You need to be logged in to leave comments. Login now