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