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