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