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