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