##// END OF EJS Templates
run-tests: suggest to append glob when only path sep does not match...
Simon Heimberg -
r20273:d9d6cbbe default
parent child Browse files
Show More
@@ -1,1269 +1,1276 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 609 log("\nInfo, unnecessary glob: %s (glob)" % el)
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 if os.altsep and l.replace('\\', '/') == el:
642 return '+glob'
641 643 return False
642 644
643 645 def tsttest(test, wd, options, replacements, env):
644 646 # We generate a shell script which outputs unique markers to line
645 647 # up script results with our source. These markers include input
646 648 # line number and the last return code
647 649 salt = "SALT" + str(time.time())
648 650 def addsalt(line, inpython):
649 651 if inpython:
650 652 script.append('%s %d 0\n' % (salt, line))
651 653 else:
652 654 script.append('echo %s %s $?\n' % (salt, line))
653 655
654 656 # After we run the shell script, we re-unify the script output
655 657 # with non-active parts of the source, with synchronization by our
656 658 # SALT line number markers. The after table contains the
657 659 # non-active components, ordered by line number
658 660 after = {}
659 661 pos = prepos = -1
660 662
661 663 # Expected shellscript output
662 664 expected = {}
663 665
664 666 # We keep track of whether or not we're in a Python block so we
665 667 # can generate the surrounding doctest magic
666 668 inpython = False
667 669
668 670 # True or False when in a true or false conditional section
669 671 skipping = None
670 672
671 673 def hghave(reqs):
672 674 # TODO: do something smarter when all other uses of hghave is gone
673 675 tdir = TESTDIR.replace('\\', '/')
674 676 proc = Popen4('%s -c "%s/hghave %s"' %
675 677 (options.shell, tdir, ' '.join(reqs)), wd, 0)
676 678 stdout, stderr = proc.communicate()
677 679 ret = proc.wait()
678 680 if wifexited(ret):
679 681 ret = os.WEXITSTATUS(ret)
680 682 if ret == 2:
681 683 print stdout
682 684 sys.exit(1)
683 685 return ret == 0
684 686
685 687 f = open(test)
686 688 t = f.readlines()
687 689 f.close()
688 690
689 691 script = []
690 692 if options.debug:
691 693 script.append('set -x\n')
692 694 if os.getenv('MSYSTEM'):
693 695 script.append('alias pwd="pwd -W"\n')
694 696 n = 0
695 697 for n, l in enumerate(t):
696 698 if not l.endswith('\n'):
697 699 l += '\n'
698 700 if l.startswith('#if'):
699 701 if skipping is not None:
700 702 after.setdefault(pos, []).append(' !!! nested #if\n')
701 703 skipping = not hghave(l.split()[1:])
702 704 after.setdefault(pos, []).append(l)
703 705 elif l.startswith('#else'):
704 706 if skipping is None:
705 707 after.setdefault(pos, []).append(' !!! missing #if\n')
706 708 skipping = not skipping
707 709 after.setdefault(pos, []).append(l)
708 710 elif l.startswith('#endif'):
709 711 if skipping is None:
710 712 after.setdefault(pos, []).append(' !!! missing #if\n')
711 713 skipping = None
712 714 after.setdefault(pos, []).append(l)
713 715 elif skipping:
714 716 after.setdefault(pos, []).append(l)
715 717 elif l.startswith(' >>> '): # python inlines
716 718 after.setdefault(pos, []).append(l)
717 719 prepos = pos
718 720 pos = n
719 721 if not inpython:
720 722 # we've just entered a Python block, add the header
721 723 inpython = True
722 724 addsalt(prepos, False) # make sure we report the exit code
723 725 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
724 726 addsalt(n, True)
725 727 script.append(l[2:])
726 728 elif l.startswith(' ... '): # python inlines
727 729 after.setdefault(prepos, []).append(l)
728 730 script.append(l[2:])
729 731 elif l.startswith(' $ '): # commands
730 732 if inpython:
731 733 script.append("EOF\n")
732 734 inpython = False
733 735 after.setdefault(pos, []).append(l)
734 736 prepos = pos
735 737 pos = n
736 738 addsalt(n, False)
737 739 cmd = l[4:].split()
738 740 if len(cmd) == 2 and cmd[0] == 'cd':
739 741 l = ' $ cd %s || exit 1\n' % cmd[1]
740 742 script.append(l[4:])
741 743 elif l.startswith(' > '): # continuations
742 744 after.setdefault(prepos, []).append(l)
743 745 script.append(l[4:])
744 746 elif l.startswith(' '): # results
745 747 # queue up a list of expected results
746 748 expected.setdefault(pos, []).append(l[2:])
747 749 else:
748 750 if inpython:
749 751 script.append("EOF\n")
750 752 inpython = False
751 753 # non-command/result - queue up for merged output
752 754 after.setdefault(pos, []).append(l)
753 755
754 756 if inpython:
755 757 script.append("EOF\n")
756 758 if skipping is not None:
757 759 after.setdefault(pos, []).append(' !!! missing #endif\n')
758 760 addsalt(n + 1, False)
759 761
760 762 # Write out the script and execute it
761 763 name = wd + '.sh'
762 764 f = open(name, 'w')
763 765 for l in script:
764 766 f.write(l)
765 767 f.close()
766 768
767 769 cmd = '%s "%s"' % (options.shell, name)
768 770 vlog("# Running", cmd)
769 771 exitcode, output = run(cmd, wd, options, replacements, env)
770 772 # do not merge output if skipped, return hghave message instead
771 773 # similarly, with --debug, output is None
772 774 if exitcode == SKIPPED_STATUS or output is None:
773 775 return exitcode, output
774 776
775 777 # Merge the script output back into a unified test
776 778
777 779 pos = -1
778 780 postout = []
779 781 ret = 0
780 782 for l in output:
781 783 lout, lcmd = l, None
782 784 if salt in l:
783 785 lout, lcmd = l.split(salt, 1)
784 786
785 787 if lout:
786 788 if not lout.endswith('\n'):
787 789 lout += ' (no-eol)\n'
788 790
789 791 # find the expected output at the current position
790 792 el = None
791 793 if pos in expected and expected[pos]:
792 794 el = expected[pos].pop(0)
793 795
794 if linematch(el, lout):
796 r = linematch(el, lout)
797 if isinstance(r, str):
798 if r == '+glob':
799 lout = el[:-1] + ' (glob)\n'
800 r = False
801 if r:
795 802 postout.append(" " + el)
796 803 else:
797 804 if needescape(lout):
798 805 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
799 806 postout.append(" " + lout) # let diff deal with it
800 807
801 808 if lcmd:
802 809 # add on last return code
803 810 ret = int(lcmd.split()[1])
804 811 if ret != 0:
805 812 postout.append(" [%s]\n" % ret)
806 813 if pos in after:
807 814 # merge in non-active test bits
808 815 postout += after.pop(pos)
809 816 pos = int(lcmd.split()[0])
810 817
811 818 if pos in after:
812 819 postout += after.pop(pos)
813 820
814 821 return exitcode, postout
815 822
816 823 wifexited = getattr(os, "WIFEXITED", lambda x: False)
817 824 def run(cmd, wd, options, replacements, env):
818 825 """Run command in a sub-process, capturing the output (stdout and stderr).
819 826 Return a tuple (exitcode, output). output is None in debug mode."""
820 827 # TODO: Use subprocess.Popen if we're running on Python 2.4
821 828 if options.debug:
822 829 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
823 830 ret = proc.wait()
824 831 return (ret, None)
825 832
826 833 proc = Popen4(cmd, wd, options.timeout, env)
827 834 def cleanup():
828 835 terminate(proc)
829 836 ret = proc.wait()
830 837 if ret == 0:
831 838 ret = signal.SIGTERM << 8
832 839 killdaemons(env['DAEMON_PIDS'])
833 840 return ret
834 841
835 842 output = ''
836 843 proc.tochild.close()
837 844
838 845 try:
839 846 output = proc.fromchild.read()
840 847 except KeyboardInterrupt:
841 848 vlog('# Handling keyboard interrupt')
842 849 cleanup()
843 850 raise
844 851
845 852 ret = proc.wait()
846 853 if wifexited(ret):
847 854 ret = os.WEXITSTATUS(ret)
848 855
849 856 if proc.timeout:
850 857 ret = 'timeout'
851 858
852 859 if ret:
853 860 killdaemons(env['DAEMON_PIDS'])
854 861
855 862 if abort:
856 863 raise KeyboardInterrupt()
857 864
858 865 for s, r in replacements:
859 866 output = re.sub(s, r, output)
860 867 return ret, output.splitlines(True)
861 868
862 869 def runone(options, test, count):
863 870 '''returns a result element: (code, test, msg)'''
864 871
865 872 def skip(msg):
866 873 if options.verbose:
867 874 log("\nSkipping %s: %s" % (testpath, msg))
868 875 return 's', test, msg
869 876
870 877 def fail(msg, ret):
871 878 if not options.nodiff:
872 879 log("\nERROR: %s %s" % (testpath, msg))
873 880 if (not ret and options.interactive
874 881 and os.path.exists(testpath + ".err")):
875 882 iolock.acquire()
876 883 print "Accept this change? [n] ",
877 884 answer = sys.stdin.readline().strip()
878 885 iolock.release()
879 886 if answer.lower() in "y yes".split():
880 887 if test.endswith(".t"):
881 888 rename(testpath + ".err", testpath)
882 889 else:
883 890 rename(testpath + ".err", testpath + ".out")
884 891 return '.', test, ''
885 892 return '!', test, msg
886 893
887 894 def success():
888 895 return '.', test, ''
889 896
890 897 def ignore(msg):
891 898 return 'i', test, msg
892 899
893 900 def describe(ret):
894 901 if ret < 0:
895 902 return 'killed by signal %d' % -ret
896 903 return 'returned error code %d' % ret
897 904
898 905 testpath = os.path.join(TESTDIR, test)
899 906 err = os.path.join(TESTDIR, test + ".err")
900 907 lctest = test.lower()
901 908
902 909 if not os.path.exists(testpath):
903 910 return skip("doesn't exist")
904 911
905 912 if not (options.whitelisted and test in options.whitelisted):
906 913 if options.blacklist and test in options.blacklist:
907 914 return skip("blacklisted")
908 915
909 916 if options.retest and not os.path.exists(test + ".err"):
910 917 return ignore("not retesting")
911 918
912 919 if options.keywords:
913 920 fp = open(test)
914 921 t = fp.read().lower() + test.lower()
915 922 fp.close()
916 923 for k in options.keywords.lower().split():
917 924 if k in t:
918 925 break
919 926 else:
920 927 return ignore("doesn't match keyword")
921 928
922 929 if not lctest.startswith("test-"):
923 930 return skip("not a test file")
924 931 for ext, func, out in testtypes:
925 932 if lctest.endswith(ext):
926 933 runner = func
927 934 ref = os.path.join(TESTDIR, test + out)
928 935 break
929 936 else:
930 937 return skip("unknown test type")
931 938
932 939 vlog("# Test", test)
933 940
934 941 if os.path.exists(err):
935 942 os.remove(err) # Remove any previous output files
936 943
937 944 # Make a tmp subdirectory to work in
938 945 threadtmp = os.path.join(HGTMP, "child%d" % count)
939 946 testtmp = os.path.join(threadtmp, os.path.basename(test))
940 947 os.mkdir(threadtmp)
941 948 os.mkdir(testtmp)
942 949
943 950 port = options.port + count * 3
944 951 replacements = [
945 952 (r':%s\b' % port, ':$HGPORT'),
946 953 (r':%s\b' % (port + 1), ':$HGPORT1'),
947 954 (r':%s\b' % (port + 2), ':$HGPORT2'),
948 955 ]
949 956 if os.name == 'nt':
950 957 replacements.append(
951 958 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
952 959 c in '/\\' and r'[/\\]' or
953 960 c.isdigit() and c or
954 961 '\\' + c
955 962 for c in testtmp), '$TESTTMP'))
956 963 else:
957 964 replacements.append((re.escape(testtmp), '$TESTTMP'))
958 965
959 966 env = createenv(options, testtmp, threadtmp, port)
960 967 createhgrc(env['HGRCPATH'], options)
961 968
962 969 starttime = time.time()
963 970 try:
964 971 ret, out = runner(testpath, testtmp, options, replacements, env)
965 972 except KeyboardInterrupt:
966 973 endtime = time.time()
967 974 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
968 975 raise
969 976 endtime = time.time()
970 977 times.append((test, endtime - starttime))
971 978 vlog("# Ret was:", ret)
972 979
973 980 killdaemons(env['DAEMON_PIDS'])
974 981
975 982 skipped = (ret == SKIPPED_STATUS)
976 983
977 984 # If we're not in --debug mode and reference output file exists,
978 985 # check test output against it.
979 986 if options.debug:
980 987 refout = None # to match "out is None"
981 988 elif os.path.exists(ref):
982 989 f = open(ref, "r")
983 990 refout = f.read().splitlines(True)
984 991 f.close()
985 992 else:
986 993 refout = []
987 994
988 995 if (ret != 0 or out != refout) and not skipped and not options.debug:
989 996 # Save errors to a file for diagnosis
990 997 f = open(err, "wb")
991 998 for line in out:
992 999 f.write(line)
993 1000 f.close()
994 1001
995 1002 if skipped:
996 1003 if out is None: # debug mode: nothing to parse
997 1004 missing = ['unknown']
998 1005 failed = None
999 1006 else:
1000 1007 missing, failed = parsehghaveoutput(out)
1001 1008 if not missing:
1002 1009 missing = ['irrelevant']
1003 1010 if failed:
1004 1011 result = fail("hghave failed checking for %s" % failed[-1], ret)
1005 1012 skipped = False
1006 1013 else:
1007 1014 result = skip(missing[-1])
1008 1015 elif ret == 'timeout':
1009 1016 result = fail("timed out", ret)
1010 1017 elif out != refout:
1011 1018 if not options.nodiff:
1012 1019 iolock.acquire()
1013 1020 if options.view:
1014 1021 os.system("%s %s %s" % (options.view, ref, err))
1015 1022 else:
1016 1023 showdiff(refout, out, ref, err)
1017 1024 iolock.release()
1018 1025 if ret:
1019 1026 result = fail("output changed and " + describe(ret), ret)
1020 1027 else:
1021 1028 result = fail("output changed", ret)
1022 1029 elif ret:
1023 1030 result = fail(describe(ret), ret)
1024 1031 else:
1025 1032 result = success()
1026 1033
1027 1034 if not options.verbose:
1028 1035 iolock.acquire()
1029 1036 sys.stdout.write(result[0])
1030 1037 sys.stdout.flush()
1031 1038 iolock.release()
1032 1039
1033 1040 if not options.keep_tmpdir:
1034 1041 shutil.rmtree(threadtmp, True)
1035 1042 return result
1036 1043
1037 1044 _hgpath = None
1038 1045
1039 1046 def _gethgpath():
1040 1047 """Return the path to the mercurial package that is actually found by
1041 1048 the current Python interpreter."""
1042 1049 global _hgpath
1043 1050 if _hgpath is not None:
1044 1051 return _hgpath
1045 1052
1046 1053 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1047 1054 pipe = os.popen(cmd % PYTHON)
1048 1055 try:
1049 1056 _hgpath = pipe.read().strip()
1050 1057 finally:
1051 1058 pipe.close()
1052 1059 return _hgpath
1053 1060
1054 1061 def _checkhglib(verb):
1055 1062 """Ensure that the 'mercurial' package imported by python is
1056 1063 the one we expect it to be. If not, print a warning to stderr."""
1057 1064 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1058 1065 actualhg = _gethgpath()
1059 1066 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1060 1067 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1061 1068 ' (expected %s)\n'
1062 1069 % (verb, actualhg, expecthg))
1063 1070
1064 1071 results = {'.':[], '!':[], 's':[], 'i':[]}
1065 1072 times = []
1066 1073 iolock = threading.Lock()
1067 1074 abort = False
1068 1075
1069 1076 def scheduletests(options, tests):
1070 1077 jobs = options.jobs
1071 1078 done = queue.Queue()
1072 1079 running = 0
1073 1080 count = 0
1074 1081 global abort
1075 1082
1076 1083 def job(test, count):
1077 1084 try:
1078 1085 done.put(runone(options, test, count))
1079 1086 except KeyboardInterrupt:
1080 1087 pass
1081 1088 except: # re-raises
1082 1089 done.put(('!', test, 'run-test raised an error, see traceback'))
1083 1090 raise
1084 1091
1085 1092 try:
1086 1093 while tests or running:
1087 1094 if not done.empty() or running == jobs or not tests:
1088 1095 try:
1089 1096 code, test, msg = done.get(True, 1)
1090 1097 results[code].append((test, msg))
1091 1098 if options.first and code not in '.si':
1092 1099 break
1093 1100 except queue.Empty:
1094 1101 continue
1095 1102 running -= 1
1096 1103 if tests and not running == jobs:
1097 1104 test = tests.pop(0)
1098 1105 if options.loop:
1099 1106 tests.append(test)
1100 1107 t = threading.Thread(target=job, name=test, args=(test, count))
1101 1108 t.start()
1102 1109 running += 1
1103 1110 count += 1
1104 1111 except KeyboardInterrupt:
1105 1112 abort = True
1106 1113
1107 1114 def runtests(options, tests):
1108 1115 try:
1109 1116 if INST:
1110 1117 installhg(options)
1111 1118 _checkhglib("Testing")
1112 1119 else:
1113 1120 usecorrectpython()
1114 1121
1115 1122 if options.restart:
1116 1123 orig = list(tests)
1117 1124 while tests:
1118 1125 if os.path.exists(tests[0] + ".err"):
1119 1126 break
1120 1127 tests.pop(0)
1121 1128 if not tests:
1122 1129 print "running all tests"
1123 1130 tests = orig
1124 1131
1125 1132 scheduletests(options, tests)
1126 1133
1127 1134 failed = len(results['!'])
1128 1135 tested = len(results['.']) + failed
1129 1136 skipped = len(results['s'])
1130 1137 ignored = len(results['i'])
1131 1138
1132 1139 print
1133 1140 if not options.noskips:
1134 1141 for s in results['s']:
1135 1142 print "Skipped %s: %s" % s
1136 1143 for s in results['!']:
1137 1144 print "Failed %s: %s" % s
1138 1145 _checkhglib("Tested")
1139 1146 print "# Ran %d tests, %d skipped, %d failed." % (
1140 1147 tested, skipped + ignored, failed)
1141 1148 if results['!']:
1142 1149 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1143 1150 if options.time:
1144 1151 outputtimes(options)
1145 1152
1146 1153 if options.anycoverage:
1147 1154 outputcoverage(options)
1148 1155 except KeyboardInterrupt:
1149 1156 failed = True
1150 1157 print "\ninterrupted!"
1151 1158
1152 1159 if failed:
1153 1160 sys.exit(1)
1154 1161
1155 1162 testtypes = [('.py', pytest, '.out'),
1156 1163 ('.t', tsttest, '')]
1157 1164
1158 1165 def main():
1159 1166 (options, args) = parseargs()
1160 1167 os.umask(022)
1161 1168
1162 1169 checktools()
1163 1170
1164 1171 if len(args) == 0:
1165 1172 args = [t for t in os.listdir(".")
1166 1173 if t.startswith("test-")
1167 1174 and (t.endswith(".py") or t.endswith(".t"))]
1168 1175
1169 1176 tests = args
1170 1177
1171 1178 if options.random:
1172 1179 random.shuffle(tests)
1173 1180 else:
1174 1181 # keywords for slow tests
1175 1182 slow = 'svn gendoc check-code-hg'.split()
1176 1183 def sortkey(f):
1177 1184 # run largest tests first, as they tend to take the longest
1178 1185 try:
1179 1186 val = -os.stat(f).st_size
1180 1187 except OSError, e:
1181 1188 if e.errno != errno.ENOENT:
1182 1189 raise
1183 1190 return -1e9 # file does not exist, tell early
1184 1191 for kw in slow:
1185 1192 if kw in f:
1186 1193 val *= 10
1187 1194 return val
1188 1195 tests.sort(key=sortkey)
1189 1196
1190 1197 if 'PYTHONHASHSEED' not in os.environ:
1191 1198 # use a random python hash seed all the time
1192 1199 # we do the randomness ourself to know what seed is used
1193 1200 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1194 1201
1195 1202 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1196 1203 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1197 1204 if options.tmpdir:
1198 1205 options.keep_tmpdir = True
1199 1206 tmpdir = options.tmpdir
1200 1207 if os.path.exists(tmpdir):
1201 1208 # Meaning of tmpdir has changed since 1.3: we used to create
1202 1209 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1203 1210 # tmpdir already exists.
1204 1211 sys.exit("error: temp dir %r already exists" % tmpdir)
1205 1212
1206 1213 # Automatically removing tmpdir sounds convenient, but could
1207 1214 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1208 1215 # or "--tmpdir=$HOME".
1209 1216 #vlog("# Removing temp dir", tmpdir)
1210 1217 #shutil.rmtree(tmpdir)
1211 1218 os.makedirs(tmpdir)
1212 1219 else:
1213 1220 d = None
1214 1221 if os.name == 'nt':
1215 1222 # without this, we get the default temp dir location, but
1216 1223 # in all lowercase, which causes troubles with paths (issue3490)
1217 1224 d = os.getenv('TMP')
1218 1225 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1219 1226 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1220 1227
1221 1228 if options.with_hg:
1222 1229 INST = None
1223 1230 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1224 1231
1225 1232 # This looks redundant with how Python initializes sys.path from
1226 1233 # the location of the script being executed. Needed because the
1227 1234 # "hg" specified by --with-hg is not the only Python script
1228 1235 # executed in the test suite that needs to import 'mercurial'
1229 1236 # ... which means it's not really redundant at all.
1230 1237 PYTHONDIR = BINDIR
1231 1238 else:
1232 1239 INST = os.path.join(HGTMP, "install")
1233 1240 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1234 1241 PYTHONDIR = os.path.join(INST, "lib", "python")
1235 1242
1236 1243 os.environ["BINDIR"] = BINDIR
1237 1244 os.environ["PYTHON"] = PYTHON
1238 1245
1239 1246 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1240 1247 os.environ["PATH"] = os.pathsep.join(path)
1241 1248
1242 1249 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1243 1250 # can run .../tests/run-tests.py test-foo where test-foo
1244 1251 # adds an extension to HGRC
1245 1252 pypath = [PYTHONDIR, TESTDIR]
1246 1253 # We have to augment PYTHONPATH, rather than simply replacing
1247 1254 # it, in case external libraries are only available via current
1248 1255 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1249 1256 # are in /opt/subversion.)
1250 1257 oldpypath = os.environ.get(IMPL_PATH)
1251 1258 if oldpypath:
1252 1259 pypath.append(oldpypath)
1253 1260 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1254 1261
1255 1262 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1256 1263
1257 1264 vlog("# Using TESTDIR", TESTDIR)
1258 1265 vlog("# Using HGTMP", HGTMP)
1259 1266 vlog("# Using PATH", os.environ["PATH"])
1260 1267 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1261 1268
1262 1269 try:
1263 1270 runtests(options, tests)
1264 1271 finally:
1265 1272 time.sleep(.1)
1266 1273 cleanup(options)
1267 1274
1268 1275 if __name__ == '__main__':
1269 1276 main()
@@ -1,83 +1,86 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 return bool(match)
30 if isinstance(match, str):
31 return 'special: ' + match
32 else:
33 return bool(match) # do not return match object
31 34
32 35 def wintests():
33 36 r"""test matching like running on windows
34 37
35 38 enable windows matching on any os
36 39 >>> _osaltsep = os.altsep
37 40 >>> os.altsep = True
38 41
39 42 valid match on windows
40 43 >>> lm('g/a*/d (glob)\n', 'g\\abc/d\n')
41 44 True
42 45
43 46 direct matching, glob unnecessary
44 47 >>> lm('g/b (glob)\n', 'g/b\n')
45 48 <BLANKLINE>
46 49 Info, unnecessary glob: g/b (glob)
47 50 True
48 51
49 52 missing glob
50 53 >>> lm('/g/c/d/fg\n', '\\g\\c\\d/fg\n')
51 False
54 'special: +glob'
52 55
53 56 restore os.altsep
54 57 >>> os.altsep = _osaltsep
55 58 """
56 59 os.altsep # for pyflakes, because it does not see os in the doctest
57 60
58 61 def otherostests():
59 62 r"""test matching like running on non-windows os
60 63
61 64 disable windows matching on any os
62 65 >>> _osaltsep = os.altsep
63 66 >>> os.altsep = False
64 67
65 68 backslash does not match slash
66 69 >>> lm('h/a* (glob)\n', 'h\\ab\n')
67 70 False
68 71
69 72 direct matching glob can not be recognized
70 73 >>> lm('h/b (glob)\n', 'h/b\n')
71 74 True
72 75
73 76 missing glob can not not be recognized
74 77 >>> lm('/h/c/df/g/\n', '\\h/c\\df/g\\\n')
75 78 False
76 79
77 80 restore os.altsep
78 81 >>> os.altsep = _osaltsep
79 82 """
80 83 pass
81 84
82 85 if __name__ == '__main__':
83 86 doctest.testmod()
General Comments 0
You need to be logged in to leave comments. Login now