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