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