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