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