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