##// END OF EJS Templates
run-tests: support multiple cases in .t test...
Jun Wu -
r32317:7340465b default
parent child Browse files
Show More
@@ -1,2667 +1,2734 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 # 10) parallel, pure, tests that call run-tests:
39 39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 40 #
41 41 # (You could use any subset of the tests: test-s* happens to match
42 42 # enough that it's worth doing parallel runs, few enough that it
43 43 # completes fairly quickly, includes both shell and Python scripts, and
44 44 # includes some scripts that run daemon processes.)
45 45
46 46 from __future__ import absolute_import, print_function
47 47
48 48 import difflib
49 49 import distutils.version as version
50 50 import errno
51 51 import json
52 52 import optparse
53 53 import os
54 54 import random
55 55 import re
56 56 import shutil
57 57 import signal
58 58 import socket
59 59 import subprocess
60 60 import sys
61 61 import sysconfig
62 62 import tempfile
63 63 import threading
64 64 import time
65 65 import unittest
66 66 import xml.dom.minidom as minidom
67 67
68 68 try:
69 69 import Queue as queue
70 70 except ImportError:
71 71 import queue
72 72
73 73 if os.environ.get('RTUNICODEPEDANTRY', False):
74 74 try:
75 75 reload(sys)
76 76 sys.setdefaultencoding("undefined")
77 77 except NameError:
78 78 pass
79 79
80 80 osenvironb = getattr(os, 'environb', os.environ)
81 81 processlock = threading.Lock()
82 82
83 83 if sys.version_info > (3, 5, 0):
84 84 PYTHON3 = True
85 85 xrange = range # we use xrange in one place, and we'd rather not use range
86 86 def _bytespath(p):
87 87 return p.encode('utf-8')
88 88
89 89 def _strpath(p):
90 90 return p.decode('utf-8')
91 91
92 92 elif sys.version_info >= (3, 0, 0):
93 93 print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' %
94 94 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
95 95 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
96 96 else:
97 97 PYTHON3 = False
98 98
99 99 # In python 2.x, path operations are generally done using
100 100 # bytestrings by default, so we don't have to do any extra
101 101 # fiddling there. We define the wrapper functions anyway just to
102 102 # help keep code consistent between platforms.
103 103 def _bytespath(p):
104 104 return p
105 105
106 106 _strpath = _bytespath
107 107
108 108 # For Windows support
109 109 wifexited = getattr(os, "WIFEXITED", lambda x: False)
110 110
111 111 # Whether to use IPv6
112 112 def checksocketfamily(name, port=20058):
113 113 """return true if we can listen on localhost using family=name
114 114
115 115 name should be either 'AF_INET', or 'AF_INET6'.
116 116 port being used is okay - EADDRINUSE is considered as successful.
117 117 """
118 118 family = getattr(socket, name, None)
119 119 if family is None:
120 120 return False
121 121 try:
122 122 s = socket.socket(family, socket.SOCK_STREAM)
123 123 s.bind(('localhost', port))
124 124 s.close()
125 125 return True
126 126 except socket.error as exc:
127 127 if exc.errno == errno.EADDRINUSE:
128 128 return True
129 129 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
130 130 return False
131 131 else:
132 132 raise
133 133 else:
134 134 return False
135 135
136 136 # useipv6 will be set by parseargs
137 137 useipv6 = None
138 138
139 139 def checkportisavailable(port):
140 140 """return true if a port seems free to bind on localhost"""
141 141 if useipv6:
142 142 family = socket.AF_INET6
143 143 else:
144 144 family = socket.AF_INET
145 145 try:
146 146 s = socket.socket(family, socket.SOCK_STREAM)
147 147 s.bind(('localhost', port))
148 148 s.close()
149 149 return True
150 150 except socket.error as exc:
151 151 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
152 152 errno.EPROTONOSUPPORT):
153 153 raise
154 154 return False
155 155
156 156 closefds = os.name == 'posix'
157 157 def Popen4(cmd, wd, timeout, env=None):
158 158 processlock.acquire()
159 159 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
160 160 close_fds=closefds,
161 161 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
162 162 stderr=subprocess.STDOUT)
163 163 processlock.release()
164 164
165 165 p.fromchild = p.stdout
166 166 p.tochild = p.stdin
167 167 p.childerr = p.stderr
168 168
169 169 p.timeout = False
170 170 if timeout:
171 171 def t():
172 172 start = time.time()
173 173 while time.time() - start < timeout and p.returncode is None:
174 174 time.sleep(.1)
175 175 p.timeout = True
176 176 if p.returncode is None:
177 177 terminate(p)
178 178 threading.Thread(target=t).start()
179 179
180 180 return p
181 181
182 182 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
183 183 IMPL_PATH = b'PYTHONPATH'
184 184 if 'java' in sys.platform:
185 185 IMPL_PATH = b'JYTHONPATH'
186 186
187 187 defaults = {
188 188 'jobs': ('HGTEST_JOBS', 1),
189 189 'timeout': ('HGTEST_TIMEOUT', 180),
190 190 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
191 191 'port': ('HGTEST_PORT', 20059),
192 192 'shell': ('HGTEST_SHELL', 'sh'),
193 193 }
194 194
195 195 def canonpath(path):
196 196 return os.path.realpath(os.path.expanduser(path))
197 197
198 198 def parselistfiles(files, listtype, warn=True):
199 199 entries = dict()
200 200 for filename in files:
201 201 try:
202 202 path = os.path.expanduser(os.path.expandvars(filename))
203 203 f = open(path, "rb")
204 204 except IOError as err:
205 205 if err.errno != errno.ENOENT:
206 206 raise
207 207 if warn:
208 208 print("warning: no such %s file: %s" % (listtype, filename))
209 209 continue
210 210
211 211 for line in f.readlines():
212 212 line = line.split(b'#', 1)[0].strip()
213 213 if line:
214 214 entries[line] = filename
215 215
216 216 f.close()
217 217 return entries
218 218
219 def parsettestcases(path):
220 """read a .t test file, return a set of test case names
221
222 If path does not exist, return an empty set.
223 """
224 cases = set()
225 try:
226 with open(path, 'rb') as f:
227 for l in f:
228 if l.startswith(b'#testcases '):
229 cases.update(l[11:].split())
230 except IOError as ex:
231 if ex.errno != errno.ENOENT:
232 raise
233 return cases
234
219 235 def getparser():
220 236 """Obtain the OptionParser used by the CLI."""
221 237 parser = optparse.OptionParser("%prog [options] [tests]")
222 238
223 239 # keep these sorted
224 240 parser.add_option("--blacklist", action="append",
225 241 help="skip tests listed in the specified blacklist file")
226 242 parser.add_option("--whitelist", action="append",
227 243 help="always run tests listed in the specified whitelist file")
228 244 parser.add_option("--changed", type="string",
229 245 help="run tests that are changed in parent rev or working directory")
230 246 parser.add_option("-C", "--annotate", action="store_true",
231 247 help="output files annotated with coverage")
232 248 parser.add_option("-c", "--cover", action="store_true",
233 249 help="print a test coverage report")
234 250 parser.add_option("-d", "--debug", action="store_true",
235 251 help="debug mode: write output of test scripts to console"
236 252 " rather than capturing and diffing it (disables timeout)")
237 253 parser.add_option("-f", "--first", action="store_true",
238 254 help="exit on the first test failure")
239 255 parser.add_option("-H", "--htmlcov", action="store_true",
240 256 help="create an HTML report of the coverage of the files")
241 257 parser.add_option("-i", "--interactive", action="store_true",
242 258 help="prompt to accept changed output")
243 259 parser.add_option("-j", "--jobs", type="int",
244 260 help="number of jobs to run in parallel"
245 261 " (default: $%s or %d)" % defaults['jobs'])
246 262 parser.add_option("--keep-tmpdir", action="store_true",
247 263 help="keep temporary directory after running tests")
248 264 parser.add_option("-k", "--keywords",
249 265 help="run tests matching keywords")
250 266 parser.add_option("-l", "--local", action="store_true",
251 267 help="shortcut for --with-hg=<testdir>/../hg, "
252 268 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
253 269 parser.add_option("--loop", action="store_true",
254 270 help="loop tests repeatedly")
255 271 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
256 272 help="run each test N times (default=1)", default=1)
257 273 parser.add_option("-n", "--nodiff", action="store_true",
258 274 help="skip showing test changes")
259 275 parser.add_option("-p", "--port", type="int",
260 276 help="port on which servers should listen"
261 277 " (default: $%s or %d)" % defaults['port'])
262 278 parser.add_option("--compiler", type="string",
263 279 help="compiler to build with")
264 280 parser.add_option("--pure", action="store_true",
265 281 help="use pure Python code instead of C extensions")
266 282 parser.add_option("-R", "--restart", action="store_true",
267 283 help="restart at last error")
268 284 parser.add_option("-r", "--retest", action="store_true",
269 285 help="retest failed tests")
270 286 parser.add_option("-S", "--noskips", action="store_true",
271 287 help="don't report skip tests verbosely")
272 288 parser.add_option("--shell", type="string",
273 289 help="shell to use (default: $%s or %s)" % defaults['shell'])
274 290 parser.add_option("-t", "--timeout", type="int",
275 291 help="kill errant tests after TIMEOUT seconds"
276 292 " (default: $%s or %d)" % defaults['timeout'])
277 293 parser.add_option("--slowtimeout", type="int",
278 294 help="kill errant slow tests after SLOWTIMEOUT seconds"
279 295 " (default: $%s or %d)" % defaults['slowtimeout'])
280 296 parser.add_option("--time", action="store_true",
281 297 help="time how long each test takes")
282 298 parser.add_option("--json", action="store_true",
283 299 help="store test result data in 'report.json' file")
284 300 parser.add_option("--tmpdir", type="string",
285 301 help="run tests in the given temporary directory"
286 302 " (implies --keep-tmpdir)")
287 303 parser.add_option("-v", "--verbose", action="store_true",
288 304 help="output verbose messages")
289 305 parser.add_option("--xunit", type="string",
290 306 help="record xunit results at specified path")
291 307 parser.add_option("--view", type="string",
292 308 help="external diff viewer")
293 309 parser.add_option("--with-hg", type="string",
294 310 metavar="HG",
295 311 help="test using specified hg script rather than a "
296 312 "temporary installation")
297 313 parser.add_option("--chg", action="store_true",
298 314 help="install and use chg wrapper in place of hg")
299 315 parser.add_option("--with-chg", metavar="CHG",
300 316 help="use specified chg wrapper in place of hg")
301 317 parser.add_option("--ipv6", action="store_true",
302 318 help="prefer IPv6 to IPv4 for network related tests")
303 319 parser.add_option("-3", "--py3k-warnings", action="store_true",
304 320 help="enable Py3k warnings on Python 2.6+")
305 321 # This option should be deleted once test-check-py3-compat.t and other
306 322 # Python 3 tests run with Python 3.
307 323 parser.add_option("--with-python3", metavar="PYTHON3",
308 324 help="Python 3 interpreter (if running under Python 2)"
309 325 " (TEMPORARY)")
310 326 parser.add_option('--extra-config-opt', action="append",
311 327 help='set the given config opt in the test hgrc')
312 328 parser.add_option('--random', action="store_true",
313 329 help='run tests in random order')
314 330 parser.add_option('--profile-runner', action='store_true',
315 331 help='run statprof on run-tests')
316 332 parser.add_option('--allow-slow-tests', action='store_true',
317 333 help='allow extremely slow tests')
318 334 parser.add_option('--showchannels', action='store_true',
319 335 help='show scheduling channels')
320 336 parser.add_option('--known-good-rev', type="string",
321 337 metavar="known_good_rev",
322 338 help=("Automatically bisect any failures using this "
323 339 "revision as a known-good revision."))
324 340
325 341 for option, (envvar, default) in defaults.items():
326 342 defaults[option] = type(default)(os.environ.get(envvar, default))
327 343 parser.set_defaults(**defaults)
328 344
329 345 return parser
330 346
331 347 def parseargs(args, parser):
332 348 """Parse arguments with our OptionParser and validate results."""
333 349 (options, args) = parser.parse_args(args)
334 350
335 351 # jython is always pure
336 352 if 'java' in sys.platform or '__pypy__' in sys.modules:
337 353 options.pure = True
338 354
339 355 if options.with_hg:
340 356 options.with_hg = canonpath(_bytespath(options.with_hg))
341 357 if not (os.path.isfile(options.with_hg) and
342 358 os.access(options.with_hg, os.X_OK)):
343 359 parser.error('--with-hg must specify an executable hg script')
344 360 if not os.path.basename(options.with_hg) == b'hg':
345 361 sys.stderr.write('warning: --with-hg should specify an hg script\n')
346 362 if options.local:
347 363 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
348 364 reporootdir = os.path.dirname(testdir)
349 365 pathandattrs = [(b'hg', 'with_hg')]
350 366 if options.chg:
351 367 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
352 368 for relpath, attr in pathandattrs:
353 369 binpath = os.path.join(reporootdir, relpath)
354 370 if os.name != 'nt' and not os.access(binpath, os.X_OK):
355 371 parser.error('--local specified, but %r not found or '
356 372 'not executable' % binpath)
357 373 setattr(options, attr, binpath)
358 374
359 375 if (options.chg or options.with_chg) and os.name == 'nt':
360 376 parser.error('chg does not work on %s' % os.name)
361 377 if options.with_chg:
362 378 options.chg = False # no installation to temporary location
363 379 options.with_chg = canonpath(_bytespath(options.with_chg))
364 380 if not (os.path.isfile(options.with_chg) and
365 381 os.access(options.with_chg, os.X_OK)):
366 382 parser.error('--with-chg must specify a chg executable')
367 383 if options.chg and options.with_hg:
368 384 # chg shares installation location with hg
369 385 parser.error('--chg does not work when --with-hg is specified '
370 386 '(use --with-chg instead)')
371 387
372 388 global useipv6
373 389 if options.ipv6:
374 390 useipv6 = checksocketfamily('AF_INET6')
375 391 else:
376 392 # only use IPv6 if IPv4 is unavailable and IPv6 is available
377 393 useipv6 = ((not checksocketfamily('AF_INET'))
378 394 and checksocketfamily('AF_INET6'))
379 395
380 396 options.anycoverage = options.cover or options.annotate or options.htmlcov
381 397 if options.anycoverage:
382 398 try:
383 399 import coverage
384 400 covver = version.StrictVersion(coverage.__version__).version
385 401 if covver < (3, 3):
386 402 parser.error('coverage options require coverage 3.3 or later')
387 403 except ImportError:
388 404 parser.error('coverage options now require the coverage package')
389 405
390 406 if options.anycoverage and options.local:
391 407 # this needs some path mangling somewhere, I guess
392 408 parser.error("sorry, coverage options do not work when --local "
393 409 "is specified")
394 410
395 411 if options.anycoverage and options.with_hg:
396 412 parser.error("sorry, coverage options do not work when --with-hg "
397 413 "is specified")
398 414
399 415 global verbose
400 416 if options.verbose:
401 417 verbose = ''
402 418
403 419 if options.tmpdir:
404 420 options.tmpdir = canonpath(options.tmpdir)
405 421
406 422 if options.jobs < 1:
407 423 parser.error('--jobs must be positive')
408 424 if options.interactive and options.debug:
409 425 parser.error("-i/--interactive and -d/--debug are incompatible")
410 426 if options.debug:
411 427 if options.timeout != defaults['timeout']:
412 428 sys.stderr.write(
413 429 'warning: --timeout option ignored with --debug\n')
414 430 if options.slowtimeout != defaults['slowtimeout']:
415 431 sys.stderr.write(
416 432 'warning: --slowtimeout option ignored with --debug\n')
417 433 options.timeout = 0
418 434 options.slowtimeout = 0
419 435 if options.py3k_warnings:
420 436 if PYTHON3:
421 437 parser.error(
422 438 '--py3k-warnings can only be used on Python 2.6 and 2.7')
423 439 if options.with_python3:
424 440 if PYTHON3:
425 441 parser.error('--with-python3 cannot be used when executing with '
426 442 'Python 3')
427 443
428 444 options.with_python3 = canonpath(options.with_python3)
429 445 # Verify Python3 executable is acceptable.
430 446 proc = subprocess.Popen([options.with_python3, b'--version'],
431 447 stdout=subprocess.PIPE,
432 448 stderr=subprocess.STDOUT)
433 449 out, _err = proc.communicate()
434 450 ret = proc.wait()
435 451 if ret != 0:
436 452 parser.error('could not determine version of python 3')
437 453 if not out.startswith('Python '):
438 454 parser.error('unexpected output from python3 --version: %s' %
439 455 out)
440 456 vers = version.LooseVersion(out[len('Python '):])
441 457 if vers < version.LooseVersion('3.5.0'):
442 458 parser.error('--with-python3 version must be 3.5.0 or greater; '
443 459 'got %s' % out)
444 460
445 461 if options.blacklist:
446 462 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
447 463 if options.whitelist:
448 464 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
449 465 else:
450 466 options.whitelisted = {}
451 467
452 468 if options.showchannels:
453 469 options.nodiff = True
454 470
455 471 return (options, args)
456 472
457 473 def rename(src, dst):
458 474 """Like os.rename(), trade atomicity and opened files friendliness
459 475 for existing destination support.
460 476 """
461 477 shutil.copy(src, dst)
462 478 os.remove(src)
463 479
464 480 _unified_diff = difflib.unified_diff
465 481 if PYTHON3:
466 482 import functools
467 483 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
468 484
469 485 def getdiff(expected, output, ref, err):
470 486 servefail = False
471 487 lines = []
472 488 for line in _unified_diff(expected, output, ref, err):
473 489 if line.startswith(b'+++') or line.startswith(b'---'):
474 490 line = line.replace(b'\\', b'/')
475 491 if line.endswith(b' \n'):
476 492 line = line[:-2] + b'\n'
477 493 lines.append(line)
478 494 if not servefail and line.startswith(
479 495 b'+ abort: child process failed to start'):
480 496 servefail = True
481 497
482 498 return servefail, lines
483 499
484 500 verbose = False
485 501 def vlog(*msg):
486 502 """Log only when in verbose mode."""
487 503 if verbose is False:
488 504 return
489 505
490 506 return log(*msg)
491 507
492 508 # Bytes that break XML even in a CDATA block: control characters 0-31
493 509 # sans \t, \n and \r
494 510 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
495 511
496 512 # Match feature conditionalized output lines in the form, capturing the feature
497 513 # list in group 2, and the preceeding line output in group 1:
498 514 #
499 515 # output..output (feature !)\n
500 516 optline = re.compile(b'(.+) \((.+?) !\)\n$')
501 517
502 518 def cdatasafe(data):
503 519 """Make a string safe to include in a CDATA block.
504 520
505 521 Certain control characters are illegal in a CDATA block, and
506 522 there's no way to include a ]]> in a CDATA either. This function
507 523 replaces illegal bytes with ? and adds a space between the ]] so
508 524 that it won't break the CDATA block.
509 525 """
510 526 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
511 527
512 528 def log(*msg):
513 529 """Log something to stdout.
514 530
515 531 Arguments are strings to print.
516 532 """
517 533 with iolock:
518 534 if verbose:
519 535 print(verbose, end=' ')
520 536 for m in msg:
521 537 print(m, end=' ')
522 538 print()
523 539 sys.stdout.flush()
524 540
525 541 def terminate(proc):
526 542 """Terminate subprocess"""
527 543 vlog('# Terminating process %d' % proc.pid)
528 544 try:
529 545 proc.terminate()
530 546 except OSError:
531 547 pass
532 548
533 549 def killdaemons(pidfile):
534 550 import killdaemons as killmod
535 551 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
536 552 logfn=vlog)
537 553
538 554 class Test(unittest.TestCase):
539 555 """Encapsulates a single, runnable test.
540 556
541 557 While this class conforms to the unittest.TestCase API, it differs in that
542 558 instances need to be instantiated manually. (Typically, unittest.TestCase
543 559 classes are instantiated automatically by scanning modules.)
544 560 """
545 561
546 562 # Status code reserved for skipped tests (used by hghave).
547 563 SKIPPED_STATUS = 80
548 564
549 565 def __init__(self, path, tmpdir, keeptmpdir=False,
550 566 debug=False,
551 567 timeout=defaults['timeout'],
552 568 startport=defaults['port'], extraconfigopts=None,
553 569 py3kwarnings=False, shell=None, hgcommand=None,
554 570 slowtimeout=defaults['slowtimeout'], usechg=False,
555 571 useipv6=False):
556 572 """Create a test from parameters.
557 573
558 574 path is the full path to the file defining the test.
559 575
560 576 tmpdir is the main temporary directory to use for this test.
561 577
562 578 keeptmpdir determines whether to keep the test's temporary directory
563 579 after execution. It defaults to removal (False).
564 580
565 581 debug mode will make the test execute verbosely, with unfiltered
566 582 output.
567 583
568 584 timeout controls the maximum run time of the test. It is ignored when
569 585 debug is True. See slowtimeout for tests with #require slow.
570 586
571 587 slowtimeout overrides timeout if the test has #require slow.
572 588
573 589 startport controls the starting port number to use for this test. Each
574 590 test will reserve 3 port numbers for execution. It is the caller's
575 591 responsibility to allocate a non-overlapping port range to Test
576 592 instances.
577 593
578 594 extraconfigopts is an iterable of extra hgrc config options. Values
579 595 must have the form "key=value" (something understood by hgrc). Values
580 596 of the form "foo.key=value" will result in "[foo] key=value".
581 597
582 598 py3kwarnings enables Py3k warnings.
583 599
584 600 shell is the shell to execute tests in.
585 601 """
586 602 self.path = path
587 603 self.bname = os.path.basename(path)
588 604 self.name = _strpath(self.bname)
589 605 self._testdir = os.path.dirname(path)
606 self._tmpname = os.path.basename(path)
590 607 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
591 608
592 609 self._threadtmp = tmpdir
593 610 self._keeptmpdir = keeptmpdir
594 611 self._debug = debug
595 612 self._timeout = timeout
596 613 self._slowtimeout = slowtimeout
597 614 self._startport = startport
598 615 self._extraconfigopts = extraconfigopts or []
599 616 self._py3kwarnings = py3kwarnings
600 617 self._shell = _bytespath(shell)
601 618 self._hgcommand = hgcommand or b'hg'
602 619 self._usechg = usechg
603 620 self._useipv6 = useipv6
604 621
605 622 self._aborted = False
606 623 self._daemonpids = []
607 624 self._finished = None
608 625 self._ret = None
609 626 self._out = None
610 627 self._skipped = None
611 628 self._testtmp = None
612 629 self._chgsockdir = None
613 630
614 631 # If we're not in --debug mode and reference output file exists,
615 632 # check test output against it.
616 633 if debug:
617 634 self._refout = None # to match "out is None"
618 635 elif os.path.exists(self.refpath):
619 636 f = open(self.refpath, 'rb')
620 637 self._refout = f.read().splitlines(True)
621 638 f.close()
622 639 else:
623 640 self._refout = []
624 641
625 642 # needed to get base class __repr__ running
626 643 @property
627 644 def _testMethodName(self):
628 645 return self.name
629 646
630 647 def __str__(self):
631 648 return self.name
632 649
633 650 def shortDescription(self):
634 651 return self.name
635 652
636 653 def setUp(self):
637 654 """Tasks to perform before run()."""
638 655 self._finished = False
639 656 self._ret = None
640 657 self._out = None
641 658 self._skipped = None
642 659
643 660 try:
644 661 os.mkdir(self._threadtmp)
645 662 except OSError as e:
646 663 if e.errno != errno.EEXIST:
647 664 raise
648 665
649 name = os.path.basename(self.path)
666 name = self._tmpname
650 667 self._testtmp = os.path.join(self._threadtmp, name)
651 668 os.mkdir(self._testtmp)
652 669
653 670 # Remove any previous output files.
654 671 if os.path.exists(self.errpath):
655 672 try:
656 673 os.remove(self.errpath)
657 674 except OSError as e:
658 675 # We might have raced another test to clean up a .err
659 676 # file, so ignore ENOENT when removing a previous .err
660 677 # file.
661 678 if e.errno != errno.ENOENT:
662 679 raise
663 680
664 681 if self._usechg:
665 682 self._chgsockdir = os.path.join(self._threadtmp,
666 683 b'%s.chgsock' % name)
667 684 os.mkdir(self._chgsockdir)
668 685
669 686 def run(self, result):
670 687 """Run this test and report results against a TestResult instance."""
671 688 # This function is extremely similar to unittest.TestCase.run(). Once
672 689 # we require Python 2.7 (or at least its version of unittest), this
673 690 # function can largely go away.
674 691 self._result = result
675 692 result.startTest(self)
676 693 try:
677 694 try:
678 695 self.setUp()
679 696 except (KeyboardInterrupt, SystemExit):
680 697 self._aborted = True
681 698 raise
682 699 except Exception:
683 700 result.addError(self, sys.exc_info())
684 701 return
685 702
686 703 success = False
687 704 try:
688 705 self.runTest()
689 706 except KeyboardInterrupt:
690 707 self._aborted = True
691 708 raise
692 709 except SkipTest as e:
693 710 result.addSkip(self, str(e))
694 711 # The base class will have already counted this as a
695 712 # test we "ran", but we want to exclude skipped tests
696 713 # from those we count towards those run.
697 714 result.testsRun -= 1
698 715 except IgnoreTest as e:
699 716 result.addIgnore(self, str(e))
700 717 # As with skips, ignores also should be excluded from
701 718 # the number of tests executed.
702 719 result.testsRun -= 1
703 720 except WarnTest as e:
704 721 result.addWarn(self, str(e))
705 722 except ReportedTest as e:
706 723 pass
707 724 except self.failureException as e:
708 725 # This differs from unittest in that we don't capture
709 726 # the stack trace. This is for historical reasons and
710 727 # this decision could be revisited in the future,
711 728 # especially for PythonTest instances.
712 729 if result.addFailure(self, str(e)):
713 730 success = True
714 731 except Exception:
715 732 result.addError(self, sys.exc_info())
716 733 else:
717 734 success = True
718 735
719 736 try:
720 737 self.tearDown()
721 738 except (KeyboardInterrupt, SystemExit):
722 739 self._aborted = True
723 740 raise
724 741 except Exception:
725 742 result.addError(self, sys.exc_info())
726 743 success = False
727 744
728 745 if success:
729 746 result.addSuccess(self)
730 747 finally:
731 748 result.stopTest(self, interrupted=self._aborted)
732 749
733 750 def runTest(self):
734 751 """Run this test instance.
735 752
736 753 This will return a tuple describing the result of the test.
737 754 """
738 755 env = self._getenv()
739 756 self._daemonpids.append(env['DAEMON_PIDS'])
740 757 self._createhgrc(env['HGRCPATH'])
741 758
742 759 vlog('# Test', self.name)
743 760
744 761 ret, out = self._run(env)
745 762 self._finished = True
746 763 self._ret = ret
747 764 self._out = out
748 765
749 766 def describe(ret):
750 767 if ret < 0:
751 768 return 'killed by signal: %d' % -ret
752 769 return 'returned error code %d' % ret
753 770
754 771 self._skipped = False
755 772
756 773 if ret == self.SKIPPED_STATUS:
757 774 if out is None: # Debug mode, nothing to parse.
758 775 missing = ['unknown']
759 776 failed = None
760 777 else:
761 778 missing, failed = TTest.parsehghaveoutput(out)
762 779
763 780 if not missing:
764 781 missing = ['skipped']
765 782
766 783 if failed:
767 784 self.fail('hg have failed checking for %s' % failed[-1])
768 785 else:
769 786 self._skipped = True
770 787 raise SkipTest(missing[-1])
771 788 elif ret == 'timeout':
772 789 self.fail('timed out')
773 790 elif ret is False:
774 791 raise WarnTest('no result code from test')
775 792 elif out != self._refout:
776 793 # Diff generation may rely on written .err file.
777 794 if (ret != 0 or out != self._refout) and not self._skipped \
778 795 and not self._debug:
779 796 f = open(self.errpath, 'wb')
780 797 for line in out:
781 798 f.write(line)
782 799 f.close()
783 800
784 801 # The result object handles diff calculation for us.
785 802 if self._result.addOutputMismatch(self, ret, out, self._refout):
786 803 # change was accepted, skip failing
787 804 return
788 805
789 806 if ret:
790 807 msg = 'output changed and ' + describe(ret)
791 808 else:
792 809 msg = 'output changed'
793 810
794 811 self.fail(msg)
795 812 elif ret:
796 813 self.fail(describe(ret))
797 814
798 815 def tearDown(self):
799 816 """Tasks to perform after run()."""
800 817 for entry in self._daemonpids:
801 818 killdaemons(entry)
802 819 self._daemonpids = []
803 820
804 821 if self._keeptmpdir:
805 822 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
806 823 (self._testtmp.decode('utf-8'),
807 824 self._threadtmp.decode('utf-8')))
808 825 else:
809 826 shutil.rmtree(self._testtmp, True)
810 827 shutil.rmtree(self._threadtmp, True)
811 828
812 829 if self._usechg:
813 830 # chgservers will stop automatically after they find the socket
814 831 # files are deleted
815 832 shutil.rmtree(self._chgsockdir, True)
816 833
817 834 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
818 835 and not self._debug and self._out:
819 836 f = open(self.errpath, 'wb')
820 837 for line in self._out:
821 838 f.write(line)
822 839 f.close()
823 840
824 841 vlog("# Ret was:", self._ret, '(%s)' % self.name)
825 842
826 843 def _run(self, env):
827 844 # This should be implemented in child classes to run tests.
828 845 raise SkipTest('unknown test type')
829 846
830 847 def abort(self):
831 848 """Terminate execution of this test."""
832 849 self._aborted = True
833 850
834 851 def _portmap(self, i):
835 852 offset = b'' if i == 0 else b'%d' % i
836 853 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
837 854
838 855 def _getreplacements(self):
839 856 """Obtain a mapping of text replacements to apply to test output.
840 857
841 858 Test output needs to be normalized so it can be compared to expected
842 859 output. This function defines how some of that normalization will
843 860 occur.
844 861 """
845 862 r = [
846 863 # This list should be parallel to defineport in _getenv
847 864 self._portmap(0),
848 865 self._portmap(1),
849 866 self._portmap(2),
850 867 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
851 868 br'\1 (glob)'),
852 869 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
853 870 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
854 871 ]
855 872 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
856 873
857 874 return r
858 875
859 876 def _escapepath(self, p):
860 877 if os.name == 'nt':
861 878 return (
862 879 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
863 880 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
864 881 for c in p))
865 882 )
866 883 else:
867 884 return re.escape(p)
868 885
869 886 def _localip(self):
870 887 if self._useipv6:
871 888 return b'::1'
872 889 else:
873 890 return b'127.0.0.1'
874 891
875 892 def _getenv(self):
876 893 """Obtain environment variables to use during test execution."""
877 894 def defineport(i):
878 895 offset = '' if i == 0 else '%s' % i
879 896 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
880 897 env = os.environ.copy()
881 898 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
882 899 env['HGEMITWARNINGS'] = '1'
883 900 env['TESTTMP'] = self._testtmp
884 901 env['HOME'] = self._testtmp
885 902 # This number should match portneeded in _getport
886 903 for port in xrange(3):
887 904 # This list should be parallel to _portmap in _getreplacements
888 905 defineport(port)
889 906 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
890 907 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
891 908 env["HGEDITOR"] = ('"' + sys.executable + '"'
892 909 + ' -c "import sys; sys.exit(0)"')
893 910 env["HGMERGE"] = "internal:merge"
894 911 env["HGUSER"] = "test"
895 912 env["HGENCODING"] = "ascii"
896 913 env["HGENCODINGMODE"] = "strict"
897 914 env['HGIPV6'] = str(int(self._useipv6))
898 915
899 916 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
900 917 # IP addresses.
901 918 env['LOCALIP'] = self._localip()
902 919
903 920 # Reset some environment variables to well-known values so that
904 921 # the tests produce repeatable output.
905 922 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
906 923 env['TZ'] = 'GMT'
907 924 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
908 925 env['COLUMNS'] = '80'
909 926 env['TERM'] = 'xterm'
910 927
911 928 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
912 929 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
913 930 'NO_PROXY CHGDEBUG').split():
914 931 if k in env:
915 932 del env[k]
916 933
917 934 # unset env related to hooks
918 935 for k in env.keys():
919 936 if k.startswith('HG_'):
920 937 del env[k]
921 938
922 939 if self._usechg:
923 940 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
924 941
925 942 return env
926 943
927 944 def _createhgrc(self, path):
928 945 """Create an hgrc file for this test."""
929 946 hgrc = open(path, 'wb')
930 947 hgrc.write(b'[ui]\n')
931 948 hgrc.write(b'slash = True\n')
932 949 hgrc.write(b'interactive = False\n')
933 950 hgrc.write(b'mergemarkers = detailed\n')
934 951 hgrc.write(b'promptecho = True\n')
935 952 hgrc.write(b'[defaults]\n')
936 953 hgrc.write(b'backout = -d "0 0"\n')
937 954 hgrc.write(b'commit = -d "0 0"\n')
938 955 hgrc.write(b'shelve = --date "0 0"\n')
939 956 hgrc.write(b'tag = -d "0 0"\n')
940 957 hgrc.write(b'[devel]\n')
941 958 hgrc.write(b'all-warnings = true\n')
942 959 hgrc.write(b'[largefiles]\n')
943 960 hgrc.write(b'usercache = %s\n' %
944 961 (os.path.join(self._testtmp, b'.cache/largefiles')))
945 962 hgrc.write(b'[web]\n')
946 963 hgrc.write(b'address = localhost\n')
947 964 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
948 965
949 966 for opt in self._extraconfigopts:
950 967 section, key = opt.split('.', 1)
951 968 assert '=' in key, ('extra config opt %s must '
952 969 'have an = for assignment' % opt)
953 970 hgrc.write(b'[%s]\n%s\n' % (section, key))
954 971 hgrc.close()
955 972
956 973 def fail(self, msg):
957 974 # unittest differentiates between errored and failed.
958 975 # Failed is denoted by AssertionError (by default at least).
959 976 raise AssertionError(msg)
960 977
961 978 def _runcommand(self, cmd, env, normalizenewlines=False):
962 979 """Run command in a sub-process, capturing the output (stdout and
963 980 stderr).
964 981
965 982 Return a tuple (exitcode, output). output is None in debug mode.
966 983 """
967 984 if self._debug:
968 985 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
969 986 env=env)
970 987 ret = proc.wait()
971 988 return (ret, None)
972 989
973 990 proc = Popen4(cmd, self._testtmp, self._timeout, env)
974 991 def cleanup():
975 992 terminate(proc)
976 993 ret = proc.wait()
977 994 if ret == 0:
978 995 ret = signal.SIGTERM << 8
979 996 killdaemons(env['DAEMON_PIDS'])
980 997 return ret
981 998
982 999 output = ''
983 1000 proc.tochild.close()
984 1001
985 1002 try:
986 1003 output = proc.fromchild.read()
987 1004 except KeyboardInterrupt:
988 1005 vlog('# Handling keyboard interrupt')
989 1006 cleanup()
990 1007 raise
991 1008
992 1009 ret = proc.wait()
993 1010 if wifexited(ret):
994 1011 ret = os.WEXITSTATUS(ret)
995 1012
996 1013 if proc.timeout:
997 1014 ret = 'timeout'
998 1015
999 1016 if ret:
1000 1017 killdaemons(env['DAEMON_PIDS'])
1001 1018
1002 1019 for s, r in self._getreplacements():
1003 1020 output = re.sub(s, r, output)
1004 1021
1005 1022 if normalizenewlines:
1006 1023 output = output.replace('\r\n', '\n')
1007 1024
1008 1025 return ret, output.splitlines(True)
1009 1026
1010 1027 class PythonTest(Test):
1011 1028 """A Python-based test."""
1012 1029
1013 1030 @property
1014 1031 def refpath(self):
1015 1032 return os.path.join(self._testdir, b'%s.out' % self.bname)
1016 1033
1017 1034 def _run(self, env):
1018 1035 py3kswitch = self._py3kwarnings and b' -3' or b''
1019 1036 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1020 1037 vlog("# Running", cmd)
1021 1038 normalizenewlines = os.name == 'nt'
1022 1039 result = self._runcommand(cmd, env,
1023 1040 normalizenewlines=normalizenewlines)
1024 1041 if self._aborted:
1025 1042 raise KeyboardInterrupt()
1026 1043
1027 1044 return result
1028 1045
1029 1046 # Some glob patterns apply only in some circumstances, so the script
1030 1047 # might want to remove (glob) annotations that otherwise should be
1031 1048 # retained.
1032 1049 checkcodeglobpats = [
1033 1050 # On Windows it looks like \ doesn't require a (glob), but we know
1034 1051 # better.
1035 1052 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1036 1053 re.compile(br'^moving \S+/.*[^)]$'),
1037 1054 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1038 1055 # Not all platforms have 127.0.0.1 as loopback (though most do),
1039 1056 # so we always glob that too.
1040 1057 re.compile(br'.*\$LOCALIP.*$'),
1041 1058 ]
1042 1059
1043 1060 bchr = chr
1044 1061 if PYTHON3:
1045 1062 bchr = lambda x: bytes([x])
1046 1063
1047 1064 class TTest(Test):
1048 1065 """A "t test" is a test backed by a .t file."""
1049 1066
1050 1067 SKIPPED_PREFIX = b'skipped: '
1051 1068 FAILED_PREFIX = b'hghave check failed: '
1052 1069 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1053 1070
1054 1071 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1055 1072 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1056 1073 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1057 1074
1075 def __init__(self, path, *args, **kwds):
1076 # accept an extra "case" parameter
1077 case = None
1078 if 'case' in kwds:
1079 case = kwds.pop('case')
1080 self._case = case
1081 self._allcases = parsettestcases(path)
1082 super(TTest, self).__init__(path, *args, **kwds)
1083 if case:
1084 self.name = '%s (case %s)' % (self.name, _strpath(case))
1085 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1086 self._tmpname += b'-%s' % case
1087
1058 1088 @property
1059 1089 def refpath(self):
1060 1090 return os.path.join(self._testdir, self.bname)
1061 1091
1062 1092 def _run(self, env):
1063 1093 f = open(self.path, 'rb')
1064 1094 lines = f.readlines()
1065 1095 f.close()
1066 1096
1067 1097 salt, script, after, expected = self._parsetest(lines)
1068 1098
1069 1099 # Write out the generated script.
1070 1100 fname = b'%s.sh' % self._testtmp
1071 1101 f = open(fname, 'wb')
1072 1102 for l in script:
1073 1103 f.write(l)
1074 1104 f.close()
1075 1105
1076 1106 cmd = b'%s "%s"' % (self._shell, fname)
1077 1107 vlog("# Running", cmd)
1078 1108
1079 1109 exitcode, output = self._runcommand(cmd, env)
1080 1110
1081 1111 if self._aborted:
1082 1112 raise KeyboardInterrupt()
1083 1113
1084 1114 # Do not merge output if skipped. Return hghave message instead.
1085 1115 # Similarly, with --debug, output is None.
1086 1116 if exitcode == self.SKIPPED_STATUS or output is None:
1087 1117 return exitcode, output
1088 1118
1089 1119 return self._processoutput(exitcode, output, salt, after, expected)
1090 1120
1091 1121 def _hghave(self, reqs):
1092 1122 # TODO do something smarter when all other uses of hghave are gone.
1093 1123 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1094 1124 tdir = runtestdir.replace(b'\\', b'/')
1095 1125 proc = Popen4(b'%s -c "%s/hghave %s"' %
1096 1126 (self._shell, tdir, b' '.join(reqs)),
1097 1127 self._testtmp, 0, self._getenv())
1098 1128 stdout, stderr = proc.communicate()
1099 1129 ret = proc.wait()
1100 1130 if wifexited(ret):
1101 1131 ret = os.WEXITSTATUS(ret)
1102 1132 if ret == 2:
1103 1133 print(stdout.decode('utf-8'))
1104 1134 sys.exit(1)
1105 1135
1106 1136 if ret != 0:
1107 1137 return False, stdout
1108 1138
1109 1139 if 'slow' in reqs:
1110 1140 self._timeout = self._slowtimeout
1111 1141 return True, None
1112 1142
1143 def _iftest(self, args):
1144 # implements "#if"
1145 reqs = []
1146 for arg in args:
1147 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1148 if arg[3:] == self._case:
1149 return False
1150 elif arg in self._allcases:
1151 if arg != self._case:
1152 return False
1153 else:
1154 reqs.append(arg)
1155 return self._hghave(reqs)[0]
1156
1113 1157 def _parsetest(self, lines):
1114 1158 # We generate a shell script which outputs unique markers to line
1115 1159 # up script results with our source. These markers include input
1116 1160 # line number and the last return code.
1117 1161 salt = b"SALT%d" % time.time()
1118 1162 def addsalt(line, inpython):
1119 1163 if inpython:
1120 1164 script.append(b'%s %d 0\n' % (salt, line))
1121 1165 else:
1122 1166 script.append(b'echo %s %d $?\n' % (salt, line))
1123 1167
1124 1168 script = []
1125 1169
1126 1170 # After we run the shell script, we re-unify the script output
1127 1171 # with non-active parts of the source, with synchronization by our
1128 1172 # SALT line number markers. The after table contains the non-active
1129 1173 # components, ordered by line number.
1130 1174 after = {}
1131 1175
1132 1176 # Expected shell script output.
1133 1177 expected = {}
1134 1178
1135 1179 pos = prepos = -1
1136 1180
1137 1181 # True or False when in a true or false conditional section
1138 1182 skipping = None
1139 1183
1140 1184 # We keep track of whether or not we're in a Python block so we
1141 1185 # can generate the surrounding doctest magic.
1142 1186 inpython = False
1143 1187
1144 1188 if self._debug:
1145 1189 script.append(b'set -x\n')
1146 1190 if self._hgcommand != b'hg':
1147 1191 script.append(b'alias hg="%s"\n' % self._hgcommand)
1148 1192 if os.getenv('MSYSTEM'):
1149 1193 script.append(b'alias pwd="pwd -W"\n')
1150 1194
1151 1195 n = 0
1152 1196 for n, l in enumerate(lines):
1153 1197 if not l.endswith(b'\n'):
1154 1198 l += b'\n'
1155 1199 if l.startswith(b'#require'):
1156 1200 lsplit = l.split()
1157 1201 if len(lsplit) < 2 or lsplit[0] != b'#require':
1158 1202 after.setdefault(pos, []).append(' !!! invalid #require\n')
1159 1203 haveresult, message = self._hghave(lsplit[1:])
1160 1204 if not haveresult:
1161 1205 script = [b'echo "%s"\nexit 80\n' % message]
1162 1206 break
1163 1207 after.setdefault(pos, []).append(l)
1164 1208 elif l.startswith(b'#if'):
1165 1209 lsplit = l.split()
1166 1210 if len(lsplit) < 2 or lsplit[0] != b'#if':
1167 1211 after.setdefault(pos, []).append(' !!! invalid #if\n')
1168 1212 if skipping is not None:
1169 1213 after.setdefault(pos, []).append(' !!! nested #if\n')
1170 skipping = not self._hghave(lsplit[1:])[0]
1214 skipping = not self._iftest(lsplit[1:])
1171 1215 after.setdefault(pos, []).append(l)
1172 1216 elif l.startswith(b'#else'):
1173 1217 if skipping is None:
1174 1218 after.setdefault(pos, []).append(' !!! missing #if\n')
1175 1219 skipping = not skipping
1176 1220 after.setdefault(pos, []).append(l)
1177 1221 elif l.startswith(b'#endif'):
1178 1222 if skipping is None:
1179 1223 after.setdefault(pos, []).append(' !!! missing #if\n')
1180 1224 skipping = None
1181 1225 after.setdefault(pos, []).append(l)
1182 1226 elif skipping:
1183 1227 after.setdefault(pos, []).append(l)
1184 1228 elif l.startswith(b' >>> '): # python inlines
1185 1229 after.setdefault(pos, []).append(l)
1186 1230 prepos = pos
1187 1231 pos = n
1188 1232 if not inpython:
1189 1233 # We've just entered a Python block. Add the header.
1190 1234 inpython = True
1191 1235 addsalt(prepos, False) # Make sure we report the exit code.
1192 1236 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1193 1237 addsalt(n, True)
1194 1238 script.append(l[2:])
1195 1239 elif l.startswith(b' ... '): # python inlines
1196 1240 after.setdefault(prepos, []).append(l)
1197 1241 script.append(l[2:])
1198 1242 elif l.startswith(b' $ '): # commands
1199 1243 if inpython:
1200 1244 script.append(b'EOF\n')
1201 1245 inpython = False
1202 1246 after.setdefault(pos, []).append(l)
1203 1247 prepos = pos
1204 1248 pos = n
1205 1249 addsalt(n, False)
1206 1250 cmd = l[4:].split()
1207 1251 if len(cmd) == 2 and cmd[0] == b'cd':
1208 1252 l = b' $ cd %s || exit 1\n' % cmd[1]
1209 1253 script.append(l[4:])
1210 1254 elif l.startswith(b' > '): # continuations
1211 1255 after.setdefault(prepos, []).append(l)
1212 1256 script.append(l[4:])
1213 1257 elif l.startswith(b' '): # results
1214 1258 # Queue up a list of expected results.
1215 1259 expected.setdefault(pos, []).append(l[2:])
1216 1260 else:
1217 1261 if inpython:
1218 1262 script.append(b'EOF\n')
1219 1263 inpython = False
1220 1264 # Non-command/result. Queue up for merged output.
1221 1265 after.setdefault(pos, []).append(l)
1222 1266
1223 1267 if inpython:
1224 1268 script.append(b'EOF\n')
1225 1269 if skipping is not None:
1226 1270 after.setdefault(pos, []).append(' !!! missing #endif\n')
1227 1271 addsalt(n + 1, False)
1228 1272
1229 1273 return salt, script, after, expected
1230 1274
1231 1275 def _processoutput(self, exitcode, output, salt, after, expected):
1232 1276 # Merge the script output back into a unified test.
1233 1277 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1234 1278 if exitcode != 0:
1235 1279 warnonly = 3
1236 1280
1237 1281 pos = -1
1238 1282 postout = []
1239 1283 for l in output:
1240 1284 lout, lcmd = l, None
1241 1285 if salt in l:
1242 1286 lout, lcmd = l.split(salt, 1)
1243 1287
1244 1288 while lout:
1245 1289 if not lout.endswith(b'\n'):
1246 1290 lout += b' (no-eol)\n'
1247 1291
1248 1292 # Find the expected output at the current position.
1249 1293 els = [None]
1250 1294 if expected.get(pos, None):
1251 1295 els = expected[pos]
1252 1296
1253 1297 i = 0
1254 1298 optional = []
1255 1299 while i < len(els):
1256 1300 el = els[i]
1257 1301
1258 1302 r = TTest.linematch(el, lout)
1259 1303 if isinstance(r, str):
1260 1304 if r == '+glob':
1261 1305 lout = el[:-1] + ' (glob)\n'
1262 1306 r = '' # Warn only this line.
1263 1307 elif r == '-glob':
1264 1308 lout = ''.join(el.rsplit(' (glob)', 1))
1265 1309 r = '' # Warn only this line.
1266 1310 elif r == "retry":
1267 1311 postout.append(b' ' + el)
1268 1312 els.pop(i)
1269 1313 break
1270 1314 else:
1271 1315 log('\ninfo, unknown linematch result: %r\n' % r)
1272 1316 r = False
1273 1317 if r:
1274 1318 els.pop(i)
1275 1319 break
1276 1320 if el:
1277 1321 if el.endswith(b" (?)\n"):
1278 1322 optional.append(i)
1279 1323 else:
1280 1324 m = optline.match(el)
1281 1325 if m:
1282 1326 conditions = [c for c in m.group(2).split(' ')]
1283 1327
1284 1328 if self._hghave(conditions)[0]:
1285 1329 lout = el
1286 1330 else:
1287 1331 optional.append(i)
1288 1332
1289 1333 i += 1
1290 1334
1291 1335 if r:
1292 1336 if r == "retry":
1293 1337 continue
1294 1338 # clean up any optional leftovers
1295 1339 for i in optional:
1296 1340 postout.append(b' ' + els[i])
1297 1341 for i in reversed(optional):
1298 1342 del els[i]
1299 1343 postout.append(b' ' + el)
1300 1344 else:
1301 1345 if self.NEEDESCAPE(lout):
1302 1346 lout = TTest._stringescape(b'%s (esc)\n' %
1303 1347 lout.rstrip(b'\n'))
1304 1348 postout.append(b' ' + lout) # Let diff deal with it.
1305 1349 if r != '': # If line failed.
1306 1350 warnonly = 3 # for sure not
1307 1351 elif warnonly == 1: # Is "not yet" and line is warn only.
1308 1352 warnonly = 2 # Yes do warn.
1309 1353 break
1310 1354 else:
1311 1355 # clean up any optional leftovers
1312 1356 while expected.get(pos, None):
1313 1357 el = expected[pos].pop(0)
1314 1358 if el:
1315 1359 if (not optline.match(el)
1316 1360 and not el.endswith(b" (?)\n")):
1317 1361 break
1318 1362 postout.append(b' ' + el)
1319 1363
1320 1364 if lcmd:
1321 1365 # Add on last return code.
1322 1366 ret = int(lcmd.split()[1])
1323 1367 if ret != 0:
1324 1368 postout.append(b' [%d]\n' % ret)
1325 1369 if pos in after:
1326 1370 # Merge in non-active test bits.
1327 1371 postout += after.pop(pos)
1328 1372 pos = int(lcmd.split()[0])
1329 1373
1330 1374 if pos in after:
1331 1375 postout += after.pop(pos)
1332 1376
1333 1377 if warnonly == 2:
1334 1378 exitcode = False # Set exitcode to warned.
1335 1379
1336 1380 return exitcode, postout
1337 1381
1338 1382 @staticmethod
1339 1383 def rematch(el, l):
1340 1384 try:
1341 1385 # use \Z to ensure that the regex matches to the end of the string
1342 1386 if os.name == 'nt':
1343 1387 return re.match(el + br'\r?\n\Z', l)
1344 1388 return re.match(el + br'\n\Z', l)
1345 1389 except re.error:
1346 1390 # el is an invalid regex
1347 1391 return False
1348 1392
1349 1393 @staticmethod
1350 1394 def globmatch(el, l):
1351 1395 # The only supported special characters are * and ? plus / which also
1352 1396 # matches \ on windows. Escaping of these characters is supported.
1353 1397 if el + b'\n' == l:
1354 1398 if os.altsep:
1355 1399 # matching on "/" is not needed for this line
1356 1400 for pat in checkcodeglobpats:
1357 1401 if pat.match(el):
1358 1402 return True
1359 1403 return b'-glob'
1360 1404 return True
1361 1405 el = el.replace(b'$LOCALIP', b'*')
1362 1406 i, n = 0, len(el)
1363 1407 res = b''
1364 1408 while i < n:
1365 1409 c = el[i:i + 1]
1366 1410 i += 1
1367 1411 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1368 1412 res += el[i - 1:i + 1]
1369 1413 i += 1
1370 1414 elif c == b'*':
1371 1415 res += b'.*'
1372 1416 elif c == b'?':
1373 1417 res += b'.'
1374 1418 elif c == b'/' and os.altsep:
1375 1419 res += b'[/\\\\]'
1376 1420 else:
1377 1421 res += re.escape(c)
1378 1422 return TTest.rematch(res, l)
1379 1423
1380 1424 @staticmethod
1381 1425 def linematch(el, l):
1382 1426 retry = False
1383 1427 if el == l: # perfect match (fast)
1384 1428 return True
1385 1429 if el:
1386 1430 if el.endswith(b" (?)\n"):
1387 1431 retry = "retry"
1388 1432 el = el[:-5] + b"\n"
1389 1433 else:
1390 1434 m = optline.match(el)
1391 1435 if m:
1392 1436 el = m.group(1) + b"\n"
1393 1437 retry = "retry"
1394 1438
1395 1439 if el.endswith(b" (esc)\n"):
1396 1440 if PYTHON3:
1397 1441 el = el[:-7].decode('unicode_escape') + '\n'
1398 1442 el = el.encode('utf-8')
1399 1443 else:
1400 1444 el = el[:-7].decode('string-escape') + '\n'
1401 1445 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1402 1446 return True
1403 1447 if el.endswith(b" (re)\n"):
1404 1448 return TTest.rematch(el[:-6], l) or retry
1405 1449 if el.endswith(b" (glob)\n"):
1406 1450 # ignore '(glob)' added to l by 'replacements'
1407 1451 if l.endswith(b" (glob)\n"):
1408 1452 l = l[:-8] + b"\n"
1409 1453 return TTest.globmatch(el[:-8], l) or retry
1410 1454 if os.altsep and l.replace(b'\\', b'/') == el:
1411 1455 return b'+glob'
1412 1456 return retry
1413 1457
1414 1458 @staticmethod
1415 1459 def parsehghaveoutput(lines):
1416 1460 '''Parse hghave log lines.
1417 1461
1418 1462 Return tuple of lists (missing, failed):
1419 1463 * the missing/unknown features
1420 1464 * the features for which existence check failed'''
1421 1465 missing = []
1422 1466 failed = []
1423 1467 for line in lines:
1424 1468 if line.startswith(TTest.SKIPPED_PREFIX):
1425 1469 line = line.splitlines()[0]
1426 1470 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1427 1471 elif line.startswith(TTest.FAILED_PREFIX):
1428 1472 line = line.splitlines()[0]
1429 1473 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1430 1474
1431 1475 return missing, failed
1432 1476
1433 1477 @staticmethod
1434 1478 def _escapef(m):
1435 1479 return TTest.ESCAPEMAP[m.group(0)]
1436 1480
1437 1481 @staticmethod
1438 1482 def _stringescape(s):
1439 1483 return TTest.ESCAPESUB(TTest._escapef, s)
1440 1484
1441 1485 iolock = threading.RLock()
1442 1486
1443 1487 class SkipTest(Exception):
1444 1488 """Raised to indicate that a test is to be skipped."""
1445 1489
1446 1490 class IgnoreTest(Exception):
1447 1491 """Raised to indicate that a test is to be ignored."""
1448 1492
1449 1493 class WarnTest(Exception):
1450 1494 """Raised to indicate that a test warned."""
1451 1495
1452 1496 class ReportedTest(Exception):
1453 1497 """Raised to indicate that a test already reported."""
1454 1498
1455 1499 class TestResult(unittest._TextTestResult):
1456 1500 """Holds results when executing via unittest."""
1457 1501 # Don't worry too much about accessing the non-public _TextTestResult.
1458 1502 # It is relatively common in Python testing tools.
1459 1503 def __init__(self, options, *args, **kwargs):
1460 1504 super(TestResult, self).__init__(*args, **kwargs)
1461 1505
1462 1506 self._options = options
1463 1507
1464 1508 # unittest.TestResult didn't have skipped until 2.7. We need to
1465 1509 # polyfill it.
1466 1510 self.skipped = []
1467 1511
1468 1512 # We have a custom "ignored" result that isn't present in any Python
1469 1513 # unittest implementation. It is very similar to skipped. It may make
1470 1514 # sense to map it into skip some day.
1471 1515 self.ignored = []
1472 1516
1473 1517 # We have a custom "warned" result that isn't present in any Python
1474 1518 # unittest implementation. It is very similar to failed. It may make
1475 1519 # sense to map it into fail some day.
1476 1520 self.warned = []
1477 1521
1478 1522 self.times = []
1479 1523 self._firststarttime = None
1480 1524 # Data stored for the benefit of generating xunit reports.
1481 1525 self.successes = []
1482 1526 self.faildata = {}
1483 1527
1484 1528 def addFailure(self, test, reason):
1485 1529 self.failures.append((test, reason))
1486 1530
1487 1531 if self._options.first:
1488 1532 self.stop()
1489 1533 else:
1490 1534 with iolock:
1491 1535 if reason == "timed out":
1492 1536 self.stream.write('t')
1493 1537 else:
1494 1538 if not self._options.nodiff:
1495 1539 self.stream.write('\nERROR: %s output changed\n' % test)
1496 1540 self.stream.write('!')
1497 1541
1498 1542 self.stream.flush()
1499 1543
1500 1544 def addSuccess(self, test):
1501 1545 with iolock:
1502 1546 super(TestResult, self).addSuccess(test)
1503 1547 self.successes.append(test)
1504 1548
1505 1549 def addError(self, test, err):
1506 1550 super(TestResult, self).addError(test, err)
1507 1551 if self._options.first:
1508 1552 self.stop()
1509 1553
1510 1554 # Polyfill.
1511 1555 def addSkip(self, test, reason):
1512 1556 self.skipped.append((test, reason))
1513 1557 with iolock:
1514 1558 if self.showAll:
1515 1559 self.stream.writeln('skipped %s' % reason)
1516 1560 else:
1517 1561 self.stream.write('s')
1518 1562 self.stream.flush()
1519 1563
1520 1564 def addIgnore(self, test, reason):
1521 1565 self.ignored.append((test, reason))
1522 1566 with iolock:
1523 1567 if self.showAll:
1524 1568 self.stream.writeln('ignored %s' % reason)
1525 1569 else:
1526 1570 if reason not in ('not retesting', "doesn't match keyword"):
1527 1571 self.stream.write('i')
1528 1572 else:
1529 1573 self.testsRun += 1
1530 1574 self.stream.flush()
1531 1575
1532 1576 def addWarn(self, test, reason):
1533 1577 self.warned.append((test, reason))
1534 1578
1535 1579 if self._options.first:
1536 1580 self.stop()
1537 1581
1538 1582 with iolock:
1539 1583 if self.showAll:
1540 1584 self.stream.writeln('warned %s' % reason)
1541 1585 else:
1542 1586 self.stream.write('~')
1543 1587 self.stream.flush()
1544 1588
1545 1589 def addOutputMismatch(self, test, ret, got, expected):
1546 1590 """Record a mismatch in test output for a particular test."""
1547 1591 if self.shouldStop:
1548 1592 # don't print, some other test case already failed and
1549 1593 # printed, we're just stale and probably failed due to our
1550 1594 # temp dir getting cleaned up.
1551 1595 return
1552 1596
1553 1597 accepted = False
1554 1598 lines = []
1555 1599
1556 1600 with iolock:
1557 1601 if self._options.nodiff:
1558 1602 pass
1559 1603 elif self._options.view:
1560 1604 v = self._options.view
1561 1605 if PYTHON3:
1562 1606 v = _bytespath(v)
1563 1607 os.system(b"%s %s %s" %
1564 1608 (v, test.refpath, test.errpath))
1565 1609 else:
1566 1610 servefail, lines = getdiff(expected, got,
1567 1611 test.refpath, test.errpath)
1568 1612 if servefail:
1569 1613 self.addFailure(
1570 1614 test,
1571 1615 'server failed to start (HGPORT=%s)' % test._startport)
1572 1616 raise ReportedTest('server failed to start')
1573 1617 else:
1574 1618 self.stream.write('\n')
1575 1619 for line in lines:
1576 1620 if PYTHON3:
1577 1621 self.stream.flush()
1578 1622 self.stream.buffer.write(line)
1579 1623 self.stream.buffer.flush()
1580 1624 else:
1581 1625 self.stream.write(line)
1582 1626 self.stream.flush()
1583 1627
1584 1628 # handle interactive prompt without releasing iolock
1585 1629 if self._options.interactive:
1586 1630 self.stream.write('Accept this change? [n] ')
1587 1631 answer = sys.stdin.readline().strip()
1588 1632 if answer.lower() in ('y', 'yes'):
1589 1633 if test.name.endswith('.t'):
1590 1634 rename(test.errpath, test.path)
1591 1635 else:
1592 1636 rename(test.errpath, '%s.out' % test.path)
1593 1637 accepted = True
1594 1638 if not accepted:
1595 1639 self.faildata[test.name] = b''.join(lines)
1596 1640
1597 1641 return accepted
1598 1642
1599 1643 def startTest(self, test):
1600 1644 super(TestResult, self).startTest(test)
1601 1645
1602 1646 # os.times module computes the user time and system time spent by
1603 1647 # child's processes along with real elapsed time taken by a process.
1604 1648 # This module has one limitation. It can only work for Linux user
1605 1649 # and not for Windows.
1606 1650 test.started = os.times()
1607 1651 if self._firststarttime is None: # thread racy but irrelevant
1608 1652 self._firststarttime = test.started[4]
1609 1653
1610 1654 def stopTest(self, test, interrupted=False):
1611 1655 super(TestResult, self).stopTest(test)
1612 1656
1613 1657 test.stopped = os.times()
1614 1658
1615 1659 starttime = test.started
1616 1660 endtime = test.stopped
1617 1661 origin = self._firststarttime
1618 1662 self.times.append((test.name,
1619 1663 endtime[2] - starttime[2], # user space CPU time
1620 1664 endtime[3] - starttime[3], # sys space CPU time
1621 1665 endtime[4] - starttime[4], # real time
1622 1666 starttime[4] - origin, # start date in run context
1623 1667 endtime[4] - origin, # end date in run context
1624 1668 ))
1625 1669
1626 1670 if interrupted:
1627 1671 with iolock:
1628 1672 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1629 1673 test.name, self.times[-1][3]))
1630 1674
1631 1675 class TestSuite(unittest.TestSuite):
1632 1676 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1633 1677
1634 1678 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1635 1679 retest=False, keywords=None, loop=False, runs_per_test=1,
1636 1680 loadtest=None, showchannels=False,
1637 1681 *args, **kwargs):
1638 1682 """Create a new instance that can run tests with a configuration.
1639 1683
1640 1684 testdir specifies the directory where tests are executed from. This
1641 1685 is typically the ``tests`` directory from Mercurial's source
1642 1686 repository.
1643 1687
1644 1688 jobs specifies the number of jobs to run concurrently. Each test
1645 1689 executes on its own thread. Tests actually spawn new processes, so
1646 1690 state mutation should not be an issue.
1647 1691
1648 1692 If there is only one job, it will use the main thread.
1649 1693
1650 1694 whitelist and blacklist denote tests that have been whitelisted and
1651 1695 blacklisted, respectively. These arguments don't belong in TestSuite.
1652 1696 Instead, whitelist and blacklist should be handled by the thing that
1653 1697 populates the TestSuite with tests. They are present to preserve
1654 1698 backwards compatible behavior which reports skipped tests as part
1655 1699 of the results.
1656 1700
1657 1701 retest denotes whether to retest failed tests. This arguably belongs
1658 1702 outside of TestSuite.
1659 1703
1660 1704 keywords denotes key words that will be used to filter which tests
1661 1705 to execute. This arguably belongs outside of TestSuite.
1662 1706
1663 1707 loop denotes whether to loop over tests forever.
1664 1708 """
1665 1709 super(TestSuite, self).__init__(*args, **kwargs)
1666 1710
1667 1711 self._jobs = jobs
1668 1712 self._whitelist = whitelist
1669 1713 self._blacklist = blacklist
1670 1714 self._retest = retest
1671 1715 self._keywords = keywords
1672 1716 self._loop = loop
1673 1717 self._runs_per_test = runs_per_test
1674 1718 self._loadtest = loadtest
1675 1719 self._showchannels = showchannels
1676 1720
1677 1721 def run(self, result):
1678 1722 # We have a number of filters that need to be applied. We do this
1679 1723 # here instead of inside Test because it makes the running logic for
1680 1724 # Test simpler.
1681 1725 tests = []
1682 1726 num_tests = [0]
1683 1727 for test in self._tests:
1684 1728 def get():
1685 1729 num_tests[0] += 1
1686 1730 if getattr(test, 'should_reload', False):
1687 1731 return self._loadtest(test, num_tests[0])
1688 1732 return test
1689 1733 if not os.path.exists(test.path):
1690 1734 result.addSkip(test, "Doesn't exist")
1691 1735 continue
1692 1736
1693 1737 if not (self._whitelist and test.name in self._whitelist):
1694 1738 if self._blacklist and test.bname in self._blacklist:
1695 1739 result.addSkip(test, 'blacklisted')
1696 1740 continue
1697 1741
1698 1742 if self._retest and not os.path.exists(test.errpath):
1699 1743 result.addIgnore(test, 'not retesting')
1700 1744 continue
1701 1745
1702 1746 if self._keywords:
1703 1747 f = open(test.path, 'rb')
1704 1748 t = f.read().lower() + test.bname.lower()
1705 1749 f.close()
1706 1750 ignored = False
1707 1751 for k in self._keywords.lower().split():
1708 1752 if k not in t:
1709 1753 result.addIgnore(test, "doesn't match keyword")
1710 1754 ignored = True
1711 1755 break
1712 1756
1713 1757 if ignored:
1714 1758 continue
1715 1759 for _ in xrange(self._runs_per_test):
1716 1760 tests.append(get())
1717 1761
1718 1762 runtests = list(tests)
1719 1763 done = queue.Queue()
1720 1764 running = 0
1721 1765
1722 1766 channels = [""] * self._jobs
1723 1767
1724 1768 def job(test, result):
1725 1769 for n, v in enumerate(channels):
1726 1770 if not v:
1727 1771 channel = n
1728 1772 break
1729 1773 channels[channel] = "=" + test.name[5:].split(".")[0]
1730 1774 try:
1731 1775 test(result)
1732 1776 done.put(None)
1733 1777 except KeyboardInterrupt:
1734 1778 pass
1735 1779 except: # re-raises
1736 1780 done.put(('!', test, 'run-test raised an error, see traceback'))
1737 1781 raise
1738 1782 try:
1739 1783 channels[channel] = ''
1740 1784 except IndexError:
1741 1785 pass
1742 1786
1743 1787 def stat():
1744 1788 count = 0
1745 1789 while channels:
1746 1790 d = '\n%03s ' % count
1747 1791 for n, v in enumerate(channels):
1748 1792 if v:
1749 1793 d += v[0]
1750 1794 channels[n] = v[1:] or '.'
1751 1795 else:
1752 1796 d += ' '
1753 1797 d += ' '
1754 1798 with iolock:
1755 1799 sys.stdout.write(d + ' ')
1756 1800 sys.stdout.flush()
1757 1801 for x in xrange(10):
1758 1802 if channels:
1759 1803 time.sleep(.1)
1760 1804 count += 1
1761 1805
1762 1806 stoppedearly = False
1763 1807
1764 1808 if self._showchannels:
1765 1809 statthread = threading.Thread(target=stat, name="stat")
1766 1810 statthread.start()
1767 1811
1768 1812 try:
1769 1813 while tests or running:
1770 1814 if not done.empty() or running == self._jobs or not tests:
1771 1815 try:
1772 1816 done.get(True, 1)
1773 1817 running -= 1
1774 1818 if result and result.shouldStop:
1775 1819 stoppedearly = True
1776 1820 break
1777 1821 except queue.Empty:
1778 1822 continue
1779 1823 if tests and not running == self._jobs:
1780 1824 test = tests.pop(0)
1781 1825 if self._loop:
1782 1826 if getattr(test, 'should_reload', False):
1783 1827 num_tests[0] += 1
1784 1828 tests.append(
1785 1829 self._loadtest(test, num_tests[0]))
1786 1830 else:
1787 1831 tests.append(test)
1788 1832 if self._jobs == 1:
1789 1833 job(test, result)
1790 1834 else:
1791 1835 t = threading.Thread(target=job, name=test.name,
1792 1836 args=(test, result))
1793 1837 t.start()
1794 1838 running += 1
1795 1839
1796 1840 # If we stop early we still need to wait on started tests to
1797 1841 # finish. Otherwise, there is a race between the test completing
1798 1842 # and the test's cleanup code running. This could result in the
1799 1843 # test reporting incorrect.
1800 1844 if stoppedearly:
1801 1845 while running:
1802 1846 try:
1803 1847 done.get(True, 1)
1804 1848 running -= 1
1805 1849 except queue.Empty:
1806 1850 continue
1807 1851 except KeyboardInterrupt:
1808 1852 for test in runtests:
1809 1853 test.abort()
1810 1854
1811 1855 channels = []
1812 1856
1813 1857 return result
1814 1858
1815 1859 # Save the most recent 5 wall-clock runtimes of each test to a
1816 1860 # human-readable text file named .testtimes. Tests are sorted
1817 1861 # alphabetically, while times for each test are listed from oldest to
1818 1862 # newest.
1819 1863
1820 1864 def loadtimes(testdir):
1821 1865 times = []
1822 1866 try:
1823 1867 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1824 1868 for line in fp:
1825 1869 ts = line.split()
1826 1870 times.append((ts[0], [float(t) for t in ts[1:]]))
1827 1871 except IOError as err:
1828 1872 if err.errno != errno.ENOENT:
1829 1873 raise
1830 1874 return times
1831 1875
1832 1876 def savetimes(testdir, result):
1833 1877 saved = dict(loadtimes(testdir))
1834 1878 maxruns = 5
1835 1879 skipped = set([str(t[0]) for t in result.skipped])
1836 1880 for tdata in result.times:
1837 1881 test, real = tdata[0], tdata[3]
1838 1882 if test not in skipped:
1839 1883 ts = saved.setdefault(test, [])
1840 1884 ts.append(real)
1841 1885 ts[:] = ts[-maxruns:]
1842 1886
1843 1887 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1844 1888 dir=testdir, text=True)
1845 1889 with os.fdopen(fd, 'w') as fp:
1846 1890 for name, ts in sorted(saved.items()):
1847 1891 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1848 1892 timepath = os.path.join(testdir, b'.testtimes')
1849 1893 try:
1850 1894 os.unlink(timepath)
1851 1895 except OSError:
1852 1896 pass
1853 1897 try:
1854 1898 os.rename(tmpname, timepath)
1855 1899 except OSError:
1856 1900 pass
1857 1901
1858 1902 class TextTestRunner(unittest.TextTestRunner):
1859 1903 """Custom unittest test runner that uses appropriate settings."""
1860 1904
1861 1905 def __init__(self, runner, *args, **kwargs):
1862 1906 super(TextTestRunner, self).__init__(*args, **kwargs)
1863 1907
1864 1908 self._runner = runner
1865 1909
1866 1910 def run(self, test):
1867 1911 result = TestResult(self._runner.options, self.stream,
1868 1912 self.descriptions, self.verbosity)
1869 1913
1870 1914 test(result)
1871 1915
1872 1916 failed = len(result.failures)
1873 1917 warned = len(result.warned)
1874 1918 skipped = len(result.skipped)
1875 1919 ignored = len(result.ignored)
1876 1920
1877 1921 with iolock:
1878 1922 self.stream.writeln('')
1879 1923
1880 1924 if not self._runner.options.noskips:
1881 1925 for test, msg in result.skipped:
1882 1926 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1883 1927 for test, msg in result.warned:
1884 1928 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1885 1929 for test, msg in result.failures:
1886 1930 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1887 1931 for test, msg in result.errors:
1888 1932 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1889 1933
1890 1934 if self._runner.options.xunit:
1891 1935 with open(self._runner.options.xunit, 'wb') as xuf:
1892 1936 timesd = dict((t[0], t[3]) for t in result.times)
1893 1937 doc = minidom.Document()
1894 1938 s = doc.createElement('testsuite')
1895 1939 s.setAttribute('name', 'run-tests')
1896 1940 s.setAttribute('tests', str(result.testsRun))
1897 1941 s.setAttribute('errors', "0") # TODO
1898 1942 s.setAttribute('failures', str(failed))
1899 1943 s.setAttribute('skipped', str(skipped + ignored))
1900 1944 doc.appendChild(s)
1901 1945 for tc in result.successes:
1902 1946 t = doc.createElement('testcase')
1903 1947 t.setAttribute('name', tc.name)
1904 1948 t.setAttribute('time', '%.3f' % timesd[tc.name])
1905 1949 s.appendChild(t)
1906 1950 for tc, err in sorted(result.faildata.items()):
1907 1951 t = doc.createElement('testcase')
1908 1952 t.setAttribute('name', tc)
1909 1953 t.setAttribute('time', '%.3f' % timesd[tc])
1910 1954 # createCDATASection expects a unicode or it will
1911 1955 # convert using default conversion rules, which will
1912 1956 # fail if string isn't ASCII.
1913 1957 err = cdatasafe(err).decode('utf-8', 'replace')
1914 1958 cd = doc.createCDATASection(err)
1915 1959 t.appendChild(cd)
1916 1960 s.appendChild(t)
1917 1961 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1918 1962
1919 1963 if self._runner.options.json:
1920 1964 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1921 1965 with open(jsonpath, 'w') as fp:
1922 1966 timesd = {}
1923 1967 for tdata in result.times:
1924 1968 test = tdata[0]
1925 1969 timesd[test] = tdata[1:]
1926 1970
1927 1971 outcome = {}
1928 1972 groups = [('success', ((tc, None)
1929 1973 for tc in result.successes)),
1930 1974 ('failure', result.failures),
1931 1975 ('skip', result.skipped)]
1932 1976 for res, testcases in groups:
1933 1977 for tc, __ in testcases:
1934 1978 if tc.name in timesd:
1935 1979 diff = result.faildata.get(tc.name, b'')
1936 1980 tres = {'result': res,
1937 1981 'time': ('%0.3f' % timesd[tc.name][2]),
1938 1982 'cuser': ('%0.3f' % timesd[tc.name][0]),
1939 1983 'csys': ('%0.3f' % timesd[tc.name][1]),
1940 1984 'start': ('%0.3f' % timesd[tc.name][3]),
1941 1985 'end': ('%0.3f' % timesd[tc.name][4]),
1942 1986 'diff': diff.decode('unicode_escape'),
1943 1987 }
1944 1988 else:
1945 1989 # blacklisted test
1946 1990 tres = {'result': res}
1947 1991
1948 1992 outcome[tc.name] = tres
1949 1993 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
1950 1994 separators=(',', ': '))
1951 1995 fp.writelines(("testreport =", jsonout))
1952 1996
1953 1997 self._runner._checkhglib('Tested')
1954 1998
1955 1999 savetimes(self._runner._testdir, result)
1956 2000
1957 2001 if failed and self._runner.options.known_good_rev:
1958 2002 def nooutput(args):
1959 2003 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
1960 2004 stdout=subprocess.PIPE)
1961 2005 p.stdout.read()
1962 2006 p.wait()
1963 2007 for test, msg in result.failures:
1964 2008 nooutput(['hg', 'bisect', '--reset']),
1965 2009 nooutput(['hg', 'bisect', '--bad', '.'])
1966 2010 nooutput(['hg', 'bisect', '--good',
1967 2011 self._runner.options.known_good_rev])
1968 2012 # TODO: we probably need to forward some options
1969 2013 # that alter hg's behavior inside the tests.
1970 2014 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
1971 2015 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
1972 2016 stderr=subprocess.STDOUT,
1973 2017 stdout=subprocess.PIPE)
1974 2018 data = sub.stdout.read()
1975 2019 sub.wait()
1976 2020 m = re.search(
1977 2021 (r'\nThe first (?P<goodbad>bad|good) revision '
1978 2022 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
1979 2023 r'summary: +(?P<summary>[^\n]+)\n'),
1980 2024 data, (re.MULTILINE | re.DOTALL))
1981 2025 if m is None:
1982 2026 self.stream.writeln(
1983 2027 'Failed to identify failure point for %s' % test)
1984 2028 continue
1985 2029 dat = m.groupdict()
1986 2030 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
1987 2031 self.stream.writeln(
1988 2032 '%s %s by %s (%s)' % (
1989 2033 test, verb, dat['node'], dat['summary']))
1990 2034 self.stream.writeln(
1991 2035 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1992 2036 % (result.testsRun,
1993 2037 skipped + ignored, warned, failed))
1994 2038 if failed:
1995 2039 self.stream.writeln('python hash seed: %s' %
1996 2040 os.environ['PYTHONHASHSEED'])
1997 2041 if self._runner.options.time:
1998 2042 self.printtimes(result.times)
1999 2043
2000 2044 return result
2001 2045
2002 2046 def printtimes(self, times):
2003 2047 # iolock held by run
2004 2048 self.stream.writeln('# Producing time report')
2005 2049 times.sort(key=lambda t: (t[3]))
2006 2050 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2007 2051 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2008 2052 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2009 2053 for tdata in times:
2010 2054 test = tdata[0]
2011 2055 cuser, csys, real, start, end = tdata[1:6]
2012 2056 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2013 2057
2014 2058 class TestRunner(object):
2015 2059 """Holds context for executing tests.
2016 2060
2017 2061 Tests rely on a lot of state. This object holds it for them.
2018 2062 """
2019 2063
2020 2064 # Programs required to run tests.
2021 2065 REQUIREDTOOLS = [
2022 2066 os.path.basename(_bytespath(sys.executable)),
2023 2067 b'diff',
2024 2068 b'grep',
2025 2069 b'unzip',
2026 2070 b'gunzip',
2027 2071 b'bunzip2',
2028 2072 b'sed',
2029 2073 ]
2030 2074
2031 2075 # Maps file extensions to test class.
2032 2076 TESTTYPES = [
2033 2077 (b'.py', PythonTest),
2034 2078 (b'.t', TTest),
2035 2079 ]
2036 2080
2037 2081 def __init__(self):
2038 2082 self.options = None
2039 2083 self._hgroot = None
2040 2084 self._testdir = None
2041 2085 self._hgtmp = None
2042 2086 self._installdir = None
2043 2087 self._bindir = None
2044 2088 self._tmpbinddir = None
2045 2089 self._pythondir = None
2046 2090 self._coveragefile = None
2047 2091 self._createdfiles = []
2048 2092 self._hgcommand = None
2049 2093 self._hgpath = None
2050 2094 self._portoffset = 0
2051 2095 self._ports = {}
2052 2096
2053 2097 def run(self, args, parser=None):
2054 2098 """Run the test suite."""
2055 2099 oldmask = os.umask(0o22)
2056 2100 try:
2057 2101 parser = parser or getparser()
2058 2102 options, args = parseargs(args, parser)
2059 2103 # positional arguments are paths to test files to run, so
2060 2104 # we make sure they're all bytestrings
2061 2105 args = [_bytespath(a) for a in args]
2062 2106 self.options = options
2063 2107
2064 2108 self._checktools()
2065 2109 testdescs = self.findtests(args)
2066 2110 if options.profile_runner:
2067 2111 import statprof
2068 2112 statprof.start()
2069 2113 result = self._run(testdescs)
2070 2114 if options.profile_runner:
2071 2115 statprof.stop()
2072 2116 statprof.display()
2073 2117 return result
2074 2118
2075 2119 finally:
2076 2120 os.umask(oldmask)
2077 2121
2078 2122 def _run(self, testdescs):
2079 2123 if self.options.random:
2080 2124 random.shuffle(testdescs)
2081 2125 else:
2082 2126 # keywords for slow tests
2083 2127 slow = {b'svn': 10,
2084 2128 b'cvs': 10,
2085 2129 b'hghave': 10,
2086 2130 b'largefiles-update': 10,
2087 2131 b'run-tests': 10,
2088 2132 b'corruption': 10,
2089 2133 b'race': 10,
2090 2134 b'i18n': 10,
2091 2135 b'check': 100,
2092 2136 b'gendoc': 100,
2093 2137 b'contrib-perf': 200,
2094 2138 }
2095 2139 perf = {}
2096 2140 def sortkey(f):
2097 2141 # run largest tests first, as they tend to take the longest
2098 2142 f = f['path']
2099 2143 try:
2100 2144 return perf[f]
2101 2145 except KeyError:
2102 2146 try:
2103 2147 val = -os.stat(f).st_size
2104 2148 except OSError as e:
2105 2149 if e.errno != errno.ENOENT:
2106 2150 raise
2107 2151 perf[f] = -1e9 # file does not exist, tell early
2108 2152 return -1e9
2109 2153 for kw, mul in slow.items():
2110 2154 if kw in f:
2111 2155 val *= mul
2112 2156 if f.endswith(b'.py'):
2113 2157 val /= 10.0
2114 2158 perf[f] = val / 1000.0
2115 2159 return perf[f]
2116 2160 testdescs.sort(key=sortkey)
2117 2161
2118 2162 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2119 2163 os, 'getcwdb', os.getcwd)()
2120 2164
2121 2165 if 'PYTHONHASHSEED' not in os.environ:
2122 2166 # use a random python hash seed all the time
2123 2167 # we do the randomness ourself to know what seed is used
2124 2168 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2125 2169
2126 2170 if self.options.tmpdir:
2127 2171 self.options.keep_tmpdir = True
2128 2172 tmpdir = _bytespath(self.options.tmpdir)
2129 2173 if os.path.exists(tmpdir):
2130 2174 # Meaning of tmpdir has changed since 1.3: we used to create
2131 2175 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2132 2176 # tmpdir already exists.
2133 2177 print("error: temp dir %r already exists" % tmpdir)
2134 2178 return 1
2135 2179
2136 2180 # Automatically removing tmpdir sounds convenient, but could
2137 2181 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2138 2182 # or "--tmpdir=$HOME".
2139 2183 #vlog("# Removing temp dir", tmpdir)
2140 2184 #shutil.rmtree(tmpdir)
2141 2185 os.makedirs(tmpdir)
2142 2186 else:
2143 2187 d = None
2144 2188 if os.name == 'nt':
2145 2189 # without this, we get the default temp dir location, but
2146 2190 # in all lowercase, which causes troubles with paths (issue3490)
2147 2191 d = osenvironb.get(b'TMP', None)
2148 2192 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2149 2193
2150 2194 self._hgtmp = osenvironb[b'HGTMP'] = (
2151 2195 os.path.realpath(tmpdir))
2152 2196
2153 2197 if self.options.with_hg:
2154 2198 self._installdir = None
2155 2199 whg = self.options.with_hg
2156 2200 self._bindir = os.path.dirname(os.path.realpath(whg))
2157 2201 assert isinstance(self._bindir, bytes)
2158 2202 self._hgcommand = os.path.basename(whg)
2159 2203 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2160 2204 os.makedirs(self._tmpbindir)
2161 2205
2162 2206 # This looks redundant with how Python initializes sys.path from
2163 2207 # the location of the script being executed. Needed because the
2164 2208 # "hg" specified by --with-hg is not the only Python script
2165 2209 # executed in the test suite that needs to import 'mercurial'
2166 2210 # ... which means it's not really redundant at all.
2167 2211 self._pythondir = self._bindir
2168 2212 else:
2169 2213 self._installdir = os.path.join(self._hgtmp, b"install")
2170 2214 self._bindir = os.path.join(self._installdir, b"bin")
2171 2215 self._hgcommand = b'hg'
2172 2216 self._tmpbindir = self._bindir
2173 2217 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2174 2218
2175 2219 # set CHGHG, then replace "hg" command by "chg"
2176 2220 chgbindir = self._bindir
2177 2221 if self.options.chg or self.options.with_chg:
2178 2222 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2179 2223 else:
2180 2224 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2181 2225 if self.options.chg:
2182 2226 self._hgcommand = b'chg'
2183 2227 elif self.options.with_chg:
2184 2228 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2185 2229 self._hgcommand = os.path.basename(self.options.with_chg)
2186 2230
2187 2231 osenvironb[b"BINDIR"] = self._bindir
2188 2232 osenvironb[b"PYTHON"] = PYTHON
2189 2233
2190 2234 if self.options.with_python3:
2191 2235 osenvironb[b'PYTHON3'] = self.options.with_python3
2192 2236
2193 2237 fileb = _bytespath(__file__)
2194 2238 runtestdir = os.path.abspath(os.path.dirname(fileb))
2195 2239 osenvironb[b'RUNTESTDIR'] = runtestdir
2196 2240 if PYTHON3:
2197 2241 sepb = _bytespath(os.pathsep)
2198 2242 else:
2199 2243 sepb = os.pathsep
2200 2244 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2201 2245 if os.path.islink(__file__):
2202 2246 # test helper will likely be at the end of the symlink
2203 2247 realfile = os.path.realpath(fileb)
2204 2248 realdir = os.path.abspath(os.path.dirname(realfile))
2205 2249 path.insert(2, realdir)
2206 2250 if chgbindir != self._bindir:
2207 2251 path.insert(1, chgbindir)
2208 2252 if self._testdir != runtestdir:
2209 2253 path = [self._testdir] + path
2210 2254 if self._tmpbindir != self._bindir:
2211 2255 path = [self._tmpbindir] + path
2212 2256 osenvironb[b"PATH"] = sepb.join(path)
2213 2257
2214 2258 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2215 2259 # can run .../tests/run-tests.py test-foo where test-foo
2216 2260 # adds an extension to HGRC. Also include run-test.py directory to
2217 2261 # import modules like heredoctest.
2218 2262 pypath = [self._pythondir, self._testdir, runtestdir]
2219 2263 # We have to augment PYTHONPATH, rather than simply replacing
2220 2264 # it, in case external libraries are only available via current
2221 2265 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2222 2266 # are in /opt/subversion.)
2223 2267 oldpypath = osenvironb.get(IMPL_PATH)
2224 2268 if oldpypath:
2225 2269 pypath.append(oldpypath)
2226 2270 osenvironb[IMPL_PATH] = sepb.join(pypath)
2227 2271
2228 2272 if self.options.pure:
2229 2273 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2230 2274 os.environ["HGMODULEPOLICY"] = "py"
2231 2275
2232 2276 if self.options.allow_slow_tests:
2233 2277 os.environ["HGTEST_SLOW"] = "slow"
2234 2278 elif 'HGTEST_SLOW' in os.environ:
2235 2279 del os.environ['HGTEST_SLOW']
2236 2280
2237 2281 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2238 2282
2239 2283 vlog("# Using TESTDIR", self._testdir)
2240 2284 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2241 2285 vlog("# Using HGTMP", self._hgtmp)
2242 2286 vlog("# Using PATH", os.environ["PATH"])
2243 2287 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2244 2288
2245 2289 try:
2246 2290 return self._runtests(testdescs) or 0
2247 2291 finally:
2248 2292 time.sleep(.1)
2249 2293 self._cleanup()
2250 2294
2251 2295 def findtests(self, args):
2252 2296 """Finds possible test files from arguments.
2253 2297
2254 2298 If you wish to inject custom tests into the test harness, this would
2255 2299 be a good function to monkeypatch or override in a derived class.
2256 2300 """
2257 2301 if not args:
2258 2302 if self.options.changed:
2259 2303 proc = Popen4('hg st --rev "%s" -man0 .' %
2260 2304 self.options.changed, None, 0)
2261 2305 stdout, stderr = proc.communicate()
2262 2306 args = stdout.strip(b'\0').split(b'\0')
2263 2307 else:
2264 2308 args = os.listdir(b'.')
2265 2309
2266 return [{'path': t} for t in args
2267 if os.path.basename(t).startswith(b'test-')
2268 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2310 tests = []
2311 for t in args:
2312 if not (os.path.basename(t).startswith(b'test-')
2313 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2314 continue
2315 if t.endswith(b'.t'):
2316 # .t file may contain multiple test cases
2317 cases = sorted(parsettestcases(t))
2318 if cases:
2319 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2320 else:
2321 tests.append({'path': t})
2322 else:
2323 tests.append({'path': t})
2324 return tests
2269 2325
2270 2326 def _runtests(self, testdescs):
2271 2327 def _reloadtest(test, i):
2272 2328 # convert a test back to its description dict
2273 2329 desc = {'path': test.path}
2330 case = getattr(test, '_case', None)
2331 if case:
2332 desc['case'] = case
2274 2333 return self._gettest(desc, i)
2275 2334
2276 2335 try:
2277 2336 if self._installdir:
2278 2337 self._installhg()
2279 2338 self._checkhglib("Testing")
2280 2339 else:
2281 2340 self._usecorrectpython()
2282 2341 if self.options.chg:
2283 2342 assert self._installdir
2284 2343 self._installchg()
2285 2344
2286 2345 if self.options.restart:
2287 2346 orig = list(testdescs)
2288 2347 while testdescs:
2289 if os.path.exists(testdescs[0]['path'] + ".err"):
2348 desc = testdescs[0]
2349 if 'case' in desc:
2350 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2351 else:
2352 errpath = b'%s.err' % desc['path']
2353 if os.path.exists(errpath):
2290 2354 break
2291 2355 testdescs.pop(0)
2292 2356 if not testdescs:
2293 2357 print("running all tests")
2294 2358 testdescs = orig
2295 2359
2296 2360 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2297 2361
2298 2362 failed = False
2299 2363 warned = False
2300 2364 kws = self.options.keywords
2301 2365 if kws is not None and PYTHON3:
2302 2366 kws = kws.encode('utf-8')
2303 2367
2304 2368 suite = TestSuite(self._testdir,
2305 2369 jobs=self.options.jobs,
2306 2370 whitelist=self.options.whitelisted,
2307 2371 blacklist=self.options.blacklist,
2308 2372 retest=self.options.retest,
2309 2373 keywords=kws,
2310 2374 loop=self.options.loop,
2311 2375 runs_per_test=self.options.runs_per_test,
2312 2376 showchannels=self.options.showchannels,
2313 2377 tests=tests, loadtest=_reloadtest)
2314 2378 verbosity = 1
2315 2379 if self.options.verbose:
2316 2380 verbosity = 2
2317 2381 runner = TextTestRunner(self, verbosity=verbosity)
2318 2382 result = runner.run(suite)
2319 2383
2320 2384 if result.failures:
2321 2385 failed = True
2322 2386 if result.warned:
2323 2387 warned = True
2324 2388
2325 2389 if self.options.anycoverage:
2326 2390 self._outputcoverage()
2327 2391 except KeyboardInterrupt:
2328 2392 failed = True
2329 2393 print("\ninterrupted!")
2330 2394
2331 2395 if failed:
2332 2396 return 1
2333 2397 if warned:
2334 2398 return 80
2335 2399
2336 2400 def _getport(self, count):
2337 2401 port = self._ports.get(count) # do we have a cached entry?
2338 2402 if port is None:
2339 2403 portneeded = 3
2340 2404 # above 100 tries we just give up and let test reports failure
2341 2405 for tries in xrange(100):
2342 2406 allfree = True
2343 2407 port = self.options.port + self._portoffset
2344 2408 for idx in xrange(portneeded):
2345 2409 if not checkportisavailable(port + idx):
2346 2410 allfree = False
2347 2411 break
2348 2412 self._portoffset += portneeded
2349 2413 if allfree:
2350 2414 break
2351 2415 self._ports[count] = port
2352 2416 return port
2353 2417
2354 2418 def _gettest(self, testdesc, count):
2355 2419 """Obtain a Test by looking at its filename.
2356 2420
2357 2421 Returns a Test instance. The Test may not be runnable if it doesn't
2358 2422 map to a known type.
2359 2423 """
2360 2424 path = testdesc['path']
2361 2425 lctest = path.lower()
2362 2426 testcls = Test
2363 2427
2364 2428 for ext, cls in self.TESTTYPES:
2365 2429 if lctest.endswith(ext):
2366 2430 testcls = cls
2367 2431 break
2368 2432
2369 2433 refpath = os.path.join(self._testdir, path)
2370 2434 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2371 2435
2436 # extra keyword parameters. 'case' is used by .t tests
2437 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2438
2372 2439 t = testcls(refpath, tmpdir,
2373 2440 keeptmpdir=self.options.keep_tmpdir,
2374 2441 debug=self.options.debug,
2375 2442 timeout=self.options.timeout,
2376 2443 startport=self._getport(count),
2377 2444 extraconfigopts=self.options.extra_config_opt,
2378 2445 py3kwarnings=self.options.py3k_warnings,
2379 2446 shell=self.options.shell,
2380 2447 hgcommand=self._hgcommand,
2381 2448 usechg=bool(self.options.with_chg or self.options.chg),
2382 useipv6=useipv6)
2449 useipv6=useipv6, **kwds)
2383 2450 t.should_reload = True
2384 2451 return t
2385 2452
2386 2453 def _cleanup(self):
2387 2454 """Clean up state from this test invocation."""
2388 2455 if self.options.keep_tmpdir:
2389 2456 return
2390 2457
2391 2458 vlog("# Cleaning up HGTMP", self._hgtmp)
2392 2459 shutil.rmtree(self._hgtmp, True)
2393 2460 for f in self._createdfiles:
2394 2461 try:
2395 2462 os.remove(f)
2396 2463 except OSError:
2397 2464 pass
2398 2465
2399 2466 def _usecorrectpython(self):
2400 2467 """Configure the environment to use the appropriate Python in tests."""
2401 2468 # Tests must use the same interpreter as us or bad things will happen.
2402 2469 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2403 2470 if getattr(os, 'symlink', None):
2404 2471 vlog("# Making python executable in test path a symlink to '%s'" %
2405 2472 sys.executable)
2406 2473 mypython = os.path.join(self._tmpbindir, pyexename)
2407 2474 try:
2408 2475 if os.readlink(mypython) == sys.executable:
2409 2476 return
2410 2477 os.unlink(mypython)
2411 2478 except OSError as err:
2412 2479 if err.errno != errno.ENOENT:
2413 2480 raise
2414 2481 if self._findprogram(pyexename) != sys.executable:
2415 2482 try:
2416 2483 os.symlink(sys.executable, mypython)
2417 2484 self._createdfiles.append(mypython)
2418 2485 except OSError as err:
2419 2486 # child processes may race, which is harmless
2420 2487 if err.errno != errno.EEXIST:
2421 2488 raise
2422 2489 else:
2423 2490 exedir, exename = os.path.split(sys.executable)
2424 2491 vlog("# Modifying search path to find %s as %s in '%s'" %
2425 2492 (exename, pyexename, exedir))
2426 2493 path = os.environ['PATH'].split(os.pathsep)
2427 2494 while exedir in path:
2428 2495 path.remove(exedir)
2429 2496 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2430 2497 if not self._findprogram(pyexename):
2431 2498 print("WARNING: Cannot find %s in search path" % pyexename)
2432 2499
2433 2500 def _installhg(self):
2434 2501 """Install hg into the test environment.
2435 2502
2436 2503 This will also configure hg with the appropriate testing settings.
2437 2504 """
2438 2505 vlog("# Performing temporary installation of HG")
2439 2506 installerrs = os.path.join(self._hgtmp, b"install.err")
2440 2507 compiler = ''
2441 2508 if self.options.compiler:
2442 2509 compiler = '--compiler ' + self.options.compiler
2443 2510 if self.options.pure:
2444 2511 pure = b"--pure"
2445 2512 else:
2446 2513 pure = b""
2447 2514
2448 2515 # Run installer in hg root
2449 2516 script = os.path.realpath(sys.argv[0])
2450 2517 exe = sys.executable
2451 2518 if PYTHON3:
2452 2519 compiler = _bytespath(compiler)
2453 2520 script = _bytespath(script)
2454 2521 exe = _bytespath(exe)
2455 2522 hgroot = os.path.dirname(os.path.dirname(script))
2456 2523 self._hgroot = hgroot
2457 2524 os.chdir(hgroot)
2458 2525 nohome = b'--home=""'
2459 2526 if os.name == 'nt':
2460 2527 # The --home="" trick works only on OS where os.sep == '/'
2461 2528 # because of a distutils convert_path() fast-path. Avoid it at
2462 2529 # least on Windows for now, deal with .pydistutils.cfg bugs
2463 2530 # when they happen.
2464 2531 nohome = b''
2465 2532 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2466 2533 b' build %(compiler)s --build-base="%(base)s"'
2467 2534 b' install --force --prefix="%(prefix)s"'
2468 2535 b' --install-lib="%(libdir)s"'
2469 2536 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2470 2537 % {b'exe': exe, b'pure': pure,
2471 2538 b'compiler': compiler,
2472 2539 b'base': os.path.join(self._hgtmp, b"build"),
2473 2540 b'prefix': self._installdir, b'libdir': self._pythondir,
2474 2541 b'bindir': self._bindir,
2475 2542 b'nohome': nohome, b'logfile': installerrs})
2476 2543
2477 2544 # setuptools requires install directories to exist.
2478 2545 def makedirs(p):
2479 2546 try:
2480 2547 os.makedirs(p)
2481 2548 except OSError as e:
2482 2549 if e.errno != errno.EEXIST:
2483 2550 raise
2484 2551 makedirs(self._pythondir)
2485 2552 makedirs(self._bindir)
2486 2553
2487 2554 vlog("# Running", cmd)
2488 2555 if os.system(cmd) == 0:
2489 2556 if not self.options.verbose:
2490 2557 try:
2491 2558 os.remove(installerrs)
2492 2559 except OSError as e:
2493 2560 if e.errno != errno.ENOENT:
2494 2561 raise
2495 2562 else:
2496 2563 f = open(installerrs, 'rb')
2497 2564 for line in f:
2498 2565 if PYTHON3:
2499 2566 sys.stdout.buffer.write(line)
2500 2567 else:
2501 2568 sys.stdout.write(line)
2502 2569 f.close()
2503 2570 sys.exit(1)
2504 2571 os.chdir(self._testdir)
2505 2572
2506 2573 self._usecorrectpython()
2507 2574
2508 2575 if self.options.py3k_warnings and not self.options.anycoverage:
2509 2576 vlog("# Updating hg command to enable Py3k Warnings switch")
2510 2577 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2511 2578 lines = [line.rstrip() for line in f]
2512 2579 lines[0] += ' -3'
2513 2580 f.close()
2514 2581 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2515 2582 for line in lines:
2516 2583 f.write(line + '\n')
2517 2584 f.close()
2518 2585
2519 2586 hgbat = os.path.join(self._bindir, b'hg.bat')
2520 2587 if os.path.isfile(hgbat):
2521 2588 # hg.bat expects to be put in bin/scripts while run-tests.py
2522 2589 # installation layout put it in bin/ directly. Fix it
2523 2590 f = open(hgbat, 'rb')
2524 2591 data = f.read()
2525 2592 f.close()
2526 2593 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2527 2594 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2528 2595 b'"%~dp0python" "%~dp0hg" %*')
2529 2596 f = open(hgbat, 'wb')
2530 2597 f.write(data)
2531 2598 f.close()
2532 2599 else:
2533 2600 print('WARNING: cannot fix hg.bat reference to python.exe')
2534 2601
2535 2602 if self.options.anycoverage:
2536 2603 custom = os.path.join(self._testdir, 'sitecustomize.py')
2537 2604 target = os.path.join(self._pythondir, 'sitecustomize.py')
2538 2605 vlog('# Installing coverage trigger to %s' % target)
2539 2606 shutil.copyfile(custom, target)
2540 2607 rc = os.path.join(self._testdir, '.coveragerc')
2541 2608 vlog('# Installing coverage rc to %s' % rc)
2542 2609 os.environ['COVERAGE_PROCESS_START'] = rc
2543 2610 covdir = os.path.join(self._installdir, '..', 'coverage')
2544 2611 try:
2545 2612 os.mkdir(covdir)
2546 2613 except OSError as e:
2547 2614 if e.errno != errno.EEXIST:
2548 2615 raise
2549 2616
2550 2617 os.environ['COVERAGE_DIR'] = covdir
2551 2618
2552 2619 def _checkhglib(self, verb):
2553 2620 """Ensure that the 'mercurial' package imported by python is
2554 2621 the one we expect it to be. If not, print a warning to stderr."""
2555 2622 if ((self._bindir == self._pythondir) and
2556 2623 (self._bindir != self._tmpbindir)):
2557 2624 # The pythondir has been inferred from --with-hg flag.
2558 2625 # We cannot expect anything sensible here.
2559 2626 return
2560 2627 expecthg = os.path.join(self._pythondir, b'mercurial')
2561 2628 actualhg = self._gethgpath()
2562 2629 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2563 2630 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2564 2631 ' (expected %s)\n'
2565 2632 % (verb, actualhg, expecthg))
2566 2633 def _gethgpath(self):
2567 2634 """Return the path to the mercurial package that is actually found by
2568 2635 the current Python interpreter."""
2569 2636 if self._hgpath is not None:
2570 2637 return self._hgpath
2571 2638
2572 2639 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2573 2640 cmd = cmd % PYTHON
2574 2641 if PYTHON3:
2575 2642 cmd = _strpath(cmd)
2576 2643 pipe = os.popen(cmd)
2577 2644 try:
2578 2645 self._hgpath = _bytespath(pipe.read().strip())
2579 2646 finally:
2580 2647 pipe.close()
2581 2648
2582 2649 return self._hgpath
2583 2650
2584 2651 def _installchg(self):
2585 2652 """Install chg into the test environment"""
2586 2653 vlog('# Performing temporary installation of CHG')
2587 2654 assert os.path.dirname(self._bindir) == self._installdir
2588 2655 assert self._hgroot, 'must be called after _installhg()'
2589 2656 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2590 2657 % {b'make': 'make', # TODO: switch by option or environment?
2591 2658 b'prefix': self._installdir})
2592 2659 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2593 2660 vlog("# Running", cmd)
2594 2661 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2595 2662 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2596 2663 stderr=subprocess.STDOUT)
2597 2664 out, _err = proc.communicate()
2598 2665 if proc.returncode != 0:
2599 2666 if PYTHON3:
2600 2667 sys.stdout.buffer.write(out)
2601 2668 else:
2602 2669 sys.stdout.write(out)
2603 2670 sys.exit(1)
2604 2671
2605 2672 def _outputcoverage(self):
2606 2673 """Produce code coverage output."""
2607 2674 import coverage
2608 2675 coverage = coverage.coverage
2609 2676
2610 2677 vlog('# Producing coverage report')
2611 2678 # chdir is the easiest way to get short, relative paths in the
2612 2679 # output.
2613 2680 os.chdir(self._hgroot)
2614 2681 covdir = os.path.join(self._installdir, '..', 'coverage')
2615 2682 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2616 2683
2617 2684 # Map install directory paths back to source directory.
2618 2685 cov.config.paths['srcdir'] = ['.', self._pythondir]
2619 2686
2620 2687 cov.combine()
2621 2688
2622 2689 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2623 2690 cov.report(ignore_errors=True, omit=omit)
2624 2691
2625 2692 if self.options.htmlcov:
2626 2693 htmldir = os.path.join(self._testdir, 'htmlcov')
2627 2694 cov.html_report(directory=htmldir, omit=omit)
2628 2695 if self.options.annotate:
2629 2696 adir = os.path.join(self._testdir, 'annotated')
2630 2697 if not os.path.isdir(adir):
2631 2698 os.mkdir(adir)
2632 2699 cov.annotate(directory=adir, omit=omit)
2633 2700
2634 2701 def _findprogram(self, program):
2635 2702 """Search PATH for a executable program"""
2636 2703 dpb = _bytespath(os.defpath)
2637 2704 sepb = _bytespath(os.pathsep)
2638 2705 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2639 2706 name = os.path.join(p, program)
2640 2707 if os.name == 'nt' or os.access(name, os.X_OK):
2641 2708 return name
2642 2709 return None
2643 2710
2644 2711 def _checktools(self):
2645 2712 """Ensure tools required to run tests are present."""
2646 2713 for p in self.REQUIREDTOOLS:
2647 2714 if os.name == 'nt' and not p.endswith('.exe'):
2648 2715 p += '.exe'
2649 2716 found = self._findprogram(p)
2650 2717 if found:
2651 2718 vlog("# Found prerequisite", p, "at", found)
2652 2719 else:
2653 2720 print("WARNING: Did not find prerequisite tool: %s " %
2654 2721 p.decode("utf-8"))
2655 2722
2656 2723 if __name__ == '__main__':
2657 2724 runner = TestRunner()
2658 2725
2659 2726 try:
2660 2727 import msvcrt
2661 2728 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2662 2729 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2663 2730 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2664 2731 except ImportError:
2665 2732 pass
2666 2733
2667 2734 sys.exit(runner.run(sys.argv[1:]))
@@ -1,902 +1,977 b''
1 1 This file tests the behavior of run-tests.py itself.
2 2
3 3 Avoid interference from actual test env:
4 4
5 5 $ . "$TESTDIR/helper-runtests.sh"
6 6
7 7 Smoke test with install
8 8 ============
9 9
10 10 $ run-tests.py $HGTEST_RUN_TESTS_PURE -l
11 11
12 12 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
13 13
14 14 Define a helper to avoid the install step
15 15 =============
16 16 $ rt()
17 17 > {
18 18 > run-tests.py --with-hg=`which hg` "$@"
19 19 > }
20 20
21 21 error paths
22 22
23 23 #if symlink
24 24 $ ln -s `which true` hg
25 25 $ run-tests.py --with-hg=./hg
26 26 warning: --with-hg should specify an hg script
27 27
28 28 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
29 29 $ rm hg
30 30 #endif
31 31
32 32 #if execbit
33 33 $ touch hg
34 34 $ run-tests.py --with-hg=./hg
35 35 Usage: run-tests.py [options] [tests]
36 36
37 37 run-tests.py: error: --with-hg must specify an executable hg script
38 38 [2]
39 39 $ rm hg
40 40 #endif
41 41
42 42 Features for testing optional lines
43 43 ===================================
44 44
45 45 $ cat > hghaveaddon.py <<EOF
46 46 > import hghave
47 47 > @hghave.check("custom", "custom hghave feature")
48 48 > def has_custom():
49 49 > return True
50 50 > @hghave.check("missing", "missing hghave feature")
51 51 > def has_missing():
52 52 > return False
53 53 > EOF
54 54
55 55 an empty test
56 56 =======================
57 57
58 58 $ touch test-empty.t
59 59 $ rt
60 60 .
61 61 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
62 62 $ rm test-empty.t
63 63
64 64 a succesful test
65 65 =======================
66 66
67 67 $ cat > test-success.t << EOF
68 68 > $ echo babar
69 69 > babar
70 70 > $ echo xyzzy
71 71 > dont_print (?)
72 72 > nothing[42]line (re) (?)
73 73 > never*happens (glob) (?)
74 74 > more_nothing (?)
75 75 > xyzzy
76 76 > nor this (?)
77 77 > $ printf 'abc\ndef\nxyz\n'
78 78 > 123 (?)
79 79 > abc
80 80 > def (?)
81 81 > 456 (?)
82 82 > xyz
83 83 > $ printf 'zyx\nwvu\ntsr\n'
84 84 > abc (?)
85 85 > zyx (custom !)
86 86 > wvu
87 87 > no_print (no-custom !)
88 88 > tsr (no-missing !)
89 89 > missing (missing !)
90 90 > EOF
91 91
92 92 $ rt
93 93 .
94 94 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
95 95
96 96 failing test
97 97 ==================
98 98
99 99 test churn with globs
100 100 $ cat > test-failure.t <<EOF
101 101 > $ echo "bar-baz"; echo "bar-bad"
102 102 > bar*bad (glob)
103 103 > bar*baz (glob)
104 104 > EOF
105 105 $ rt test-failure.t
106 106
107 107 --- $TESTTMP/test-failure.t
108 108 +++ $TESTTMP/test-failure.t.err
109 109 @@ -1,3 +1,3 @@
110 110 $ echo "bar-baz"; echo "bar-bad"
111 111 + bar*baz (glob)
112 112 bar*bad (glob)
113 113 - bar*baz (glob)
114 114
115 115 ERROR: test-failure.t output changed
116 116 !
117 117 Failed test-failure.t: output changed
118 118 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
119 119 python hash seed: * (glob)
120 120 [1]
121 121
122 122 basic failing test
123 123 $ cat > test-failure.t << EOF
124 124 > $ echo babar
125 125 > rataxes
126 126 > This is a noop statement so that
127 127 > this test is still more bytes than success.
128 128 > pad pad pad pad............................................................
129 129 > pad pad pad pad............................................................
130 130 > pad pad pad pad............................................................
131 131 > pad pad pad pad............................................................
132 132 > pad pad pad pad............................................................
133 133 > pad pad pad pad............................................................
134 134 > EOF
135 135
136 136 >>> fh = open('test-failure-unicode.t', 'wb')
137 137 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
138 138 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
139 139
140 140 $ rt
141 141
142 142 --- $TESTTMP/test-failure.t
143 143 +++ $TESTTMP/test-failure.t.err
144 144 @@ -1,5 +1,5 @@
145 145 $ echo babar
146 146 - rataxes
147 147 + babar
148 148 This is a noop statement so that
149 149 this test is still more bytes than success.
150 150 pad pad pad pad............................................................
151 151
152 152 ERROR: test-failure.t output changed
153 153 !.
154 154 --- $TESTTMP/test-failure-unicode.t
155 155 +++ $TESTTMP/test-failure-unicode.t.err
156 156 @@ -1,2 +1,2 @@
157 157 $ echo babar\xce\xb1 (esc)
158 158 - l\xce\xb5\xce\xb5t (esc)
159 159 + babar\xce\xb1 (esc)
160 160
161 161 ERROR: test-failure-unicode.t output changed
162 162 !
163 163 Failed test-failure.t: output changed
164 164 Failed test-failure-unicode.t: output changed
165 165 # Ran 3 tests, 0 skipped, 0 warned, 2 failed.
166 166 python hash seed: * (glob)
167 167 [1]
168 168
169 169 test --xunit support
170 170 $ rt --xunit=xunit.xml
171 171
172 172 --- $TESTTMP/test-failure.t
173 173 +++ $TESTTMP/test-failure.t.err
174 174 @@ -1,5 +1,5 @@
175 175 $ echo babar
176 176 - rataxes
177 177 + babar
178 178 This is a noop statement so that
179 179 this test is still more bytes than success.
180 180 pad pad pad pad............................................................
181 181
182 182 ERROR: test-failure.t output changed
183 183 !.
184 184 --- $TESTTMP/test-failure-unicode.t
185 185 +++ $TESTTMP/test-failure-unicode.t.err
186 186 @@ -1,2 +1,2 @@
187 187 $ echo babar\xce\xb1 (esc)
188 188 - l\xce\xb5\xce\xb5t (esc)
189 189 + babar\xce\xb1 (esc)
190 190
191 191 ERROR: test-failure-unicode.t output changed
192 192 !
193 193 Failed test-failure.t: output changed
194 194 Failed test-failure-unicode.t: output changed
195 195 # Ran 3 tests, 0 skipped, 0 warned, 2 failed.
196 196 python hash seed: * (glob)
197 197 [1]
198 198 $ cat xunit.xml
199 199 <?xml version="1.0" encoding="utf-8"?>
200 200 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
201 201 <testcase name="test-success.t" time="*"/> (glob)
202 202 <testcase name="test-failure-unicode.t" time="*"> (glob)
203 203 <![CDATA[--- $TESTTMP/test-failure-unicode.t
204 204 +++ $TESTTMP/test-failure-unicode.t.err
205 205 @@ -1,2 +1,2 @@
206 206 $ echo babar\xce\xb1 (esc)
207 207 - l\xce\xb5\xce\xb5t (esc)
208 208 + babar\xce\xb1 (esc)
209 209 ]]> </testcase>
210 210 <testcase name="test-failure.t" time="*"> (glob)
211 211 <![CDATA[--- $TESTTMP/test-failure.t
212 212 +++ $TESTTMP/test-failure.t.err
213 213 @@ -1,5 +1,5 @@
214 214 $ echo babar
215 215 - rataxes
216 216 + babar
217 217 This is a noop statement so that
218 218 this test is still more bytes than success.
219 219 pad pad pad pad............................................................
220 220 ]]> </testcase>
221 221 </testsuite>
222 222
223 223 $ cat .testtimes
224 224 test-failure-unicode.t * (glob)
225 225 test-failure.t * (glob)
226 226 test-success.t * (glob)
227 227 $ rm test-failure-unicode.t
228 228
229 229 test for --retest
230 230 ====================
231 231
232 232 $ rt --retest
233 233
234 234 --- $TESTTMP/test-failure.t
235 235 +++ $TESTTMP/test-failure.t.err
236 236 @@ -1,5 +1,5 @@
237 237 $ echo babar
238 238 - rataxes
239 239 + babar
240 240 This is a noop statement so that
241 241 this test is still more bytes than success.
242 242 pad pad pad pad............................................................
243 243
244 244 ERROR: test-failure.t output changed
245 245 !
246 246 Failed test-failure.t: output changed
247 247 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
248 248 python hash seed: * (glob)
249 249 [1]
250 250
251 251 Selecting Tests To Run
252 252 ======================
253 253
254 254 successful
255 255
256 256 $ rt test-success.t
257 257 .
258 258 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
259 259
260 260 success w/ keyword
261 261 $ rt -k xyzzy
262 262 .
263 263 # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
264 264
265 265 failed
266 266
267 267 $ rt test-failure.t
268 268
269 269 --- $TESTTMP/test-failure.t
270 270 +++ $TESTTMP/test-failure.t.err
271 271 @@ -1,5 +1,5 @@
272 272 $ echo babar
273 273 - rataxes
274 274 + babar
275 275 This is a noop statement so that
276 276 this test is still more bytes than success.
277 277 pad pad pad pad............................................................
278 278
279 279 ERROR: test-failure.t output changed
280 280 !
281 281 Failed test-failure.t: output changed
282 282 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
283 283 python hash seed: * (glob)
284 284 [1]
285 285
286 286 failure w/ keyword
287 287 $ rt -k rataxes
288 288
289 289 --- $TESTTMP/test-failure.t
290 290 +++ $TESTTMP/test-failure.t.err
291 291 @@ -1,5 +1,5 @@
292 292 $ echo babar
293 293 - rataxes
294 294 + babar
295 295 This is a noop statement so that
296 296 this test is still more bytes than success.
297 297 pad pad pad pad............................................................
298 298
299 299 ERROR: test-failure.t output changed
300 300 !
301 301 Failed test-failure.t: output changed
302 302 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
303 303 python hash seed: * (glob)
304 304 [1]
305 305
306 306 Verify that when a process fails to start we show a useful message
307 307 ==================================================================
308 308
309 309 $ cat > test-serve-fail.t <<EOF
310 310 > $ echo 'abort: child process failed to start blah'
311 311 > EOF
312 312 $ rt test-serve-fail.t
313 313
314 314 ERROR: test-serve-fail.t output changed
315 315 !
316 316 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
317 317 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
318 318 python hash seed: * (glob)
319 319 [1]
320 320 $ rm test-serve-fail.t
321 321
322 322 Verify that we can try other ports
323 323 ===================================
324 324 $ hg init inuse
325 325 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
326 326 $ cat blocks.pid >> $DAEMON_PIDS
327 327 $ cat > test-serve-inuse.t <<EOF
328 328 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
329 329 > $ cat hg.pid >> \$DAEMON_PIDS
330 330 > EOF
331 331 $ rt test-serve-inuse.t
332 332 .
333 333 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
334 334 $ rm test-serve-inuse.t
335 335 $ killdaemons.py $DAEMON_PIDS
336 336 $ rm $DAEMON_PIDS
337 337
338 338 Running In Debug Mode
339 339 ======================
340 340
341 341 $ rt --debug 2>&1 | grep -v pwd
342 342 + echo *SALT* 0 0 (glob)
343 343 *SALT* 0 0 (glob)
344 344 + echo babar
345 345 babar
346 346 + echo *SALT* 10 0 (glob)
347 347 *SALT* 10 0 (glob)
348 348 *+ echo *SALT* 0 0 (glob)
349 349 *SALT* 0 0 (glob)
350 350 + echo babar
351 351 babar
352 352 + echo *SALT* 2 0 (glob)
353 353 *SALT* 2 0 (glob)
354 354 + echo xyzzy
355 355 xyzzy
356 356 + echo *SALT* 9 0 (glob)
357 357 *SALT* 9 0 (glob)
358 358 + printf *abc\ndef\nxyz\n* (glob)
359 359 abc
360 360 def
361 361 xyz
362 362 + echo *SALT* 15 0 (glob)
363 363 *SALT* 15 0 (glob)
364 364 + printf *zyx\nwvu\ntsr\n* (glob)
365 365 zyx
366 366 wvu
367 367 tsr
368 368 + echo *SALT* 22 0 (glob)
369 369 *SALT* 22 0 (glob)
370 370 .
371 371 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
372 372
373 373 Parallel runs
374 374 ==============
375 375
376 376 (duplicate the failing test to get predictable output)
377 377 $ cp test-failure.t test-failure-copy.t
378 378
379 379 $ rt --jobs 2 test-failure*.t -n
380 380 !!
381 381 Failed test-failure*.t: output changed (glob)
382 382 Failed test-failure*.t: output changed (glob)
383 383 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
384 384 python hash seed: * (glob)
385 385 [1]
386 386
387 387 failures in parallel with --first should only print one failure
388 388 >>> f = open('test-nothing.t', 'w')
389 389 >>> f.write('foo\n' * 1024) and None
390 390 >>> f.write(' $ sleep 1') and None
391 391 $ rt --jobs 2 --first
392 392
393 393 --- $TESTTMP/test-failure*.t (glob)
394 394 +++ $TESTTMP/test-failure*.t.err (glob)
395 395 @@ -1,5 +1,5 @@
396 396 $ echo babar
397 397 - rataxes
398 398 + babar
399 399 This is a noop statement so that
400 400 this test is still more bytes than success.
401 401 pad pad pad pad............................................................
402 402
403 403 Failed test-failure*.t: output changed (glob)
404 404 Failed test-nothing.t: output changed
405 405 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
406 406 python hash seed: * (glob)
407 407 [1]
408 408
409 409
410 410 (delete the duplicated test file)
411 411 $ rm test-failure-copy.t test-nothing.t
412 412
413 413
414 414 Interactive run
415 415 ===============
416 416
417 417 (backup the failing test)
418 418 $ cp test-failure.t backup
419 419
420 420 Refuse the fix
421 421
422 422 $ echo 'n' | rt -i
423 423
424 424 --- $TESTTMP/test-failure.t
425 425 +++ $TESTTMP/test-failure.t.err
426 426 @@ -1,5 +1,5 @@
427 427 $ echo babar
428 428 - rataxes
429 429 + babar
430 430 This is a noop statement so that
431 431 this test is still more bytes than success.
432 432 pad pad pad pad............................................................
433 433 Accept this change? [n]
434 434 ERROR: test-failure.t output changed
435 435 !.
436 436 Failed test-failure.t: output changed
437 437 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
438 438 python hash seed: * (glob)
439 439 [1]
440 440
441 441 $ cat test-failure.t
442 442 $ echo babar
443 443 rataxes
444 444 This is a noop statement so that
445 445 this test is still more bytes than success.
446 446 pad pad pad pad............................................................
447 447 pad pad pad pad............................................................
448 448 pad pad pad pad............................................................
449 449 pad pad pad pad............................................................
450 450 pad pad pad pad............................................................
451 451 pad pad pad pad............................................................
452 452
453 453 Interactive with custom view
454 454
455 455 $ echo 'n' | rt -i --view echo
456 456 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
457 457 Accept this change? [n]* (glob)
458 458 ERROR: test-failure.t output changed
459 459 !.
460 460 Failed test-failure.t: output changed
461 461 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
462 462 python hash seed: * (glob)
463 463 [1]
464 464
465 465 View the fix
466 466
467 467 $ echo 'y' | rt --view echo
468 468 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
469 469
470 470 ERROR: test-failure.t output changed
471 471 !.
472 472 Failed test-failure.t: output changed
473 473 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
474 474 python hash seed: * (glob)
475 475 [1]
476 476
477 477 Accept the fix
478 478
479 479 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
480 480 $ echo " saved backup bundle to \$TESTTMP/foo.hg" >> test-failure.t
481 481 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
482 482 $ echo " saved backup bundle to \$TESTTMP/foo.hg (glob)" >> test-failure.t
483 483 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
484 484 $ echo " saved backup bundle to \$TESTTMP/*.hg (glob)" >> test-failure.t
485 485 $ echo 'y' | rt -i 2>&1
486 486
487 487 --- $TESTTMP/test-failure.t
488 488 +++ $TESTTMP/test-failure.t.err
489 489 @@ -1,5 +1,5 @@
490 490 $ echo babar
491 491 - rataxes
492 492 + babar
493 493 This is a noop statement so that
494 494 this test is still more bytes than success.
495 495 pad pad pad pad............................................................
496 496 @@ -9,7 +9,7 @@
497 497 pad pad pad pad............................................................
498 498 pad pad pad pad............................................................
499 499 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
500 500 - saved backup bundle to $TESTTMP/foo.hg
501 501 + saved backup bundle to $TESTTMP/foo.hg* (glob)
502 502 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
503 503 saved backup bundle to $TESTTMP/foo.hg* (glob)
504 504 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
505 505 Accept this change? [n] ..
506 506 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
507 507
508 508 $ sed -e 's,(glob)$,&<,g' test-failure.t
509 509 $ echo babar
510 510 babar
511 511 This is a noop statement so that
512 512 this test is still more bytes than success.
513 513 pad pad pad pad............................................................
514 514 pad pad pad pad............................................................
515 515 pad pad pad pad............................................................
516 516 pad pad pad pad............................................................
517 517 pad pad pad pad............................................................
518 518 pad pad pad pad............................................................
519 519 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
520 520 saved backup bundle to $TESTTMP/foo.hg (glob)<
521 521 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
522 522 saved backup bundle to $TESTTMP/foo.hg (glob)<
523 523 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
524 524 saved backup bundle to $TESTTMP/*.hg (glob)<
525 525
526 526 (reinstall)
527 527 $ mv backup test-failure.t
528 528
529 529 No Diff
530 530 ===============
531 531
532 532 $ rt --nodiff
533 533 !.
534 534 Failed test-failure.t: output changed
535 535 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
536 536 python hash seed: * (glob)
537 537 [1]
538 538
539 539 test --tmpdir support
540 540 $ rt --tmpdir=$TESTTMP/keep test-success.t
541 541
542 542 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t (glob)
543 543 Keeping threadtmp dir: $TESTTMP/keep/child1 (glob)
544 544 .
545 545 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
546 546
547 547 timeouts
548 548 ========
549 549 $ cat > test-timeout.t <<EOF
550 550 > $ sleep 2
551 551 > $ echo pass
552 552 > pass
553 553 > EOF
554 554 > echo '#require slow' > test-slow-timeout.t
555 555 > cat test-timeout.t >> test-slow-timeout.t
556 556 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
557 557 st
558 558 Skipped test-slow-timeout.t: missing feature: allow slow tests
559 559 Failed test-timeout.t: timed out
560 560 # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
561 561 python hash seed: * (glob)
562 562 [1]
563 563 $ rt --timeout=1 --slowtimeout=3 \
564 564 > test-timeout.t test-slow-timeout.t --allow-slow-tests
565 565 .t
566 566 Failed test-timeout.t: timed out
567 567 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
568 568 python hash seed: * (glob)
569 569 [1]
570 570 $ rm test-timeout.t test-slow-timeout.t
571 571
572 572 test for --time
573 573 ==================
574 574
575 575 $ rt test-success.t --time
576 576 .
577 577 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
578 578 # Producing time report
579 579 start end cuser csys real Test
580 580 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
581 581
582 582 test for --time with --job enabled
583 583 ====================================
584 584
585 585 $ rt test-success.t --time --jobs 2
586 586 .
587 587 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
588 588 # Producing time report
589 589 start end cuser csys real Test
590 590 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
591 591
592 592 Skips
593 593 ================
594 594 $ cat > test-skip.t <<EOF
595 595 > $ echo xyzzy
596 596 > #require false
597 597 > EOF
598 598 $ rt --nodiff
599 599 !.s
600 600 Skipped test-skip.t: missing feature: nail clipper
601 601 Failed test-failure.t: output changed
602 602 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
603 603 python hash seed: * (glob)
604 604 [1]
605 605
606 606 $ rt --keyword xyzzy
607 607 .s
608 608 Skipped test-skip.t: missing feature: nail clipper
609 609 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
610 610
611 611 Skips with xml
612 612 $ rt --keyword xyzzy \
613 613 > --xunit=xunit.xml
614 614 .s
615 615 Skipped test-skip.t: missing feature: nail clipper
616 616 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
617 617 $ cat xunit.xml
618 618 <?xml version="1.0" encoding="utf-8"?>
619 619 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
620 620 <testcase name="test-success.t" time="*"/> (glob)
621 621 </testsuite>
622 622
623 623 Missing skips or blacklisted skips don't count as executed:
624 624 $ echo test-failure.t > blacklist
625 625 $ rt --blacklist=blacklist --json\
626 626 > test-failure.t test-bogus.t
627 627 ss
628 628 Skipped test-bogus.t: Doesn't exist
629 629 Skipped test-failure.t: blacklisted
630 630 # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
631 631 $ cat report.json
632 632 testreport ={
633 633 "test-bogus.t": {
634 634 "result": "skip"
635 635 },
636 636 "test-failure.t": {
637 637 "result": "skip"
638 638 }
639 639 } (no-eol)
640 640
641 641 Whitelist trumps blacklist
642 642 $ echo test-failure.t > whitelist
643 643 $ rt --blacklist=blacklist --whitelist=whitelist --json\
644 644 > test-failure.t test-bogus.t
645 645 s
646 646 --- $TESTTMP/test-failure.t
647 647 +++ $TESTTMP/test-failure.t.err
648 648 @@ -1,5 +1,5 @@
649 649 $ echo babar
650 650 - rataxes
651 651 + babar
652 652 This is a noop statement so that
653 653 this test is still more bytes than success.
654 654 pad pad pad pad............................................................
655 655
656 656 ERROR: test-failure.t output changed
657 657 !
658 658 Skipped test-bogus.t: Doesn't exist
659 659 Failed test-failure.t: output changed
660 660 # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
661 661 python hash seed: * (glob)
662 662 [1]
663 663
664 664 test for --json
665 665 ==================
666 666
667 667 $ rt --json
668 668
669 669 --- $TESTTMP/test-failure.t
670 670 +++ $TESTTMP/test-failure.t.err
671 671 @@ -1,5 +1,5 @@
672 672 $ echo babar
673 673 - rataxes
674 674 + babar
675 675 This is a noop statement so that
676 676 this test is still more bytes than success.
677 677 pad pad pad pad............................................................
678 678
679 679 ERROR: test-failure.t output changed
680 680 !.s
681 681 Skipped test-skip.t: missing feature: nail clipper
682 682 Failed test-failure.t: output changed
683 683 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
684 684 python hash seed: * (glob)
685 685 [1]
686 686
687 687 $ cat report.json
688 688 testreport ={
689 689 "test-failure.t": [\{] (re)
690 690 "csys": "\s*[\d\.]{4,5}", ? (re)
691 691 "cuser": "\s*[\d\.]{4,5}", ? (re)
692 692 "diff": "---.+\+\+\+.+", ? (re)
693 693 "end": "\s*[\d\.]{4,5}", ? (re)
694 694 "result": "failure", ? (re)
695 695 "start": "\s*[\d\.]{4,5}", ? (re)
696 696 "time": "\s*[\d\.]{4,5}" (re)
697 697 }, ? (re)
698 698 "test-skip.t": {
699 699 "csys": "\s*[\d\.]{4,5}", ? (re)
700 700 "cuser": "\s*[\d\.]{4,5}", ? (re)
701 701 "diff": "", ? (re)
702 702 "end": "\s*[\d\.]{4,5}", ? (re)
703 703 "result": "skip", ? (re)
704 704 "start": "\s*[\d\.]{4,5}", ? (re)
705 705 "time": "\s*[\d\.]{4,5}" (re)
706 706 }, ? (re)
707 707 "test-success.t": [\{] (re)
708 708 "csys": "\s*[\d\.]{4,5}", ? (re)
709 709 "cuser": "\s*[\d\.]{4,5}", ? (re)
710 710 "diff": "", ? (re)
711 711 "end": "\s*[\d\.]{4,5}", ? (re)
712 712 "result": "success", ? (re)
713 713 "start": "\s*[\d\.]{4,5}", ? (re)
714 714 "time": "\s*[\d\.]{4,5}" (re)
715 715 }
716 716 } (no-eol)
717 717
718 718 Test that failed test accepted through interactive are properly reported:
719 719
720 720 $ cp test-failure.t backup
721 721 $ echo y | rt --json -i
722 722
723 723 --- $TESTTMP/test-failure.t
724 724 +++ $TESTTMP/test-failure.t.err
725 725 @@ -1,5 +1,5 @@
726 726 $ echo babar
727 727 - rataxes
728 728 + babar
729 729 This is a noop statement so that
730 730 this test is still more bytes than success.
731 731 pad pad pad pad............................................................
732 732 Accept this change? [n] ..s
733 733 Skipped test-skip.t: missing feature: nail clipper
734 734 # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
735 735
736 736 $ cat report.json
737 737 testreport ={
738 738 "test-failure.t": [\{] (re)
739 739 "csys": "\s*[\d\.]{4,5}", ? (re)
740 740 "cuser": "\s*[\d\.]{4,5}", ? (re)
741 741 "diff": "", ? (re)
742 742 "end": "\s*[\d\.]{4,5}", ? (re)
743 743 "result": "success", ? (re)
744 744 "start": "\s*[\d\.]{4,5}", ? (re)
745 745 "time": "\s*[\d\.]{4,5}" (re)
746 746 }, ? (re)
747 747 "test-skip.t": {
748 748 "csys": "\s*[\d\.]{4,5}", ? (re)
749 749 "cuser": "\s*[\d\.]{4,5}", ? (re)
750 750 "diff": "", ? (re)
751 751 "end": "\s*[\d\.]{4,5}", ? (re)
752 752 "result": "skip", ? (re)
753 753 "start": "\s*[\d\.]{4,5}", ? (re)
754 754 "time": "\s*[\d\.]{4,5}" (re)
755 755 }, ? (re)
756 756 "test-success.t": [\{] (re)
757 757 "csys": "\s*[\d\.]{4,5}", ? (re)
758 758 "cuser": "\s*[\d\.]{4,5}", ? (re)
759 759 "diff": "", ? (re)
760 760 "end": "\s*[\d\.]{4,5}", ? (re)
761 761 "result": "success", ? (re)
762 762 "start": "\s*[\d\.]{4,5}", ? (re)
763 763 "time": "\s*[\d\.]{4,5}" (re)
764 764 }
765 765 } (no-eol)
766 766 $ mv backup test-failure.t
767 767
768 768 backslash on end of line with glob matching is handled properly
769 769
770 770 $ cat > test-glob-backslash.t << EOF
771 771 > $ echo 'foo bar \\'
772 772 > foo * \ (glob)
773 773 > EOF
774 774
775 775 $ rt test-glob-backslash.t
776 776 .
777 777 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
778 778
779 779 $ rm -f test-glob-backslash.t
780 780
781 781 Test globbing of local IP addresses
782 782 $ echo 172.16.18.1
783 783 $LOCALIP (glob)
784 784 $ echo dead:beef::1
785 785 $LOCALIP (glob)
786 786
787 787 Test reusability for third party tools
788 788 ======================================
789 789
790 790 $ mkdir "$TESTTMP"/anothertests
791 791 $ cd "$TESTTMP"/anothertests
792 792
793 793 test that `run-tests.py` can execute hghave, even if it runs not in
794 794 Mercurial source tree.
795 795
796 796 $ cat > test-hghave.t <<EOF
797 797 > #require true
798 798 > $ echo foo
799 799 > foo
800 800 > EOF
801 801 $ rt test-hghave.t
802 802 .
803 803 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
804 804
805 805 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
806 806 running is placed.
807 807
808 808 $ cat > test-runtestdir.t <<EOF
809 809 > - $TESTDIR, in which test-run-tests.t is placed
810 810 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
811 811 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
812 812 >
813 813 > #if windows
814 814 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
815 815 > #else
816 816 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
817 817 > #endif
818 818 > $ test "\$RUNTESTDIR" = "$TESTDIR"
819 819 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py
820 820 > #!/usr/bin/env python
821 821 > #
822 822 > # check-code - a style and portability checker for Mercurial
823 823 > EOF
824 824 $ rt test-runtestdir.t
825 825 .
826 826 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
827 827
828 828 #if execbit
829 829
830 830 test that TESTDIR is referred in PATH
831 831
832 832 $ cat > custom-command.sh <<EOF
833 833 > #!/bin/sh
834 834 > echo "hello world"
835 835 > EOF
836 836 $ chmod +x custom-command.sh
837 837 $ cat > test-testdir-path.t <<EOF
838 838 > $ custom-command.sh
839 839 > hello world
840 840 > EOF
841 841 $ rt test-testdir-path.t
842 842 .
843 843 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
844 844
845 845 #endif
846 846
847 847 test support for --allow-slow-tests
848 848 $ cat > test-very-slow-test.t <<EOF
849 849 > #require slow
850 850 > $ echo pass
851 851 > pass
852 852 > EOF
853 853 $ rt test-very-slow-test.t
854 854 s
855 855 Skipped test-very-slow-test.t: missing feature: allow slow tests
856 856 # Ran 0 tests, 1 skipped, 0 warned, 0 failed.
857 857 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
858 858 .
859 859 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
860 860
861 861 support for running a test outside the current directory
862 862 $ mkdir nonlocal
863 863 $ cat > nonlocal/test-is-not-here.t << EOF
864 864 > $ echo pass
865 865 > pass
866 866 > EOF
867 867 $ rt nonlocal/test-is-not-here.t
868 868 .
869 869 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
870 870
871 871 support for bisecting failed tests automatically
872 872 $ hg init bisect
873 873 $ cd bisect
874 874 $ cat >> test-bisect.t <<EOF
875 875 > $ echo pass
876 876 > pass
877 877 > EOF
878 878 $ hg add test-bisect.t
879 879 $ hg ci -m 'good'
880 880 $ cat >> test-bisect.t <<EOF
881 881 > $ echo pass
882 882 > fail
883 883 > EOF
884 884 $ hg ci -m 'bad'
885 885 $ rt --known-good-rev=0 test-bisect.t
886 886
887 887 --- $TESTTMP/anothertests/bisect/test-bisect.t
888 888 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
889 889 @@ -1,4 +1,4 @@
890 890 $ echo pass
891 891 pass
892 892 $ echo pass
893 893 - fail
894 894 + pass
895 895
896 896 ERROR: test-bisect.t output changed
897 897 !
898 898 Failed test-bisect.t: output changed
899 899 test-bisect.t broken by 72cbf122d116 (bad)
900 900 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
901 901 python hash seed: * (glob)
902 902 [1]
903
904 $ cd ..
905
906 Test cases in .t files
907 ======================
908 $ mkdir cases
909 $ cd cases
910 $ cat > test-cases-abc.t <<'EOF'
911 > #testcases A B C
912 > $ V=B
913 > #if A
914 > $ V=A
915 > #endif
916 > #if C
917 > $ V=C
918 > #endif
919 > $ echo $V | sed 's/A/C/'
920 > C
921 > #if C
922 > $ [ $V = C ]
923 > #endif
924 > #if A
925 > $ [ $V = C ]
926 > [1]
927 > #endif
928 > #if no-C
929 > $ [ $V = C ]
930 > [1]
931 > #endif
932 > $ [ $V = D ]
933 > [1]
934 > EOF
935 $ rt
936 .
937 --- $TESTTMP/anothertests/cases/test-cases-abc.t
938 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
939 @@ -7,7 +7,7 @@
940 $ V=C
941 #endif
942 $ echo $V | sed 's/A/C/'
943 - C
944 + B
945 #if C
946 $ [ $V = C ]
947 #endif
948
949 ERROR: test-cases-abc.t (case B) output changed
950 !.
951 Failed test-cases-abc.t (case B): output changed
952 # Ran 3 tests, 0 skipped, 0 warned, 1 failed.
953 python hash seed: * (glob)
954 [1]
955
956 --restart works
957
958 $ rt --restart
959
960 --- $TESTTMP/anothertests/cases/test-cases-abc.t
961 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
962 @@ -7,7 +7,7 @@
963 $ V=C
964 #endif
965 $ echo $V | sed 's/A/C/'
966 - C
967 + B
968 #if C
969 $ [ $V = C ]
970 #endif
971
972 ERROR: test-cases-abc.t (case B) output changed
973 !.
974 Failed test-cases-abc.t (case B): output changed
975 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
976 python hash seed: * (glob)
977 [1]
General Comments 0
You need to be logged in to leave comments. Login now