##// END OF EJS Templates
run-tests: make build command line less intimidating...
Bryan O'Sullivan -
r17965:0167da3c default
parent child Browse files
Show More
@@ -1,1316 +1,1318 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 cmd = ('%s setup.py %s clean --all'
388 ' build --build-base="%s"'
389 ' install --force --prefix="%s" --install-lib="%s"'
390 ' --install-scripts="%s" %s >%s 2>&1'
391 % (sys.executable, pure, os.path.join(HGTMP, "build"),
392 INST, PYTHONDIR, BINDIR, nohome, installerrs))
387 cmd = ('%(exe)s setup.py %(pure)s clean --all'
388 ' build --build-base="%(base)s"'
389 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
390 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
391 % dict(exe=sys.executable, pure=pure,
392 base=os.path.join(HGTMP, "build"),
393 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
394 nohome=nohome, logfile=installerrs))
393 395 vlog("# Running", cmd)
394 396 if os.system(cmd) == 0:
395 397 if not options.verbose:
396 398 os.remove(installerrs)
397 399 else:
398 400 f = open(installerrs)
399 401 for line in f:
400 402 print line,
401 403 f.close()
402 404 sys.exit(1)
403 405 os.chdir(TESTDIR)
404 406
405 407 usecorrectpython()
406 408
407 409 vlog("# Installing dummy diffstat")
408 410 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
409 411 f.write('#!' + sys.executable + '\n'
410 412 'import sys\n'
411 413 'files = 0\n'
412 414 'for line in sys.stdin:\n'
413 415 ' if line.startswith("diff "):\n'
414 416 ' files += 1\n'
415 417 'sys.stdout.write("files patched: %d\\n" % files)\n')
416 418 f.close()
417 419 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
418 420
419 421 if options.py3k_warnings and not options.anycoverage:
420 422 vlog("# Updating hg command to enable Py3k Warnings switch")
421 423 f = open(os.path.join(BINDIR, 'hg'), 'r')
422 424 lines = [line.rstrip() for line in f]
423 425 lines[0] += ' -3'
424 426 f.close()
425 427 f = open(os.path.join(BINDIR, 'hg'), 'w')
426 428 for line in lines:
427 429 f.write(line + '\n')
428 430 f.close()
429 431
430 432 hgbat = os.path.join(BINDIR, 'hg.bat')
431 433 if os.path.isfile(hgbat):
432 434 # hg.bat expects to be put in bin/scripts while run-tests.py
433 435 # installation layout put it in bin/ directly. Fix it
434 436 f = open(hgbat, 'rb')
435 437 data = f.read()
436 438 f.close()
437 439 if '"%~dp0..\python" "%~dp0hg" %*' in data:
438 440 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
439 441 '"%~dp0python" "%~dp0hg" %*')
440 442 f = open(hgbat, 'wb')
441 443 f.write(data)
442 444 f.close()
443 445 else:
444 446 print 'WARNING: cannot fix hg.bat reference to python.exe'
445 447
446 448 if options.anycoverage:
447 449 custom = os.path.join(TESTDIR, 'sitecustomize.py')
448 450 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
449 451 vlog('# Installing coverage trigger to %s' % target)
450 452 shutil.copyfile(custom, target)
451 453 rc = os.path.join(TESTDIR, '.coveragerc')
452 454 vlog('# Installing coverage rc to %s' % rc)
453 455 os.environ['COVERAGE_PROCESS_START'] = rc
454 456 fn = os.path.join(INST, '..', '.coverage')
455 457 os.environ['COVERAGE_FILE'] = fn
456 458
457 459 def outputtimes(options):
458 460 vlog('# Producing time report')
459 461 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
460 462 cols = '%7.3f %s'
461 463 print '\n%-7s %s' % ('Time', 'Test')
462 464 for test, timetaken in times:
463 465 print cols % (timetaken, test)
464 466
465 467 def outputcoverage(options):
466 468
467 469 vlog('# Producing coverage report')
468 470 os.chdir(PYTHONDIR)
469 471
470 472 def covrun(*args):
471 473 cmd = 'coverage %s' % ' '.join(args)
472 474 vlog('# Running: %s' % cmd)
473 475 os.system(cmd)
474 476
475 477 if options.child:
476 478 return
477 479
478 480 covrun('-c')
479 481 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
480 482 covrun('-i', '-r', '"--omit=%s"' % omit) # report
481 483 if options.htmlcov:
482 484 htmldir = os.path.join(TESTDIR, 'htmlcov')
483 485 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
484 486 if options.annotate:
485 487 adir = os.path.join(TESTDIR, 'annotated')
486 488 if not os.path.isdir(adir):
487 489 os.mkdir(adir)
488 490 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
489 491
490 492 def pytest(test, wd, options, replacements):
491 493 py3kswitch = options.py3k_warnings and ' -3' or ''
492 494 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
493 495 vlog("# Running", cmd)
494 496 if os.name == 'nt':
495 497 replacements.append((r'\r\n', '\n'))
496 498 return run(cmd, wd, options, replacements)
497 499
498 500 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
499 501 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
500 502 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
501 503 escapemap.update({'\\': '\\\\', '\r': r'\r'})
502 504 def escapef(m):
503 505 return escapemap[m.group(0)]
504 506 def stringescape(s):
505 507 return escapesub(escapef, s)
506 508
507 509 def rematch(el, l):
508 510 try:
509 511 # use \Z to ensure that the regex matches to the end of the string
510 512 if os.name == 'nt':
511 513 return re.match(el + r'\r?\n\Z', l)
512 514 return re.match(el + r'\n\Z', l)
513 515 except re.error:
514 516 # el is an invalid regex
515 517 return False
516 518
517 519 def globmatch(el, l):
518 520 # The only supported special characters are * and ? plus / which also
519 521 # matches \ on windows. Escaping of these caracters is supported.
520 522 i, n = 0, len(el)
521 523 res = ''
522 524 while i < n:
523 525 c = el[i]
524 526 i += 1
525 527 if c == '\\' and el[i] in '*?\\/':
526 528 res += el[i - 1:i + 1]
527 529 i += 1
528 530 elif c == '*':
529 531 res += '.*'
530 532 elif c == '?':
531 533 res += '.'
532 534 elif c == '/' and os.name == 'nt':
533 535 res += '[/\\\\]'
534 536 else:
535 537 res += re.escape(c)
536 538 return rematch(res, l)
537 539
538 540 def linematch(el, l):
539 541 if el == l: # perfect match (fast)
540 542 return True
541 543 if el:
542 544 if el.endswith(" (esc)\n"):
543 545 el = el[:-7].decode('string-escape') + '\n'
544 546 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
545 547 return True
546 548 if (el.endswith(" (re)\n") and rematch(el[:-6], l) or
547 549 el.endswith(" (glob)\n") and globmatch(el[:-8], l)):
548 550 return True
549 551 return False
550 552
551 553 def tsttest(test, wd, options, replacements):
552 554 # We generate a shell script which outputs unique markers to line
553 555 # up script results with our source. These markers include input
554 556 # line number and the last return code
555 557 salt = "SALT" + str(time.time())
556 558 def addsalt(line, inpython):
557 559 if inpython:
558 560 script.append('%s %d 0\n' % (salt, line))
559 561 else:
560 562 script.append('echo %s %s $?\n' % (salt, line))
561 563
562 564 # After we run the shell script, we re-unify the script output
563 565 # with non-active parts of the source, with synchronization by our
564 566 # SALT line number markers. The after table contains the
565 567 # non-active components, ordered by line number
566 568 after = {}
567 569 pos = prepos = -1
568 570
569 571 # Expected shellscript output
570 572 expected = {}
571 573
572 574 # We keep track of whether or not we're in a Python block so we
573 575 # can generate the surrounding doctest magic
574 576 inpython = False
575 577
576 578 # True or False when in a true or false conditional section
577 579 skipping = None
578 580
579 581 def hghave(reqs):
580 582 # TODO: do something smarter when all other uses of hghave is gone
581 583 tdir = TESTDIR.replace('\\', '/')
582 584 proc = Popen4('%s -c "%s/hghave %s"' %
583 585 (options.shell, tdir, ' '.join(reqs)), wd, 0)
584 586 proc.communicate()
585 587 ret = proc.wait()
586 588 if wifexited(ret):
587 589 ret = os.WEXITSTATUS(ret)
588 590 return ret == 0
589 591
590 592 f = open(test)
591 593 t = f.readlines()
592 594 f.close()
593 595
594 596 script = []
595 597 if options.debug:
596 598 script.append('set -x\n')
597 599 if os.getenv('MSYSTEM'):
598 600 script.append('alias pwd="pwd -W"\n')
599 601 for n, l in enumerate(t):
600 602 if not l.endswith('\n'):
601 603 l += '\n'
602 604 if l.startswith('#if'):
603 605 if skipping is not None:
604 606 after.setdefault(pos, []).append(' !!! nested #if\n')
605 607 skipping = not hghave(l.split()[1:])
606 608 after.setdefault(pos, []).append(l)
607 609 elif l.startswith('#else'):
608 610 if skipping is None:
609 611 after.setdefault(pos, []).append(' !!! missing #if\n')
610 612 skipping = not skipping
611 613 after.setdefault(pos, []).append(l)
612 614 elif l.startswith('#endif'):
613 615 if skipping is None:
614 616 after.setdefault(pos, []).append(' !!! missing #if\n')
615 617 skipping = None
616 618 after.setdefault(pos, []).append(l)
617 619 elif skipping:
618 620 after.setdefault(pos, []).append(l)
619 621 elif l.startswith(' >>> '): # python inlines
620 622 after.setdefault(pos, []).append(l)
621 623 prepos = pos
622 624 pos = n
623 625 if not inpython:
624 626 # we've just entered a Python block, add the header
625 627 inpython = True
626 628 addsalt(prepos, False) # make sure we report the exit code
627 629 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
628 630 addsalt(n, True)
629 631 script.append(l[2:])
630 632 elif l.startswith(' ... '): # python inlines
631 633 after.setdefault(prepos, []).append(l)
632 634 script.append(l[2:])
633 635 elif l.startswith(' $ '): # commands
634 636 if inpython:
635 637 script.append("EOF\n")
636 638 inpython = False
637 639 after.setdefault(pos, []).append(l)
638 640 prepos = pos
639 641 pos = n
640 642 addsalt(n, False)
641 643 cmd = l[4:].split()
642 644 if len(cmd) == 2 and cmd[0] == 'cd':
643 645 l = ' $ cd %s || exit 1\n' % cmd[1]
644 646 script.append(l[4:])
645 647 elif l.startswith(' > '): # continuations
646 648 after.setdefault(prepos, []).append(l)
647 649 script.append(l[4:])
648 650 elif l.startswith(' '): # results
649 651 # queue up a list of expected results
650 652 expected.setdefault(pos, []).append(l[2:])
651 653 else:
652 654 if inpython:
653 655 script.append("EOF\n")
654 656 inpython = False
655 657 # non-command/result - queue up for merged output
656 658 after.setdefault(pos, []).append(l)
657 659
658 660 if inpython:
659 661 script.append("EOF\n")
660 662 if skipping is not None:
661 663 after.setdefault(pos, []).append(' !!! missing #endif\n')
662 664 addsalt(n + 1, False)
663 665
664 666 # Write out the script and execute it
665 667 fd, name = tempfile.mkstemp(suffix='hg-tst')
666 668 try:
667 669 for l in script:
668 670 os.write(fd, l)
669 671 os.close(fd)
670 672
671 673 cmd = '%s "%s"' % (options.shell, name)
672 674 vlog("# Running", cmd)
673 675 exitcode, output = run(cmd, wd, options, replacements)
674 676 # do not merge output if skipped, return hghave message instead
675 677 # similarly, with --debug, output is None
676 678 if exitcode == SKIPPED_STATUS or output is None:
677 679 return exitcode, output
678 680 finally:
679 681 os.remove(name)
680 682
681 683 # Merge the script output back into a unified test
682 684
683 685 pos = -1
684 686 postout = []
685 687 ret = 0
686 688 for l in output:
687 689 lout, lcmd = l, None
688 690 if salt in l:
689 691 lout, lcmd = l.split(salt, 1)
690 692
691 693 if lout:
692 694 if not lout.endswith('\n'):
693 695 lout += ' (no-eol)\n'
694 696
695 697 # find the expected output at the current position
696 698 el = None
697 699 if pos in expected and expected[pos]:
698 700 el = expected[pos].pop(0)
699 701
700 702 if linematch(el, lout):
701 703 postout.append(" " + el)
702 704 else:
703 705 if needescape(lout):
704 706 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
705 707 postout.append(" " + lout) # let diff deal with it
706 708
707 709 if lcmd:
708 710 # add on last return code
709 711 ret = int(lcmd.split()[1])
710 712 if ret != 0:
711 713 postout.append(" [%s]\n" % ret)
712 714 if pos in after:
713 715 # merge in non-active test bits
714 716 postout += after.pop(pos)
715 717 pos = int(lcmd.split()[0])
716 718
717 719 if pos in after:
718 720 postout += after.pop(pos)
719 721
720 722 return exitcode, postout
721 723
722 724 wifexited = getattr(os, "WIFEXITED", lambda x: False)
723 725 def run(cmd, wd, options, replacements):
724 726 """Run command in a sub-process, capturing the output (stdout and stderr).
725 727 Return a tuple (exitcode, output). output is None in debug mode."""
726 728 # TODO: Use subprocess.Popen if we're running on Python 2.4
727 729 if options.debug:
728 730 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
729 731 ret = proc.wait()
730 732 return (ret, None)
731 733
732 734 proc = Popen4(cmd, wd, options.timeout)
733 735 def cleanup():
734 736 terminate(proc)
735 737 ret = proc.wait()
736 738 if ret == 0:
737 739 ret = signal.SIGTERM << 8
738 740 killdaemons()
739 741 return ret
740 742
741 743 output = ''
742 744 proc.tochild.close()
743 745
744 746 try:
745 747 output = proc.fromchild.read()
746 748 except KeyboardInterrupt:
747 749 vlog('# Handling keyboard interrupt')
748 750 cleanup()
749 751 raise
750 752
751 753 ret = proc.wait()
752 754 if wifexited(ret):
753 755 ret = os.WEXITSTATUS(ret)
754 756
755 757 if proc.timeout:
756 758 ret = 'timeout'
757 759
758 760 if ret:
759 761 killdaemons()
760 762
761 763 for s, r in replacements:
762 764 output = re.sub(s, r, output)
763 765 return ret, output.splitlines(True)
764 766
765 767 def runone(options, test):
766 768 '''tristate output:
767 769 None -> skipped
768 770 True -> passed
769 771 False -> failed'''
770 772
771 773 global results, resultslock, iolock
772 774
773 775 testpath = os.path.join(TESTDIR, test)
774 776
775 777 def result(l, e):
776 778 resultslock.acquire()
777 779 results[l].append(e)
778 780 resultslock.release()
779 781
780 782 def skip(msg):
781 783 if not options.verbose:
782 784 result('s', (test, msg))
783 785 else:
784 786 iolock.acquire()
785 787 print "\nSkipping %s: %s" % (testpath, msg)
786 788 iolock.release()
787 789 return None
788 790
789 791 def fail(msg, ret):
790 792 if not options.nodiff:
791 793 iolock.acquire()
792 794 print "\nERROR: %s %s" % (testpath, msg)
793 795 iolock.release()
794 796 if (not ret and options.interactive
795 797 and os.path.exists(testpath + ".err")):
796 798 iolock.acquire()
797 799 print "Accept this change? [n] ",
798 800 answer = sys.stdin.readline().strip()
799 801 iolock.release()
800 802 if answer.lower() in "y yes".split():
801 803 if test.endswith(".t"):
802 804 rename(testpath + ".err", testpath)
803 805 else:
804 806 rename(testpath + ".err", testpath + ".out")
805 807 result('p', test)
806 808 return
807 809 result('f', (test, msg))
808 810
809 811 def success():
810 812 result('p', test)
811 813
812 814 def ignore(msg):
813 815 result('i', (test, msg))
814 816
815 817 if (os.path.basename(test).startswith("test-") and '~' not in test and
816 818 ('.' not in test or test.endswith('.py') or
817 819 test.endswith('.bat') or test.endswith('.t'))):
818 820 if not os.path.exists(test):
819 821 skip("doesn't exist")
820 822 return None
821 823 else:
822 824 vlog('# Test file', test, 'not supported, ignoring')
823 825 return None # not a supported test, don't record
824 826
825 827 if not (options.whitelisted and test in options.whitelisted):
826 828 if options.blacklist and test in options.blacklist:
827 829 skip("blacklisted")
828 830 return None
829 831
830 832 if options.retest and not os.path.exists(test + ".err"):
831 833 ignore("not retesting")
832 834 return None
833 835
834 836 if options.keywords:
835 837 fp = open(test)
836 838 t = fp.read().lower() + test.lower()
837 839 fp.close()
838 840 for k in options.keywords.lower().split():
839 841 if k in t:
840 842 break
841 843 else:
842 844 ignore("doesn't match keyword")
843 845 return None
844 846
845 847 vlog("# Test", test)
846 848
847 849 # create a fresh hgrc
848 850 hgrc = open(HGRCPATH, 'w+')
849 851 hgrc.write('[ui]\n')
850 852 hgrc.write('slash = True\n')
851 853 hgrc.write('[defaults]\n')
852 854 hgrc.write('backout = -d "0 0"\n')
853 855 hgrc.write('commit = -d "0 0"\n')
854 856 hgrc.write('tag = -d "0 0"\n')
855 857 if options.inotify:
856 858 hgrc.write('[extensions]\n')
857 859 hgrc.write('inotify=\n')
858 860 hgrc.write('[inotify]\n')
859 861 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
860 862 hgrc.write('appendpid=True\n')
861 863 if options.extra_config_opt:
862 864 for opt in options.extra_config_opt:
863 865 section, key = opt.split('.', 1)
864 866 assert '=' in key, ('extra config opt %s must '
865 867 'have an = for assignment' % opt)
866 868 hgrc.write('[%s]\n%s\n' % (section, key))
867 869 hgrc.close()
868 870
869 871 ref = os.path.join(TESTDIR, test+".out")
870 872 err = os.path.join(TESTDIR, test+".err")
871 873 if os.path.exists(err):
872 874 os.remove(err) # Remove any previous output files
873 875 try:
874 876 tf = open(testpath)
875 877 firstline = tf.readline().rstrip()
876 878 tf.close()
877 879 except IOError:
878 880 firstline = ''
879 881 lctest = test.lower()
880 882
881 883 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
882 884 runner = pytest
883 885 elif lctest.endswith('.t'):
884 886 runner = tsttest
885 887 ref = testpath
886 888 else:
887 889 return skip("unknown test type")
888 890
889 891 # Make a tmp subdirectory to work in
890 892 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
891 893 os.path.join(HGTMP, os.path.basename(test))
892 894
893 895 replacements = [
894 896 (r':%s\b' % options.port, ':$HGPORT'),
895 897 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
896 898 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
897 899 ]
898 900 if os.name == 'nt':
899 901 replacements.append(
900 902 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
901 903 c in '/\\' and r'[/\\]' or
902 904 c.isdigit() and c or
903 905 '\\' + c
904 906 for c in testtmp), '$TESTTMP'))
905 907 else:
906 908 replacements.append((re.escape(testtmp), '$TESTTMP'))
907 909
908 910 os.mkdir(testtmp)
909 911 if options.time:
910 912 starttime = time.time()
911 913 ret, out = runner(testpath, testtmp, options, replacements)
912 914 if options.time:
913 915 endtime = time.time()
914 916 times.append((test, endtime - starttime))
915 917 vlog("# Ret was:", ret)
916 918
917 919 mark = '.'
918 920
919 921 skipped = (ret == SKIPPED_STATUS)
920 922
921 923 # If we're not in --debug mode and reference output file exists,
922 924 # check test output against it.
923 925 if options.debug:
924 926 refout = None # to match "out is None"
925 927 elif os.path.exists(ref):
926 928 f = open(ref, "r")
927 929 refout = f.read().splitlines(True)
928 930 f.close()
929 931 else:
930 932 refout = []
931 933
932 934 if (ret != 0 or out != refout) and not skipped and not options.debug:
933 935 # Save errors to a file for diagnosis
934 936 f = open(err, "wb")
935 937 for line in out:
936 938 f.write(line)
937 939 f.close()
938 940
939 941 def describe(ret):
940 942 if ret < 0:
941 943 return 'killed by signal %d' % -ret
942 944 return 'returned error code %d' % ret
943 945
944 946 if skipped:
945 947 mark = 's'
946 948 if out is None: # debug mode: nothing to parse
947 949 missing = ['unknown']
948 950 failed = None
949 951 else:
950 952 missing, failed = parsehghaveoutput(out)
951 953 if not missing:
952 954 missing = ['irrelevant']
953 955 if failed:
954 956 fail("hghave failed checking for %s" % failed[-1], ret)
955 957 skipped = False
956 958 else:
957 959 skip(missing[-1])
958 960 elif ret == 'timeout':
959 961 mark = 't'
960 962 fail("timed out", ret)
961 963 elif out != refout:
962 964 mark = '!'
963 965 if not options.nodiff:
964 966 iolock.acquire()
965 967 if options.view:
966 968 os.system("%s %s %s" % (options.view, ref, err))
967 969 else:
968 970 showdiff(refout, out, ref, err)
969 971 iolock.release()
970 972 if ret:
971 973 fail("output changed and " + describe(ret), ret)
972 974 else:
973 975 fail("output changed", ret)
974 976 ret = 1
975 977 elif ret:
976 978 mark = '!'
977 979 fail(describe(ret), ret)
978 980 else:
979 981 success()
980 982
981 983 if not options.verbose:
982 984 iolock.acquire()
983 985 sys.stdout.write(mark)
984 986 sys.stdout.flush()
985 987 iolock.release()
986 988
987 989 killdaemons()
988 990
989 991 if not options.keep_tmpdir:
990 992 shutil.rmtree(testtmp, True)
991 993 if skipped:
992 994 return None
993 995 return ret == 0
994 996
995 997 _hgpath = None
996 998
997 999 def _gethgpath():
998 1000 """Return the path to the mercurial package that is actually found by
999 1001 the current Python interpreter."""
1000 1002 global _hgpath
1001 1003 if _hgpath is not None:
1002 1004 return _hgpath
1003 1005
1004 1006 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1005 1007 pipe = os.popen(cmd % PYTHON)
1006 1008 try:
1007 1009 _hgpath = pipe.read().strip()
1008 1010 finally:
1009 1011 pipe.close()
1010 1012 return _hgpath
1011 1013
1012 1014 def _checkhglib(verb):
1013 1015 """Ensure that the 'mercurial' package imported by python is
1014 1016 the one we expect it to be. If not, print a warning to stderr."""
1015 1017 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1016 1018 actualhg = _gethgpath()
1017 1019 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1018 1020 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1019 1021 ' (expected %s)\n'
1020 1022 % (verb, actualhg, expecthg))
1021 1023
1022 1024 def runchildren(options, tests):
1023 1025 if INST:
1024 1026 installhg(options)
1025 1027 _checkhglib("Testing")
1026 1028
1027 1029 optcopy = dict(options.__dict__)
1028 1030 optcopy['jobs'] = 1
1029 1031
1030 1032 # Because whitelist has to override keyword matches, we have to
1031 1033 # actually load the whitelist in the children as well, so we allow
1032 1034 # the list of whitelist files to pass through and be parsed in the
1033 1035 # children, but not the dict of whitelisted tests resulting from
1034 1036 # the parse, used here to override blacklisted tests.
1035 1037 whitelist = optcopy['whitelisted'] or []
1036 1038 del optcopy['whitelisted']
1037 1039
1038 1040 blacklist = optcopy['blacklist'] or []
1039 1041 del optcopy['blacklist']
1040 1042 blacklisted = []
1041 1043
1042 1044 if optcopy['with_hg'] is None:
1043 1045 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1044 1046 optcopy.pop('anycoverage', None)
1045 1047
1046 1048 opts = []
1047 1049 for opt, value in optcopy.iteritems():
1048 1050 name = '--' + opt.replace('_', '-')
1049 1051 if value is True:
1050 1052 opts.append(name)
1051 1053 elif isinstance(value, list):
1052 1054 for v in value:
1053 1055 opts.append(name + '=' + str(v))
1054 1056 elif value is not None:
1055 1057 opts.append(name + '=' + str(value))
1056 1058
1057 1059 tests.reverse()
1058 1060 jobs = [[] for j in xrange(options.jobs)]
1059 1061 while tests:
1060 1062 for job in jobs:
1061 1063 if not tests:
1062 1064 break
1063 1065 test = tests.pop()
1064 1066 if test not in whitelist and test in blacklist:
1065 1067 blacklisted.append(test)
1066 1068 else:
1067 1069 job.append(test)
1068 1070 fps = {}
1069 1071
1070 1072 for j, job in enumerate(jobs):
1071 1073 if not job:
1072 1074 continue
1073 1075 rfd, wfd = os.pipe()
1074 1076 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1075 1077 childtmp = os.path.join(HGTMP, 'child%d' % j)
1076 1078 childopts += ['--tmpdir', childtmp]
1077 1079 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1078 1080 vlog(' '.join(cmdline))
1079 1081 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'rb')
1080 1082 os.close(wfd)
1081 1083 signal.signal(signal.SIGINT, signal.SIG_IGN)
1082 1084 failures = 0
1083 1085 passed, skipped, failed = 0, 0, 0
1084 1086 skips = []
1085 1087 fails = []
1086 1088 while fps:
1087 1089 pid, status = os.wait()
1088 1090 fp = fps.pop(pid)
1089 1091 try:
1090 1092 childresults = pickle.load(fp)
1091 1093 except pickle.UnpicklingError:
1092 1094 pass
1093 1095 else:
1094 1096 passed += len(childresults['p'])
1095 1097 skipped += len(childresults['s'])
1096 1098 failed += len(childresults['f'])
1097 1099 skips.extend(childresults['s'])
1098 1100 fails.extend(childresults['f'])
1099 1101 if options.time:
1100 1102 childtimes = pickle.load(fp)
1101 1103 times.extend(childtimes)
1102 1104
1103 1105 vlog('pid %d exited, status %d' % (pid, status))
1104 1106 failures |= status
1105 1107 print
1106 1108 skipped += len(blacklisted)
1107 1109 if not options.noskips:
1108 1110 for s in skips:
1109 1111 print "Skipped %s: %s" % (s[0], s[1])
1110 1112 for s in blacklisted:
1111 1113 print "Skipped %s: blacklisted" % s
1112 1114 for s in fails:
1113 1115 print "Failed %s: %s" % (s[0], s[1])
1114 1116
1115 1117 _checkhglib("Tested")
1116 1118 print "# Ran %d tests, %d skipped, %d failed." % (
1117 1119 passed + failed, skipped, failed)
1118 1120
1119 1121 if options.time:
1120 1122 outputtimes(options)
1121 1123 if options.anycoverage:
1122 1124 outputcoverage(options)
1123 1125 sys.exit(failures != 0)
1124 1126
1125 1127 results = dict(p=[], f=[], s=[], i=[])
1126 1128 resultslock = threading.Lock()
1127 1129 times = []
1128 1130 iolock = threading.Lock()
1129 1131
1130 1132 def runqueue(options, tests):
1131 1133 for test in tests:
1132 1134 ret = runone(options, test)
1133 1135 if options.first and ret is not None and not ret:
1134 1136 break
1135 1137
1136 1138 def runtests(options, tests):
1137 1139 global DAEMON_PIDS, HGRCPATH
1138 1140 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1139 1141 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1140 1142
1141 1143 try:
1142 1144 if INST:
1143 1145 installhg(options)
1144 1146 _checkhglib("Testing")
1145 1147
1146 1148 if options.restart:
1147 1149 orig = list(tests)
1148 1150 while tests:
1149 1151 if os.path.exists(tests[0] + ".err"):
1150 1152 break
1151 1153 tests.pop(0)
1152 1154 if not tests:
1153 1155 print "running all tests"
1154 1156 tests = orig
1155 1157
1156 1158 runqueue(options, tests)
1157 1159
1158 1160 failed = len(results['f'])
1159 1161 tested = len(results['p']) + failed
1160 1162 skipped = len(results['s'])
1161 1163 ignored = len(results['i'])
1162 1164
1163 1165 if options.child:
1164 1166 fp = os.fdopen(options.child, 'wb')
1165 1167 pickle.dump(results, fp, pickle.HIGHEST_PROTOCOL)
1166 1168 if options.time:
1167 1169 pickle.dump(times, fp, pickle.HIGHEST_PROTOCOL)
1168 1170 fp.close()
1169 1171 else:
1170 1172 print
1171 1173 for s in results['s']:
1172 1174 print "Skipped %s: %s" % s
1173 1175 for s in results['f']:
1174 1176 print "Failed %s: %s" % s
1175 1177 _checkhglib("Tested")
1176 1178 print "# Ran %d tests, %d skipped, %d failed." % (
1177 1179 tested, skipped + ignored, failed)
1178 1180 if options.time:
1179 1181 outputtimes(options)
1180 1182
1181 1183 if options.anycoverage:
1182 1184 outputcoverage(options)
1183 1185 except KeyboardInterrupt:
1184 1186 failed = True
1185 1187 print "\ninterrupted!"
1186 1188
1187 1189 if failed:
1188 1190 sys.exit(1)
1189 1191
1190 1192 def main():
1191 1193 (options, args) = parseargs()
1192 1194 if not options.child:
1193 1195 os.umask(022)
1194 1196
1195 1197 checktools()
1196 1198
1197 1199 if len(args) == 0:
1198 1200 args = os.listdir(".")
1199 1201 args.sort()
1200 1202
1201 1203 tests = args
1202 1204
1203 1205 # Reset some environment variables to well-known values so that
1204 1206 # the tests produce repeatable output.
1205 1207 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1206 1208 os.environ['TZ'] = 'GMT'
1207 1209 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1208 1210 os.environ['CDPATH'] = ''
1209 1211 os.environ['COLUMNS'] = '80'
1210 1212 os.environ['GREP_OPTIONS'] = ''
1211 1213 os.environ['http_proxy'] = ''
1212 1214 os.environ['no_proxy'] = ''
1213 1215 os.environ['NO_PROXY'] = ''
1214 1216 os.environ['TERM'] = 'xterm'
1215 1217
1216 1218 # unset env related to hooks
1217 1219 for k in os.environ.keys():
1218 1220 if k.startswith('HG_'):
1219 1221 # can't remove on solaris
1220 1222 os.environ[k] = ''
1221 1223 del os.environ[k]
1222 1224 if 'HG' in os.environ:
1223 1225 # can't remove on solaris
1224 1226 os.environ['HG'] = ''
1225 1227 del os.environ['HG']
1226 1228
1227 1229 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1228 1230 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1229 1231 if options.tmpdir:
1230 1232 options.keep_tmpdir = True
1231 1233 tmpdir = options.tmpdir
1232 1234 if os.path.exists(tmpdir):
1233 1235 # Meaning of tmpdir has changed since 1.3: we used to create
1234 1236 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1235 1237 # tmpdir already exists.
1236 1238 sys.exit("error: temp dir %r already exists" % tmpdir)
1237 1239
1238 1240 # Automatically removing tmpdir sounds convenient, but could
1239 1241 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1240 1242 # or "--tmpdir=$HOME".
1241 1243 #vlog("# Removing temp dir", tmpdir)
1242 1244 #shutil.rmtree(tmpdir)
1243 1245 os.makedirs(tmpdir)
1244 1246 else:
1245 1247 d = None
1246 1248 if os.name == 'nt':
1247 1249 # without this, we get the default temp dir location, but
1248 1250 # in all lowercase, which causes troubles with paths (issue3490)
1249 1251 d = os.getenv('TMP')
1250 1252 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1251 1253 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1252 1254 DAEMON_PIDS = None
1253 1255 HGRCPATH = None
1254 1256
1255 1257 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1256 1258 os.environ["HGMERGE"] = "internal:merge"
1257 1259 os.environ["HGUSER"] = "test"
1258 1260 os.environ["HGENCODING"] = "ascii"
1259 1261 os.environ["HGENCODINGMODE"] = "strict"
1260 1262 os.environ["HGPORT"] = str(options.port)
1261 1263 os.environ["HGPORT1"] = str(options.port + 1)
1262 1264 os.environ["HGPORT2"] = str(options.port + 2)
1263 1265
1264 1266 if options.with_hg:
1265 1267 INST = None
1266 1268 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1267 1269
1268 1270 # This looks redundant with how Python initializes sys.path from
1269 1271 # the location of the script being executed. Needed because the
1270 1272 # "hg" specified by --with-hg is not the only Python script
1271 1273 # executed in the test suite that needs to import 'mercurial'
1272 1274 # ... which means it's not really redundant at all.
1273 1275 PYTHONDIR = BINDIR
1274 1276 else:
1275 1277 INST = os.path.join(HGTMP, "install")
1276 1278 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1277 1279 PYTHONDIR = os.path.join(INST, "lib", "python")
1278 1280
1279 1281 os.environ["BINDIR"] = BINDIR
1280 1282 os.environ["PYTHON"] = PYTHON
1281 1283
1282 1284 if not options.child:
1283 1285 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1284 1286 os.environ["PATH"] = os.pathsep.join(path)
1285 1287
1286 1288 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1287 1289 # can run .../tests/run-tests.py test-foo where test-foo
1288 1290 # adds an extension to HGRC
1289 1291 pypath = [PYTHONDIR, TESTDIR]
1290 1292 # We have to augment PYTHONPATH, rather than simply replacing
1291 1293 # it, in case external libraries are only available via current
1292 1294 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1293 1295 # are in /opt/subversion.)
1294 1296 oldpypath = os.environ.get(IMPL_PATH)
1295 1297 if oldpypath:
1296 1298 pypath.append(oldpypath)
1297 1299 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1298 1300
1299 1301 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1300 1302
1301 1303 vlog("# Using TESTDIR", TESTDIR)
1302 1304 vlog("# Using HGTMP", HGTMP)
1303 1305 vlog("# Using PATH", os.environ["PATH"])
1304 1306 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1305 1307
1306 1308 try:
1307 1309 if len(tests) > 1 and options.jobs > 1:
1308 1310 runchildren(options, tests)
1309 1311 else:
1310 1312 runtests(options, tests)
1311 1313 finally:
1312 1314 time.sleep(.1)
1313 1315 cleanup(options)
1314 1316
1315 1317 if __name__ == '__main__':
1316 1318 main()
General Comments 0
You need to be logged in to leave comments. Login now