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