##// END OF EJS Templates
run-tests: add information about skipped tests to XUnit output...
Siddharth Agarwal -
r32715:a4d0e816 default
parent child Browse files
Show More
@@ -1,2778 +1,2789 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 2027 # See http://llg.cubic.org/docs/junit/ for a reference.
2028 2028 timesd = dict((t[0], t[3]) for t in result.times)
2029 2029 doc = minidom.Document()
2030 2030 s = doc.createElement('testsuite')
2031 2031 s.setAttribute('name', 'run-tests')
2032 2032 s.setAttribute('tests', str(result.testsRun))
2033 2033 s.setAttribute('errors', "0") # TODO
2034 2034 s.setAttribute('failures', str(len(result.failures)))
2035 2035 s.setAttribute('skipped', str(len(result.skipped) +
2036 2036 len(result.ignored)))
2037 2037 doc.appendChild(s)
2038 2038 for tc in result.successes:
2039 2039 t = doc.createElement('testcase')
2040 2040 t.setAttribute('name', tc.name)
2041 2041 tctime = timesd.get(tc.name)
2042 2042 if tctime is not None:
2043 2043 t.setAttribute('time', '%.3f' % tctime)
2044 2044 s.appendChild(t)
2045 2045 for tc, err in sorted(result.faildata.items()):
2046 2046 t = doc.createElement('testcase')
2047 2047 t.setAttribute('name', tc)
2048 2048 tctime = timesd.get(tc)
2049 2049 if tctime is not None:
2050 2050 t.setAttribute('time', '%.3f' % tctime)
2051 2051 # createCDATASection expects a unicode or it will
2052 2052 # convert using default conversion rules, which will
2053 2053 # fail if string isn't ASCII.
2054 2054 err = cdatasafe(err).decode('utf-8', 'replace')
2055 2055 cd = doc.createCDATASection(err)
2056 2056 # Use 'failure' here instead of 'error' to match errors = 0,
2057 2057 # failures = len(result.failures) in the testsuite element.
2058 2058 failelem = doc.createElement('failure')
2059 2059 failelem.setAttribute('message', 'output changed')
2060 2060 failelem.setAttribute('type', 'output-mismatch')
2061 2061 failelem.appendChild(cd)
2062 2062 t.appendChild(failelem)
2063 2063 s.appendChild(t)
2064 for tc, message in result.skipped:
2065 # According to the schema, 'skipped' has no attributes. So store
2066 # the skip message as a text node instead.
2067 t = doc.createElement('testcase')
2068 t.setAttribute('name', tc.name)
2069 message = cdatasafe(message).decode('utf-8', 'replace')
2070 cd = doc.createCDATASection(message)
2071 skipelem = doc.createElement('skipped')
2072 skipelem.appendChild(cd)
2073 t.appendChild(skipelem)
2074 s.appendChild(t)
2064 2075 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2065 2076
2066 2077 @staticmethod
2067 2078 def _writejson(result, outf):
2068 2079 timesd = {}
2069 2080 for tdata in result.times:
2070 2081 test = tdata[0]
2071 2082 timesd[test] = tdata[1:]
2072 2083
2073 2084 outcome = {}
2074 2085 groups = [('success', ((tc, None)
2075 2086 for tc in result.successes)),
2076 2087 ('failure', result.failures),
2077 2088 ('skip', result.skipped)]
2078 2089 for res, testcases in groups:
2079 2090 for tc, __ in testcases:
2080 2091 if tc.name in timesd:
2081 2092 diff = result.faildata.get(tc.name, b'')
2082 2093 tres = {'result': res,
2083 2094 'time': ('%0.3f' % timesd[tc.name][2]),
2084 2095 'cuser': ('%0.3f' % timesd[tc.name][0]),
2085 2096 'csys': ('%0.3f' % timesd[tc.name][1]),
2086 2097 'start': ('%0.3f' % timesd[tc.name][3]),
2087 2098 'end': ('%0.3f' % timesd[tc.name][4]),
2088 2099 'diff': diff.decode('unicode_escape'),
2089 2100 }
2090 2101 else:
2091 2102 # blacklisted test
2092 2103 tres = {'result': res}
2093 2104
2094 2105 outcome[tc.name] = tres
2095 2106 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2096 2107 separators=(',', ': '))
2097 2108 outf.writelines(("testreport =", jsonout))
2098 2109
2099 2110 class TestRunner(object):
2100 2111 """Holds context for executing tests.
2101 2112
2102 2113 Tests rely on a lot of state. This object holds it for them.
2103 2114 """
2104 2115
2105 2116 # Programs required to run tests.
2106 2117 REQUIREDTOOLS = [
2107 2118 b'diff',
2108 2119 b'grep',
2109 2120 b'unzip',
2110 2121 b'gunzip',
2111 2122 b'bunzip2',
2112 2123 b'sed',
2113 2124 ]
2114 2125
2115 2126 # Maps file extensions to test class.
2116 2127 TESTTYPES = [
2117 2128 (b'.py', PythonTest),
2118 2129 (b'.t', TTest),
2119 2130 ]
2120 2131
2121 2132 def __init__(self):
2122 2133 self.options = None
2123 2134 self._hgroot = None
2124 2135 self._testdir = None
2125 2136 self._hgtmp = None
2126 2137 self._installdir = None
2127 2138 self._bindir = None
2128 2139 self._tmpbinddir = None
2129 2140 self._pythondir = None
2130 2141 self._coveragefile = None
2131 2142 self._createdfiles = []
2132 2143 self._hgcommand = None
2133 2144 self._hgpath = None
2134 2145 self._portoffset = 0
2135 2146 self._ports = {}
2136 2147
2137 2148 def run(self, args, parser=None):
2138 2149 """Run the test suite."""
2139 2150 oldmask = os.umask(0o22)
2140 2151 try:
2141 2152 parser = parser or getparser()
2142 2153 options, args = parseargs(args, parser)
2143 2154 # positional arguments are paths to test files to run, so
2144 2155 # we make sure they're all bytestrings
2145 2156 args = [_bytespath(a) for a in args]
2146 2157 self.options = options
2147 2158
2148 2159 self._checktools()
2149 2160 testdescs = self.findtests(args)
2150 2161 if options.profile_runner:
2151 2162 import statprof
2152 2163 statprof.start()
2153 2164 result = self._run(testdescs)
2154 2165 if options.profile_runner:
2155 2166 statprof.stop()
2156 2167 statprof.display()
2157 2168 return result
2158 2169
2159 2170 finally:
2160 2171 os.umask(oldmask)
2161 2172
2162 2173 def _run(self, testdescs):
2163 2174 if self.options.random:
2164 2175 random.shuffle(testdescs)
2165 2176 else:
2166 2177 # keywords for slow tests
2167 2178 slow = {b'svn': 10,
2168 2179 b'cvs': 10,
2169 2180 b'hghave': 10,
2170 2181 b'largefiles-update': 10,
2171 2182 b'run-tests': 10,
2172 2183 b'corruption': 10,
2173 2184 b'race': 10,
2174 2185 b'i18n': 10,
2175 2186 b'check': 100,
2176 2187 b'gendoc': 100,
2177 2188 b'contrib-perf': 200,
2178 2189 }
2179 2190 perf = {}
2180 2191 def sortkey(f):
2181 2192 # run largest tests first, as they tend to take the longest
2182 2193 f = f['path']
2183 2194 try:
2184 2195 return perf[f]
2185 2196 except KeyError:
2186 2197 try:
2187 2198 val = -os.stat(f).st_size
2188 2199 except OSError as e:
2189 2200 if e.errno != errno.ENOENT:
2190 2201 raise
2191 2202 perf[f] = -1e9 # file does not exist, tell early
2192 2203 return -1e9
2193 2204 for kw, mul in slow.items():
2194 2205 if kw in f:
2195 2206 val *= mul
2196 2207 if f.endswith(b'.py'):
2197 2208 val /= 10.0
2198 2209 perf[f] = val / 1000.0
2199 2210 return perf[f]
2200 2211 testdescs.sort(key=sortkey)
2201 2212
2202 2213 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2203 2214 os, 'getcwdb', os.getcwd)()
2204 2215
2205 2216 if 'PYTHONHASHSEED' not in os.environ:
2206 2217 # use a random python hash seed all the time
2207 2218 # we do the randomness ourself to know what seed is used
2208 2219 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2209 2220
2210 2221 if self.options.tmpdir:
2211 2222 self.options.keep_tmpdir = True
2212 2223 tmpdir = _bytespath(self.options.tmpdir)
2213 2224 if os.path.exists(tmpdir):
2214 2225 # Meaning of tmpdir has changed since 1.3: we used to create
2215 2226 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2216 2227 # tmpdir already exists.
2217 2228 print("error: temp dir %r already exists" % tmpdir)
2218 2229 return 1
2219 2230
2220 2231 # Automatically removing tmpdir sounds convenient, but could
2221 2232 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2222 2233 # or "--tmpdir=$HOME".
2223 2234 #vlog("# Removing temp dir", tmpdir)
2224 2235 #shutil.rmtree(tmpdir)
2225 2236 os.makedirs(tmpdir)
2226 2237 else:
2227 2238 d = None
2228 2239 if os.name == 'nt':
2229 2240 # without this, we get the default temp dir location, but
2230 2241 # in all lowercase, which causes troubles with paths (issue3490)
2231 2242 d = osenvironb.get(b'TMP', None)
2232 2243 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2233 2244
2234 2245 self._hgtmp = osenvironb[b'HGTMP'] = (
2235 2246 os.path.realpath(tmpdir))
2236 2247
2237 2248 if self.options.with_hg:
2238 2249 self._installdir = None
2239 2250 whg = self.options.with_hg
2240 2251 self._bindir = os.path.dirname(os.path.realpath(whg))
2241 2252 assert isinstance(self._bindir, bytes)
2242 2253 self._hgcommand = os.path.basename(whg)
2243 2254 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2244 2255 os.makedirs(self._tmpbindir)
2245 2256
2246 2257 # This looks redundant with how Python initializes sys.path from
2247 2258 # the location of the script being executed. Needed because the
2248 2259 # "hg" specified by --with-hg is not the only Python script
2249 2260 # executed in the test suite that needs to import 'mercurial'
2250 2261 # ... which means it's not really redundant at all.
2251 2262 self._pythondir = self._bindir
2252 2263 else:
2253 2264 self._installdir = os.path.join(self._hgtmp, b"install")
2254 2265 self._bindir = os.path.join(self._installdir, b"bin")
2255 2266 self._hgcommand = b'hg'
2256 2267 self._tmpbindir = self._bindir
2257 2268 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2258 2269
2259 2270 # set CHGHG, then replace "hg" command by "chg"
2260 2271 chgbindir = self._bindir
2261 2272 if self.options.chg or self.options.with_chg:
2262 2273 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2263 2274 else:
2264 2275 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2265 2276 if self.options.chg:
2266 2277 self._hgcommand = b'chg'
2267 2278 elif self.options.with_chg:
2268 2279 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2269 2280 self._hgcommand = os.path.basename(self.options.with_chg)
2270 2281
2271 2282 osenvironb[b"BINDIR"] = self._bindir
2272 2283 osenvironb[b"PYTHON"] = PYTHON
2273 2284
2274 2285 if self.options.with_python3:
2275 2286 osenvironb[b'PYTHON3'] = self.options.with_python3
2276 2287
2277 2288 fileb = _bytespath(__file__)
2278 2289 runtestdir = os.path.abspath(os.path.dirname(fileb))
2279 2290 osenvironb[b'RUNTESTDIR'] = runtestdir
2280 2291 if PYTHON3:
2281 2292 sepb = _bytespath(os.pathsep)
2282 2293 else:
2283 2294 sepb = os.pathsep
2284 2295 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2285 2296 if os.path.islink(__file__):
2286 2297 # test helper will likely be at the end of the symlink
2287 2298 realfile = os.path.realpath(fileb)
2288 2299 realdir = os.path.abspath(os.path.dirname(realfile))
2289 2300 path.insert(2, realdir)
2290 2301 if chgbindir != self._bindir:
2291 2302 path.insert(1, chgbindir)
2292 2303 if self._testdir != runtestdir:
2293 2304 path = [self._testdir] + path
2294 2305 if self._tmpbindir != self._bindir:
2295 2306 path = [self._tmpbindir] + path
2296 2307 osenvironb[b"PATH"] = sepb.join(path)
2297 2308
2298 2309 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2299 2310 # can run .../tests/run-tests.py test-foo where test-foo
2300 2311 # adds an extension to HGRC. Also include run-test.py directory to
2301 2312 # import modules like heredoctest.
2302 2313 pypath = [self._pythondir, self._testdir, runtestdir]
2303 2314 # We have to augment PYTHONPATH, rather than simply replacing
2304 2315 # it, in case external libraries are only available via current
2305 2316 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2306 2317 # are in /opt/subversion.)
2307 2318 oldpypath = osenvironb.get(IMPL_PATH)
2308 2319 if oldpypath:
2309 2320 pypath.append(oldpypath)
2310 2321 osenvironb[IMPL_PATH] = sepb.join(pypath)
2311 2322
2312 2323 if self.options.pure:
2313 2324 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2314 2325 os.environ["HGMODULEPOLICY"] = "py"
2315 2326
2316 2327 if self.options.allow_slow_tests:
2317 2328 os.environ["HGTEST_SLOW"] = "slow"
2318 2329 elif 'HGTEST_SLOW' in os.environ:
2319 2330 del os.environ['HGTEST_SLOW']
2320 2331
2321 2332 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2322 2333
2323 2334 vlog("# Using TESTDIR", self._testdir)
2324 2335 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2325 2336 vlog("# Using HGTMP", self._hgtmp)
2326 2337 vlog("# Using PATH", os.environ["PATH"])
2327 2338 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2328 2339
2329 2340 try:
2330 2341 return self._runtests(testdescs) or 0
2331 2342 finally:
2332 2343 time.sleep(.1)
2333 2344 self._cleanup()
2334 2345
2335 2346 def findtests(self, args):
2336 2347 """Finds possible test files from arguments.
2337 2348
2338 2349 If you wish to inject custom tests into the test harness, this would
2339 2350 be a good function to monkeypatch or override in a derived class.
2340 2351 """
2341 2352 if not args:
2342 2353 if self.options.changed:
2343 2354 proc = Popen4('hg st --rev "%s" -man0 .' %
2344 2355 self.options.changed, None, 0)
2345 2356 stdout, stderr = proc.communicate()
2346 2357 args = stdout.strip(b'\0').split(b'\0')
2347 2358 else:
2348 2359 args = os.listdir(b'.')
2349 2360
2350 2361 tests = []
2351 2362 for t in args:
2352 2363 if not (os.path.basename(t).startswith(b'test-')
2353 2364 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2354 2365 continue
2355 2366 if t.endswith(b'.t'):
2356 2367 # .t file may contain multiple test cases
2357 2368 cases = sorted(parsettestcases(t))
2358 2369 if cases:
2359 2370 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2360 2371 else:
2361 2372 tests.append({'path': t})
2362 2373 else:
2363 2374 tests.append({'path': t})
2364 2375 return tests
2365 2376
2366 2377 def _runtests(self, testdescs):
2367 2378 def _reloadtest(test, i):
2368 2379 # convert a test back to its description dict
2369 2380 desc = {'path': test.path}
2370 2381 case = getattr(test, '_case', None)
2371 2382 if case:
2372 2383 desc['case'] = case
2373 2384 return self._gettest(desc, i)
2374 2385
2375 2386 try:
2376 2387 if self.options.restart:
2377 2388 orig = list(testdescs)
2378 2389 while testdescs:
2379 2390 desc = testdescs[0]
2380 2391 if 'case' in desc:
2381 2392 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2382 2393 else:
2383 2394 errpath = b'%s.err' % desc['path']
2384 2395 if os.path.exists(errpath):
2385 2396 break
2386 2397 testdescs.pop(0)
2387 2398 if not testdescs:
2388 2399 print("running all tests")
2389 2400 testdescs = orig
2390 2401
2391 2402 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2392 2403
2393 2404 failed = False
2394 2405 warned = False
2395 2406 kws = self.options.keywords
2396 2407 if kws is not None and PYTHON3:
2397 2408 kws = kws.encode('utf-8')
2398 2409
2399 2410 suite = TestSuite(self._testdir,
2400 2411 jobs=self.options.jobs,
2401 2412 whitelist=self.options.whitelisted,
2402 2413 blacklist=self.options.blacklist,
2403 2414 retest=self.options.retest,
2404 2415 keywords=kws,
2405 2416 loop=self.options.loop,
2406 2417 runs_per_test=self.options.runs_per_test,
2407 2418 showchannels=self.options.showchannels,
2408 2419 tests=tests, loadtest=_reloadtest)
2409 2420 verbosity = 1
2410 2421 if self.options.verbose:
2411 2422 verbosity = 2
2412 2423 runner = TextTestRunner(self, verbosity=verbosity)
2413 2424
2414 2425 if self.options.list_tests:
2415 2426 result = runner.listtests(suite)
2416 2427 else:
2417 2428 if self._installdir:
2418 2429 self._installhg()
2419 2430 self._checkhglib("Testing")
2420 2431 else:
2421 2432 self._usecorrectpython()
2422 2433 if self.options.chg:
2423 2434 assert self._installdir
2424 2435 self._installchg()
2425 2436
2426 2437 result = runner.run(suite)
2427 2438
2428 2439 if result.failures:
2429 2440 failed = True
2430 2441 if result.warned:
2431 2442 warned = True
2432 2443
2433 2444 if self.options.anycoverage:
2434 2445 self._outputcoverage()
2435 2446 except KeyboardInterrupt:
2436 2447 failed = True
2437 2448 print("\ninterrupted!")
2438 2449
2439 2450 if failed:
2440 2451 return 1
2441 2452 if warned:
2442 2453 return 80
2443 2454
2444 2455 def _getport(self, count):
2445 2456 port = self._ports.get(count) # do we have a cached entry?
2446 2457 if port is None:
2447 2458 portneeded = 3
2448 2459 # above 100 tries we just give up and let test reports failure
2449 2460 for tries in xrange(100):
2450 2461 allfree = True
2451 2462 port = self.options.port + self._portoffset
2452 2463 for idx in xrange(portneeded):
2453 2464 if not checkportisavailable(port + idx):
2454 2465 allfree = False
2455 2466 break
2456 2467 self._portoffset += portneeded
2457 2468 if allfree:
2458 2469 break
2459 2470 self._ports[count] = port
2460 2471 return port
2461 2472
2462 2473 def _gettest(self, testdesc, count):
2463 2474 """Obtain a Test by looking at its filename.
2464 2475
2465 2476 Returns a Test instance. The Test may not be runnable if it doesn't
2466 2477 map to a known type.
2467 2478 """
2468 2479 path = testdesc['path']
2469 2480 lctest = path.lower()
2470 2481 testcls = Test
2471 2482
2472 2483 for ext, cls in self.TESTTYPES:
2473 2484 if lctest.endswith(ext):
2474 2485 testcls = cls
2475 2486 break
2476 2487
2477 2488 refpath = os.path.join(self._testdir, path)
2478 2489 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2479 2490
2480 2491 # extra keyword parameters. 'case' is used by .t tests
2481 2492 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2482 2493
2483 2494 t = testcls(refpath, tmpdir,
2484 2495 keeptmpdir=self.options.keep_tmpdir,
2485 2496 debug=self.options.debug,
2486 2497 timeout=self.options.timeout,
2487 2498 startport=self._getport(count),
2488 2499 extraconfigopts=self.options.extra_config_opt,
2489 2500 py3kwarnings=self.options.py3k_warnings,
2490 2501 shell=self.options.shell,
2491 2502 hgcommand=self._hgcommand,
2492 2503 usechg=bool(self.options.with_chg or self.options.chg),
2493 2504 useipv6=useipv6, **kwds)
2494 2505 t.should_reload = True
2495 2506 return t
2496 2507
2497 2508 def _cleanup(self):
2498 2509 """Clean up state from this test invocation."""
2499 2510 if self.options.keep_tmpdir:
2500 2511 return
2501 2512
2502 2513 vlog("# Cleaning up HGTMP", self._hgtmp)
2503 2514 shutil.rmtree(self._hgtmp, True)
2504 2515 for f in self._createdfiles:
2505 2516 try:
2506 2517 os.remove(f)
2507 2518 except OSError:
2508 2519 pass
2509 2520
2510 2521 def _usecorrectpython(self):
2511 2522 """Configure the environment to use the appropriate Python in tests."""
2512 2523 # Tests must use the same interpreter as us or bad things will happen.
2513 2524 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2514 2525 if getattr(os, 'symlink', None):
2515 2526 vlog("# Making python executable in test path a symlink to '%s'" %
2516 2527 sys.executable)
2517 2528 mypython = os.path.join(self._tmpbindir, pyexename)
2518 2529 try:
2519 2530 if os.readlink(mypython) == sys.executable:
2520 2531 return
2521 2532 os.unlink(mypython)
2522 2533 except OSError as err:
2523 2534 if err.errno != errno.ENOENT:
2524 2535 raise
2525 2536 if self._findprogram(pyexename) != sys.executable:
2526 2537 try:
2527 2538 os.symlink(sys.executable, mypython)
2528 2539 self._createdfiles.append(mypython)
2529 2540 except OSError as err:
2530 2541 # child processes may race, which is harmless
2531 2542 if err.errno != errno.EEXIST:
2532 2543 raise
2533 2544 else:
2534 2545 exedir, exename = os.path.split(sys.executable)
2535 2546 vlog("# Modifying search path to find %s as %s in '%s'" %
2536 2547 (exename, pyexename, exedir))
2537 2548 path = os.environ['PATH'].split(os.pathsep)
2538 2549 while exedir in path:
2539 2550 path.remove(exedir)
2540 2551 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2541 2552 if not self._findprogram(pyexename):
2542 2553 print("WARNING: Cannot find %s in search path" % pyexename)
2543 2554
2544 2555 def _installhg(self):
2545 2556 """Install hg into the test environment.
2546 2557
2547 2558 This will also configure hg with the appropriate testing settings.
2548 2559 """
2549 2560 vlog("# Performing temporary installation of HG")
2550 2561 installerrs = os.path.join(self._hgtmp, b"install.err")
2551 2562 compiler = ''
2552 2563 if self.options.compiler:
2553 2564 compiler = '--compiler ' + self.options.compiler
2554 2565 if self.options.pure:
2555 2566 pure = b"--pure"
2556 2567 else:
2557 2568 pure = b""
2558 2569
2559 2570 # Run installer in hg root
2560 2571 script = os.path.realpath(sys.argv[0])
2561 2572 exe = sys.executable
2562 2573 if PYTHON3:
2563 2574 compiler = _bytespath(compiler)
2564 2575 script = _bytespath(script)
2565 2576 exe = _bytespath(exe)
2566 2577 hgroot = os.path.dirname(os.path.dirname(script))
2567 2578 self._hgroot = hgroot
2568 2579 os.chdir(hgroot)
2569 2580 nohome = b'--home=""'
2570 2581 if os.name == 'nt':
2571 2582 # The --home="" trick works only on OS where os.sep == '/'
2572 2583 # because of a distutils convert_path() fast-path. Avoid it at
2573 2584 # least on Windows for now, deal with .pydistutils.cfg bugs
2574 2585 # when they happen.
2575 2586 nohome = b''
2576 2587 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2577 2588 b' build %(compiler)s --build-base="%(base)s"'
2578 2589 b' install --force --prefix="%(prefix)s"'
2579 2590 b' --install-lib="%(libdir)s"'
2580 2591 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2581 2592 % {b'exe': exe, b'pure': pure,
2582 2593 b'compiler': compiler,
2583 2594 b'base': os.path.join(self._hgtmp, b"build"),
2584 2595 b'prefix': self._installdir, b'libdir': self._pythondir,
2585 2596 b'bindir': self._bindir,
2586 2597 b'nohome': nohome, b'logfile': installerrs})
2587 2598
2588 2599 # setuptools requires install directories to exist.
2589 2600 def makedirs(p):
2590 2601 try:
2591 2602 os.makedirs(p)
2592 2603 except OSError as e:
2593 2604 if e.errno != errno.EEXIST:
2594 2605 raise
2595 2606 makedirs(self._pythondir)
2596 2607 makedirs(self._bindir)
2597 2608
2598 2609 vlog("# Running", cmd)
2599 2610 if os.system(cmd) == 0:
2600 2611 if not self.options.verbose:
2601 2612 try:
2602 2613 os.remove(installerrs)
2603 2614 except OSError as e:
2604 2615 if e.errno != errno.ENOENT:
2605 2616 raise
2606 2617 else:
2607 2618 f = open(installerrs, 'rb')
2608 2619 for line in f:
2609 2620 if PYTHON3:
2610 2621 sys.stdout.buffer.write(line)
2611 2622 else:
2612 2623 sys.stdout.write(line)
2613 2624 f.close()
2614 2625 sys.exit(1)
2615 2626 os.chdir(self._testdir)
2616 2627
2617 2628 self._usecorrectpython()
2618 2629
2619 2630 if self.options.py3k_warnings and not self.options.anycoverage:
2620 2631 vlog("# Updating hg command to enable Py3k Warnings switch")
2621 2632 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2622 2633 lines = [line.rstrip() for line in f]
2623 2634 lines[0] += ' -3'
2624 2635 f.close()
2625 2636 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2626 2637 for line in lines:
2627 2638 f.write(line + '\n')
2628 2639 f.close()
2629 2640
2630 2641 hgbat = os.path.join(self._bindir, b'hg.bat')
2631 2642 if os.path.isfile(hgbat):
2632 2643 # hg.bat expects to be put in bin/scripts while run-tests.py
2633 2644 # installation layout put it in bin/ directly. Fix it
2634 2645 f = open(hgbat, 'rb')
2635 2646 data = f.read()
2636 2647 f.close()
2637 2648 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2638 2649 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2639 2650 b'"%~dp0python" "%~dp0hg" %*')
2640 2651 f = open(hgbat, 'wb')
2641 2652 f.write(data)
2642 2653 f.close()
2643 2654 else:
2644 2655 print('WARNING: cannot fix hg.bat reference to python.exe')
2645 2656
2646 2657 if self.options.anycoverage:
2647 2658 custom = os.path.join(self._testdir, 'sitecustomize.py')
2648 2659 target = os.path.join(self._pythondir, 'sitecustomize.py')
2649 2660 vlog('# Installing coverage trigger to %s' % target)
2650 2661 shutil.copyfile(custom, target)
2651 2662 rc = os.path.join(self._testdir, '.coveragerc')
2652 2663 vlog('# Installing coverage rc to %s' % rc)
2653 2664 os.environ['COVERAGE_PROCESS_START'] = rc
2654 2665 covdir = os.path.join(self._installdir, '..', 'coverage')
2655 2666 try:
2656 2667 os.mkdir(covdir)
2657 2668 except OSError as e:
2658 2669 if e.errno != errno.EEXIST:
2659 2670 raise
2660 2671
2661 2672 os.environ['COVERAGE_DIR'] = covdir
2662 2673
2663 2674 def _checkhglib(self, verb):
2664 2675 """Ensure that the 'mercurial' package imported by python is
2665 2676 the one we expect it to be. If not, print a warning to stderr."""
2666 2677 if ((self._bindir == self._pythondir) and
2667 2678 (self._bindir != self._tmpbindir)):
2668 2679 # The pythondir has been inferred from --with-hg flag.
2669 2680 # We cannot expect anything sensible here.
2670 2681 return
2671 2682 expecthg = os.path.join(self._pythondir, b'mercurial')
2672 2683 actualhg = self._gethgpath()
2673 2684 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2674 2685 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2675 2686 ' (expected %s)\n'
2676 2687 % (verb, actualhg, expecthg))
2677 2688 def _gethgpath(self):
2678 2689 """Return the path to the mercurial package that is actually found by
2679 2690 the current Python interpreter."""
2680 2691 if self._hgpath is not None:
2681 2692 return self._hgpath
2682 2693
2683 2694 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2684 2695 cmd = cmd % PYTHON
2685 2696 if PYTHON3:
2686 2697 cmd = _strpath(cmd)
2687 2698 pipe = os.popen(cmd)
2688 2699 try:
2689 2700 self._hgpath = _bytespath(pipe.read().strip())
2690 2701 finally:
2691 2702 pipe.close()
2692 2703
2693 2704 return self._hgpath
2694 2705
2695 2706 def _installchg(self):
2696 2707 """Install chg into the test environment"""
2697 2708 vlog('# Performing temporary installation of CHG')
2698 2709 assert os.path.dirname(self._bindir) == self._installdir
2699 2710 assert self._hgroot, 'must be called after _installhg()'
2700 2711 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2701 2712 % {b'make': 'make', # TODO: switch by option or environment?
2702 2713 b'prefix': self._installdir})
2703 2714 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2704 2715 vlog("# Running", cmd)
2705 2716 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2706 2717 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2707 2718 stderr=subprocess.STDOUT)
2708 2719 out, _err = proc.communicate()
2709 2720 if proc.returncode != 0:
2710 2721 if PYTHON3:
2711 2722 sys.stdout.buffer.write(out)
2712 2723 else:
2713 2724 sys.stdout.write(out)
2714 2725 sys.exit(1)
2715 2726
2716 2727 def _outputcoverage(self):
2717 2728 """Produce code coverage output."""
2718 2729 import coverage
2719 2730 coverage = coverage.coverage
2720 2731
2721 2732 vlog('# Producing coverage report')
2722 2733 # chdir is the easiest way to get short, relative paths in the
2723 2734 # output.
2724 2735 os.chdir(self._hgroot)
2725 2736 covdir = os.path.join(self._installdir, '..', 'coverage')
2726 2737 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2727 2738
2728 2739 # Map install directory paths back to source directory.
2729 2740 cov.config.paths['srcdir'] = ['.', self._pythondir]
2730 2741
2731 2742 cov.combine()
2732 2743
2733 2744 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2734 2745 cov.report(ignore_errors=True, omit=omit)
2735 2746
2736 2747 if self.options.htmlcov:
2737 2748 htmldir = os.path.join(self._testdir, 'htmlcov')
2738 2749 cov.html_report(directory=htmldir, omit=omit)
2739 2750 if self.options.annotate:
2740 2751 adir = os.path.join(self._testdir, 'annotated')
2741 2752 if not os.path.isdir(adir):
2742 2753 os.mkdir(adir)
2743 2754 cov.annotate(directory=adir, omit=omit)
2744 2755
2745 2756 def _findprogram(self, program):
2746 2757 """Search PATH for a executable program"""
2747 2758 dpb = _bytespath(os.defpath)
2748 2759 sepb = _bytespath(os.pathsep)
2749 2760 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2750 2761 name = os.path.join(p, program)
2751 2762 if os.name == 'nt' or os.access(name, os.X_OK):
2752 2763 return name
2753 2764 return None
2754 2765
2755 2766 def _checktools(self):
2756 2767 """Ensure tools required to run tests are present."""
2757 2768 for p in self.REQUIREDTOOLS:
2758 2769 if os.name == 'nt' and not p.endswith('.exe'):
2759 2770 p += '.exe'
2760 2771 found = self._findprogram(p)
2761 2772 if found:
2762 2773 vlog("# Found prerequisite", p, "at", found)
2763 2774 else:
2764 2775 print("WARNING: Did not find prerequisite tool: %s " %
2765 2776 p.decode("utf-8"))
2766 2777
2767 2778 if __name__ == '__main__':
2768 2779 runner = TestRunner()
2769 2780
2770 2781 try:
2771 2782 import msvcrt
2772 2783 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2773 2784 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2774 2785 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2775 2786 except ImportError:
2776 2787 pass
2777 2788
2778 2789 sys.exit(runner.run(sys.argv[1:]))
@@ -1,1059 +1,1063 b''
1 1 This file tests the behavior of run-tests.py itself.
2 2
3 3 Avoid interference from actual test env:
4 4
5 5 $ . "$TESTDIR/helper-runtests.sh"
6 6
7 7 Smoke test with install
8 8 ============
9 9
10 10 $ run-tests.py $HGTEST_RUN_TESTS_PURE -l
11 11
12 12 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
13 13
14 14 Define a helper to avoid the install step
15 15 =============
16 16 $ rt()
17 17 > {
18 18 > run-tests.py --with-hg=`which hg` "$@"
19 19 > }
20 20
21 21 error paths
22 22
23 23 #if symlink
24 24 $ ln -s `which true` hg
25 25 $ run-tests.py --with-hg=./hg
26 26 warning: --with-hg should specify an hg script
27 27
28 28 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
29 29 $ rm hg
30 30 #endif
31 31
32 32 #if execbit
33 33 $ touch hg
34 34 $ run-tests.py --with-hg=./hg
35 35 Usage: run-tests.py [options] [tests]
36 36
37 37 run-tests.py: error: --with-hg must specify an executable hg script
38 38 [2]
39 39 $ rm hg
40 40 #endif
41 41
42 42 Features for testing optional lines
43 43 ===================================
44 44
45 45 $ cat > hghaveaddon.py <<EOF
46 46 > import hghave
47 47 > @hghave.check("custom", "custom hghave feature")
48 48 > def has_custom():
49 49 > return True
50 50 > @hghave.check("missing", "missing hghave feature")
51 51 > def has_missing():
52 52 > return False
53 53 > EOF
54 54
55 55 an empty test
56 56 =======================
57 57
58 58 $ touch test-empty.t
59 59 $ rt
60 60 .
61 61 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
62 62 $ rm test-empty.t
63 63
64 64 a succesful test
65 65 =======================
66 66
67 67 $ cat > test-success.t << EOF
68 68 > $ echo babar
69 69 > babar
70 70 > $ echo xyzzy
71 71 > dont_print (?)
72 72 > nothing[42]line (re) (?)
73 73 > never*happens (glob) (?)
74 74 > more_nothing (?)
75 75 > xyzzy
76 76 > nor this (?)
77 77 > $ printf 'abc\ndef\nxyz\n'
78 78 > 123 (?)
79 79 > abc
80 80 > def (?)
81 81 > 456 (?)
82 82 > xyz
83 83 > $ printf 'zyx\nwvu\ntsr\n'
84 84 > abc (?)
85 85 > zyx (custom !)
86 86 > wvu
87 87 > no_print (no-custom !)
88 88 > tsr (no-missing !)
89 89 > missing (missing !)
90 90 > EOF
91 91
92 92 $ rt
93 93 .
94 94 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
95 95
96 96 failing test
97 97 ==================
98 98
99 99 test churn with globs
100 100 $ cat > test-failure.t <<EOF
101 101 > $ echo "bar-baz"; echo "bar-bad"
102 102 > bar*bad (glob)
103 103 > bar*baz (glob)
104 104 > EOF
105 105 $ rt test-failure.t
106 106
107 107 --- $TESTTMP/test-failure.t
108 108 +++ $TESTTMP/test-failure.t.err
109 109 @@ -1,3 +1,3 @@
110 110 $ echo "bar-baz"; echo "bar-bad"
111 111 + bar*baz (glob)
112 112 bar*bad (glob)
113 113 - bar*baz (glob)
114 114
115 115 ERROR: test-failure.t output changed
116 116 !
117 117 Failed test-failure.t: output changed
118 118 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
119 119 python hash seed: * (glob)
120 120 [1]
121 121
122 122 basic failing test
123 123 $ cat > test-failure.t << EOF
124 124 > $ echo babar
125 125 > rataxes
126 126 > This is a noop statement so that
127 127 > this test is still more bytes than success.
128 128 > pad pad pad pad............................................................
129 129 > pad pad pad pad............................................................
130 130 > pad pad pad pad............................................................
131 131 > pad pad pad pad............................................................
132 132 > pad pad pad pad............................................................
133 133 > pad pad pad pad............................................................
134 134 > EOF
135 135
136 136 >>> fh = open('test-failure-unicode.t', 'wb')
137 137 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
138 138 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
139 139
140 140 $ rt
141 141
142 142 --- $TESTTMP/test-failure.t
143 143 +++ $TESTTMP/test-failure.t.err
144 144 @@ -1,5 +1,5 @@
145 145 $ echo babar
146 146 - rataxes
147 147 + babar
148 148 This is a noop statement so that
149 149 this test is still more bytes than success.
150 150 pad pad pad pad............................................................
151 151
152 152 ERROR: test-failure.t output changed
153 153 !.
154 154 --- $TESTTMP/test-failure-unicode.t
155 155 +++ $TESTTMP/test-failure-unicode.t.err
156 156 @@ -1,2 +1,2 @@
157 157 $ echo babar\xce\xb1 (esc)
158 158 - l\xce\xb5\xce\xb5t (esc)
159 159 + babar\xce\xb1 (esc)
160 160
161 161 ERROR: test-failure-unicode.t output changed
162 162 !
163 163 Failed test-failure.t: output changed
164 164 Failed test-failure-unicode.t: output changed
165 165 # Ran 3 tests, 0 skipped, 0 warned, 2 failed.
166 166 python hash seed: * (glob)
167 167 [1]
168 168
169 169 test --xunit support
170 170 $ rt --xunit=xunit.xml
171 171
172 172 --- $TESTTMP/test-failure.t
173 173 +++ $TESTTMP/test-failure.t.err
174 174 @@ -1,5 +1,5 @@
175 175 $ echo babar
176 176 - rataxes
177 177 + babar
178 178 This is a noop statement so that
179 179 this test is still more bytes than success.
180 180 pad pad pad pad............................................................
181 181
182 182 ERROR: test-failure.t output changed
183 183 !.
184 184 --- $TESTTMP/test-failure-unicode.t
185 185 +++ $TESTTMP/test-failure-unicode.t.err
186 186 @@ -1,2 +1,2 @@
187 187 $ echo babar\xce\xb1 (esc)
188 188 - l\xce\xb5\xce\xb5t (esc)
189 189 + babar\xce\xb1 (esc)
190 190
191 191 ERROR: test-failure-unicode.t output changed
192 192 !
193 193 Failed test-failure.t: output changed
194 194 Failed test-failure-unicode.t: output changed
195 195 # Ran 3 tests, 0 skipped, 0 warned, 2 failed.
196 196 python hash seed: * (glob)
197 197 [1]
198 198 $ cat xunit.xml
199 199 <?xml version="1.0" encoding="utf-8"?>
200 200 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
201 201 <testcase name="test-success.t" time="*"/> (glob)
202 202 <testcase name="test-failure-unicode.t" time="*"> (glob)
203 203 <failure message="output changed" type="output-mismatch">
204 204 <![CDATA[--- $TESTTMP/test-failure-unicode.t
205 205 +++ $TESTTMP/test-failure-unicode.t.err
206 206 @@ -1,2 +1,2 @@
207 207 $ echo babar\xce\xb1 (esc)
208 208 - l\xce\xb5\xce\xb5t (esc)
209 209 + babar\xce\xb1 (esc)
210 210 ]]> </failure>
211 211 </testcase>
212 212 <testcase name="test-failure.t" time="*"> (glob)
213 213 <failure message="output changed" type="output-mismatch">
214 214 <![CDATA[--- $TESTTMP/test-failure.t
215 215 +++ $TESTTMP/test-failure.t.err
216 216 @@ -1,5 +1,5 @@
217 217 $ echo babar
218 218 - rataxes
219 219 + babar
220 220 This is a noop statement so that
221 221 this test is still more bytes than success.
222 222 pad pad pad pad............................................................
223 223 ]]> </failure>
224 224 </testcase>
225 225 </testsuite>
226 226
227 227 $ cat .testtimes
228 228 test-failure-unicode.t * (glob)
229 229 test-failure.t * (glob)
230 230 test-success.t * (glob)
231 231
232 232 $ rt --list-tests
233 233 test-failure-unicode.t
234 234 test-failure.t
235 235 test-success.t
236 236
237 237 $ rt --list-tests --json
238 238 test-failure-unicode.t
239 239 test-failure.t
240 240 test-success.t
241 241 $ cat report.json
242 242 testreport ={
243 243 "test-failure-unicode.t": {
244 244 "result": "success"
245 245 },
246 246 "test-failure.t": {
247 247 "result": "success"
248 248 },
249 249 "test-success.t": {
250 250 "result": "success"
251 251 }
252 252 } (no-eol)
253 253
254 254 $ rt --list-tests --xunit=xunit.xml
255 255 test-failure-unicode.t
256 256 test-failure.t
257 257 test-success.t
258 258 $ cat xunit.xml
259 259 <?xml version="1.0" encoding="utf-8"?>
260 260 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
261 261 <testcase name="test-failure-unicode.t"/>
262 262 <testcase name="test-failure.t"/>
263 263 <testcase name="test-success.t"/>
264 264 </testsuite>
265 265
266 266 $ rt --list-tests test-failure* --json --xunit=xunit.xml
267 267 test-failure-unicode.t
268 268 test-failure.t
269 269 $ cat report.json
270 270 testreport ={
271 271 "test-failure-unicode.t": {
272 272 "result": "success"
273 273 },
274 274 "test-failure.t": {
275 275 "result": "success"
276 276 }
277 277 } (no-eol)
278 278 $ cat xunit.xml
279 279 <?xml version="1.0" encoding="utf-8"?>
280 280 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
281 281 <testcase name="test-failure-unicode.t"/>
282 282 <testcase name="test-failure.t"/>
283 283 </testsuite>
284 284
285 285 $ rm test-failure-unicode.t
286 286
287 287 test for --retest
288 288 ====================
289 289
290 290 $ rt --retest
291 291
292 292 --- $TESTTMP/test-failure.t
293 293 +++ $TESTTMP/test-failure.t.err
294 294 @@ -1,5 +1,5 @@
295 295 $ echo babar
296 296 - rataxes
297 297 + babar
298 298 This is a noop statement so that
299 299 this test is still more bytes than success.
300 300 pad pad pad pad............................................................
301 301
302 302 ERROR: test-failure.t output changed
303 303 !
304 304 Failed test-failure.t: output changed
305 305 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
306 306 python hash seed: * (glob)
307 307 [1]
308 308
309 309 Selecting Tests To Run
310 310 ======================
311 311
312 312 successful
313 313
314 314 $ rt test-success.t
315 315 .
316 316 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
317 317
318 318 success w/ keyword
319 319 $ rt -k xyzzy
320 320 .
321 321 # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
322 322
323 323 failed
324 324
325 325 $ rt test-failure.t
326 326
327 327 --- $TESTTMP/test-failure.t
328 328 +++ $TESTTMP/test-failure.t.err
329 329 @@ -1,5 +1,5 @@
330 330 $ echo babar
331 331 - rataxes
332 332 + babar
333 333 This is a noop statement so that
334 334 this test is still more bytes than success.
335 335 pad pad pad pad............................................................
336 336
337 337 ERROR: test-failure.t output changed
338 338 !
339 339 Failed test-failure.t: output changed
340 340 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
341 341 python hash seed: * (glob)
342 342 [1]
343 343
344 344 failure w/ keyword
345 345 $ rt -k rataxes
346 346
347 347 --- $TESTTMP/test-failure.t
348 348 +++ $TESTTMP/test-failure.t.err
349 349 @@ -1,5 +1,5 @@
350 350 $ echo babar
351 351 - rataxes
352 352 + babar
353 353 This is a noop statement so that
354 354 this test is still more bytes than success.
355 355 pad pad pad pad............................................................
356 356
357 357 ERROR: test-failure.t output changed
358 358 !
359 359 Failed test-failure.t: output changed
360 360 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
361 361 python hash seed: * (glob)
362 362 [1]
363 363
364 364 Verify that when a process fails to start we show a useful message
365 365 ==================================================================
366 366
367 367 $ cat > test-serve-fail.t <<EOF
368 368 > $ echo 'abort: child process failed to start blah'
369 369 > EOF
370 370 $ rt test-serve-fail.t
371 371
372 372 ERROR: test-serve-fail.t output changed
373 373 !
374 374 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
375 375 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
376 376 python hash seed: * (glob)
377 377 [1]
378 378 $ rm test-serve-fail.t
379 379
380 380 Verify that we can try other ports
381 381 ===================================
382 382 $ hg init inuse
383 383 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
384 384 $ cat blocks.pid >> $DAEMON_PIDS
385 385 $ cat > test-serve-inuse.t <<EOF
386 386 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
387 387 > $ cat hg.pid >> \$DAEMON_PIDS
388 388 > EOF
389 389 $ rt test-serve-inuse.t
390 390 .
391 391 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
392 392 $ rm test-serve-inuse.t
393 393 $ killdaemons.py $DAEMON_PIDS
394 394 $ rm $DAEMON_PIDS
395 395
396 396 Running In Debug Mode
397 397 ======================
398 398
399 399 $ rt --debug 2>&1 | grep -v pwd
400 400 + echo *SALT* 0 0 (glob)
401 401 *SALT* 0 0 (glob)
402 402 + echo babar
403 403 babar
404 404 + echo *SALT* 10 0 (glob)
405 405 *SALT* 10 0 (glob)
406 406 *+ echo *SALT* 0 0 (glob)
407 407 *SALT* 0 0 (glob)
408 408 + echo babar
409 409 babar
410 410 + echo *SALT* 2 0 (glob)
411 411 *SALT* 2 0 (glob)
412 412 + echo xyzzy
413 413 xyzzy
414 414 + echo *SALT* 9 0 (glob)
415 415 *SALT* 9 0 (glob)
416 416 + printf *abc\ndef\nxyz\n* (glob)
417 417 abc
418 418 def
419 419 xyz
420 420 + echo *SALT* 15 0 (glob)
421 421 *SALT* 15 0 (glob)
422 422 + printf *zyx\nwvu\ntsr\n* (glob)
423 423 zyx
424 424 wvu
425 425 tsr
426 426 + echo *SALT* 22 0 (glob)
427 427 *SALT* 22 0 (glob)
428 428 .
429 429 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
430 430
431 431 Parallel runs
432 432 ==============
433 433
434 434 (duplicate the failing test to get predictable output)
435 435 $ cp test-failure.t test-failure-copy.t
436 436
437 437 $ rt --jobs 2 test-failure*.t -n
438 438 !!
439 439 Failed test-failure*.t: output changed (glob)
440 440 Failed test-failure*.t: output changed (glob)
441 441 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
442 442 python hash seed: * (glob)
443 443 [1]
444 444
445 445 failures in parallel with --first should only print one failure
446 446 >>> f = open('test-nothing.t', 'w')
447 447 >>> f.write('foo\n' * 1024) and None
448 448 >>> f.write(' $ sleep 1') and None
449 449 $ rt --jobs 2 --first
450 450
451 451 --- $TESTTMP/test-failure*.t (glob)
452 452 +++ $TESTTMP/test-failure*.t.err (glob)
453 453 @@ -1,5 +1,5 @@
454 454 $ echo babar
455 455 - rataxes
456 456 + babar
457 457 This is a noop statement so that
458 458 this test is still more bytes than success.
459 459 pad pad pad pad............................................................
460 460
461 461 Failed test-failure*.t: output changed (glob)
462 462 Failed test-nothing.t: output changed
463 463 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
464 464 python hash seed: * (glob)
465 465 [1]
466 466
467 467
468 468 (delete the duplicated test file)
469 469 $ rm test-failure-copy.t test-nothing.t
470 470
471 471
472 472 Interactive run
473 473 ===============
474 474
475 475 (backup the failing test)
476 476 $ cp test-failure.t backup
477 477
478 478 Refuse the fix
479 479
480 480 $ echo 'n' | rt -i
481 481
482 482 --- $TESTTMP/test-failure.t
483 483 +++ $TESTTMP/test-failure.t.err
484 484 @@ -1,5 +1,5 @@
485 485 $ echo babar
486 486 - rataxes
487 487 + babar
488 488 This is a noop statement so that
489 489 this test is still more bytes than success.
490 490 pad pad pad pad............................................................
491 491 Accept this change? [n]
492 492 ERROR: test-failure.t output changed
493 493 !.
494 494 Failed test-failure.t: output changed
495 495 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
496 496 python hash seed: * (glob)
497 497 [1]
498 498
499 499 $ cat test-failure.t
500 500 $ echo babar
501 501 rataxes
502 502 This is a noop statement so that
503 503 this test is still more bytes than success.
504 504 pad pad pad pad............................................................
505 505 pad pad pad pad............................................................
506 506 pad pad pad pad............................................................
507 507 pad pad pad pad............................................................
508 508 pad pad pad pad............................................................
509 509 pad pad pad pad............................................................
510 510
511 511 Interactive with custom view
512 512
513 513 $ echo 'n' | rt -i --view echo
514 514 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
515 515 Accept this change? [n]* (glob)
516 516 ERROR: test-failure.t output changed
517 517 !.
518 518 Failed test-failure.t: output changed
519 519 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
520 520 python hash seed: * (glob)
521 521 [1]
522 522
523 523 View the fix
524 524
525 525 $ echo 'y' | rt --view echo
526 526 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
527 527
528 528 ERROR: test-failure.t output changed
529 529 !.
530 530 Failed test-failure.t: output changed
531 531 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
532 532 python hash seed: * (glob)
533 533 [1]
534 534
535 535 Accept the fix
536 536
537 537 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
538 538 $ echo " saved backup bundle to \$TESTTMP/foo.hg" >> test-failure.t
539 539 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
540 540 $ echo " saved backup bundle to \$TESTTMP/foo.hg (glob)" >> test-failure.t
541 541 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
542 542 $ echo " saved backup bundle to \$TESTTMP/*.hg (glob)" >> test-failure.t
543 543 $ echo 'y' | rt -i 2>&1
544 544
545 545 --- $TESTTMP/test-failure.t
546 546 +++ $TESTTMP/test-failure.t.err
547 547 @@ -1,5 +1,5 @@
548 548 $ echo babar
549 549 - rataxes
550 550 + babar
551 551 This is a noop statement so that
552 552 this test is still more bytes than success.
553 553 pad pad pad pad............................................................
554 554 @@ -9,7 +9,7 @@
555 555 pad pad pad pad............................................................
556 556 pad pad pad pad............................................................
557 557 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
558 558 - saved backup bundle to $TESTTMP/foo.hg
559 559 + saved backup bundle to $TESTTMP/foo.hg* (glob)
560 560 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
561 561 saved backup bundle to $TESTTMP/foo.hg* (glob)
562 562 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
563 563 Accept this change? [n] ..
564 564 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
565 565
566 566 $ sed -e 's,(glob)$,&<,g' test-failure.t
567 567 $ echo babar
568 568 babar
569 569 This is a noop statement so that
570 570 this test is still more bytes than success.
571 571 pad pad pad pad............................................................
572 572 pad pad pad pad............................................................
573 573 pad pad pad pad............................................................
574 574 pad pad pad pad............................................................
575 575 pad pad pad pad............................................................
576 576 pad pad pad pad............................................................
577 577 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
578 578 saved backup bundle to $TESTTMP/foo.hg (glob)<
579 579 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
580 580 saved backup bundle to $TESTTMP/foo.hg (glob)<
581 581 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
582 582 saved backup bundle to $TESTTMP/*.hg (glob)<
583 583
584 584 (reinstall)
585 585 $ mv backup test-failure.t
586 586
587 587 No Diff
588 588 ===============
589 589
590 590 $ rt --nodiff
591 591 !.
592 592 Failed test-failure.t: output changed
593 593 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
594 594 python hash seed: * (glob)
595 595 [1]
596 596
597 597 test --tmpdir support
598 598 $ rt --tmpdir=$TESTTMP/keep test-success.t
599 599
600 600 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t (glob)
601 601 Keeping threadtmp dir: $TESTTMP/keep/child1 (glob)
602 602 .
603 603 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
604 604
605 605 timeouts
606 606 ========
607 607 $ cat > test-timeout.t <<EOF
608 608 > $ sleep 2
609 609 > $ echo pass
610 610 > pass
611 611 > EOF
612 612 > echo '#require slow' > test-slow-timeout.t
613 613 > cat test-timeout.t >> test-slow-timeout.t
614 614 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
615 615 st
616 616 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
617 617 Failed test-timeout.t: timed out
618 618 # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
619 619 python hash seed: * (glob)
620 620 [1]
621 621 $ rt --timeout=1 --slowtimeout=3 \
622 622 > test-timeout.t test-slow-timeout.t --allow-slow-tests
623 623 .t
624 624 Failed test-timeout.t: timed out
625 625 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
626 626 python hash seed: * (glob)
627 627 [1]
628 628 $ rm test-timeout.t test-slow-timeout.t
629 629
630 630 test for --time
631 631 ==================
632 632
633 633 $ rt test-success.t --time
634 634 .
635 635 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
636 636 # Producing time report
637 637 start end cuser csys real Test
638 638 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
639 639
640 640 test for --time with --job enabled
641 641 ====================================
642 642
643 643 $ rt test-success.t --time --jobs 2
644 644 .
645 645 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
646 646 # Producing time report
647 647 start end cuser csys real Test
648 648 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
649 649
650 650 Skips
651 651 ================
652 652 $ cat > test-skip.t <<EOF
653 653 > $ echo xyzzy
654 654 > #require false
655 655 > EOF
656 656 $ rt --nodiff
657 657 !.s
658 658 Skipped test-skip.t: missing feature: nail clipper
659 659 Failed test-failure.t: output changed
660 660 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
661 661 python hash seed: * (glob)
662 662 [1]
663 663
664 664 $ rt --keyword xyzzy
665 665 .s
666 666 Skipped test-skip.t: missing feature: nail clipper
667 667 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
668 668
669 669 Skips with xml
670 670 $ rt --keyword xyzzy \
671 671 > --xunit=xunit.xml
672 672 .s
673 673 Skipped test-skip.t: missing feature: nail clipper
674 674 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
675 675 $ cat xunit.xml
676 676 <?xml version="1.0" encoding="utf-8"?>
677 677 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
678 678 <testcase name="test-success.t" time="*"/> (glob)
679 <testcase name="test-skip.t">
680 <skipped>
681 <![CDATA[missing feature: nail clipper]]> </skipped>
682 </testcase>
679 683 </testsuite>
680 684
681 685 Missing skips or blacklisted skips don't count as executed:
682 686 $ echo test-failure.t > blacklist
683 687 $ rt --blacklist=blacklist --json\
684 688 > test-failure.t test-bogus.t
685 689 ss
686 690 Skipped test-bogus.t: Doesn't exist
687 691 Skipped test-failure.t: blacklisted
688 692 # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
689 693 $ cat report.json
690 694 testreport ={
691 695 "test-bogus.t": {
692 696 "result": "skip"
693 697 },
694 698 "test-failure.t": {
695 699 "result": "skip"
696 700 }
697 701 } (no-eol)
698 702
699 703 Whitelist trumps blacklist
700 704 $ echo test-failure.t > whitelist
701 705 $ rt --blacklist=blacklist --whitelist=whitelist --json\
702 706 > test-failure.t test-bogus.t
703 707 s
704 708 --- $TESTTMP/test-failure.t
705 709 +++ $TESTTMP/test-failure.t.err
706 710 @@ -1,5 +1,5 @@
707 711 $ echo babar
708 712 - rataxes
709 713 + babar
710 714 This is a noop statement so that
711 715 this test is still more bytes than success.
712 716 pad pad pad pad............................................................
713 717
714 718 ERROR: test-failure.t output changed
715 719 !
716 720 Skipped test-bogus.t: Doesn't exist
717 721 Failed test-failure.t: output changed
718 722 # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
719 723 python hash seed: * (glob)
720 724 [1]
721 725
722 726 test for --json
723 727 ==================
724 728
725 729 $ rt --json
726 730
727 731 --- $TESTTMP/test-failure.t
728 732 +++ $TESTTMP/test-failure.t.err
729 733 @@ -1,5 +1,5 @@
730 734 $ echo babar
731 735 - rataxes
732 736 + babar
733 737 This is a noop statement so that
734 738 this test is still more bytes than success.
735 739 pad pad pad pad............................................................
736 740
737 741 ERROR: test-failure.t output changed
738 742 !.s
739 743 Skipped test-skip.t: missing feature: nail clipper
740 744 Failed test-failure.t: output changed
741 745 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
742 746 python hash seed: * (glob)
743 747 [1]
744 748
745 749 $ cat report.json
746 750 testreport ={
747 751 "test-failure.t": [\{] (re)
748 752 "csys": "\s*[\d\.]{4,5}", ? (re)
749 753 "cuser": "\s*[\d\.]{4,5}", ? (re)
750 754 "diff": "---.+\+\+\+.+", ? (re)
751 755 "end": "\s*[\d\.]{4,5}", ? (re)
752 756 "result": "failure", ? (re)
753 757 "start": "\s*[\d\.]{4,5}", ? (re)
754 758 "time": "\s*[\d\.]{4,5}" (re)
755 759 }, ? (re)
756 760 "test-skip.t": {
757 761 "csys": "\s*[\d\.]{4,5}", ? (re)
758 762 "cuser": "\s*[\d\.]{4,5}", ? (re)
759 763 "diff": "", ? (re)
760 764 "end": "\s*[\d\.]{4,5}", ? (re)
761 765 "result": "skip", ? (re)
762 766 "start": "\s*[\d\.]{4,5}", ? (re)
763 767 "time": "\s*[\d\.]{4,5}" (re)
764 768 }, ? (re)
765 769 "test-success.t": [\{] (re)
766 770 "csys": "\s*[\d\.]{4,5}", ? (re)
767 771 "cuser": "\s*[\d\.]{4,5}", ? (re)
768 772 "diff": "", ? (re)
769 773 "end": "\s*[\d\.]{4,5}", ? (re)
770 774 "result": "success", ? (re)
771 775 "start": "\s*[\d\.]{4,5}", ? (re)
772 776 "time": "\s*[\d\.]{4,5}" (re)
773 777 }
774 778 } (no-eol)
775 779
776 780 Test that failed test accepted through interactive are properly reported:
777 781
778 782 $ cp test-failure.t backup
779 783 $ echo y | rt --json -i
780 784
781 785 --- $TESTTMP/test-failure.t
782 786 +++ $TESTTMP/test-failure.t.err
783 787 @@ -1,5 +1,5 @@
784 788 $ echo babar
785 789 - rataxes
786 790 + babar
787 791 This is a noop statement so that
788 792 this test is still more bytes than success.
789 793 pad pad pad pad............................................................
790 794 Accept this change? [n] ..s
791 795 Skipped test-skip.t: missing feature: nail clipper
792 796 # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
793 797
794 798 $ cat report.json
795 799 testreport ={
796 800 "test-failure.t": [\{] (re)
797 801 "csys": "\s*[\d\.]{4,5}", ? (re)
798 802 "cuser": "\s*[\d\.]{4,5}", ? (re)
799 803 "diff": "", ? (re)
800 804 "end": "\s*[\d\.]{4,5}", ? (re)
801 805 "result": "success", ? (re)
802 806 "start": "\s*[\d\.]{4,5}", ? (re)
803 807 "time": "\s*[\d\.]{4,5}" (re)
804 808 }, ? (re)
805 809 "test-skip.t": {
806 810 "csys": "\s*[\d\.]{4,5}", ? (re)
807 811 "cuser": "\s*[\d\.]{4,5}", ? (re)
808 812 "diff": "", ? (re)
809 813 "end": "\s*[\d\.]{4,5}", ? (re)
810 814 "result": "skip", ? (re)
811 815 "start": "\s*[\d\.]{4,5}", ? (re)
812 816 "time": "\s*[\d\.]{4,5}" (re)
813 817 }, ? (re)
814 818 "test-success.t": [\{] (re)
815 819 "csys": "\s*[\d\.]{4,5}", ? (re)
816 820 "cuser": "\s*[\d\.]{4,5}", ? (re)
817 821 "diff": "", ? (re)
818 822 "end": "\s*[\d\.]{4,5}", ? (re)
819 823 "result": "success", ? (re)
820 824 "start": "\s*[\d\.]{4,5}", ? (re)
821 825 "time": "\s*[\d\.]{4,5}" (re)
822 826 }
823 827 } (no-eol)
824 828 $ mv backup test-failure.t
825 829
826 830 backslash on end of line with glob matching is handled properly
827 831
828 832 $ cat > test-glob-backslash.t << EOF
829 833 > $ echo 'foo bar \\'
830 834 > foo * \ (glob)
831 835 > EOF
832 836
833 837 $ rt test-glob-backslash.t
834 838 .
835 839 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
836 840
837 841 $ rm -f test-glob-backslash.t
838 842
839 843 Test globbing of local IP addresses
840 844 $ echo 172.16.18.1
841 845 $LOCALIP (glob)
842 846 $ echo dead:beef::1
843 847 $LOCALIP (glob)
844 848
845 849 Test reusability for third party tools
846 850 ======================================
847 851
848 852 $ mkdir "$TESTTMP"/anothertests
849 853 $ cd "$TESTTMP"/anothertests
850 854
851 855 test that `run-tests.py` can execute hghave, even if it runs not in
852 856 Mercurial source tree.
853 857
854 858 $ cat > test-hghave.t <<EOF
855 859 > #require true
856 860 > $ echo foo
857 861 > foo
858 862 > EOF
859 863 $ rt test-hghave.t
860 864 .
861 865 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
862 866
863 867 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
864 868 running is placed.
865 869
866 870 $ cat > test-runtestdir.t <<EOF
867 871 > - $TESTDIR, in which test-run-tests.t is placed
868 872 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
869 873 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
870 874 >
871 875 > #if windows
872 876 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
873 877 > #else
874 878 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
875 879 > #endif
876 880 > $ test "\$RUNTESTDIR" = "$TESTDIR"
877 881 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py
878 882 > #!/usr/bin/env python
879 883 > #
880 884 > # check-code - a style and portability checker for Mercurial
881 885 > EOF
882 886 $ rt test-runtestdir.t
883 887 .
884 888 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
885 889
886 890 #if execbit
887 891
888 892 test that TESTDIR is referred in PATH
889 893
890 894 $ cat > custom-command.sh <<EOF
891 895 > #!/bin/sh
892 896 > echo "hello world"
893 897 > EOF
894 898 $ chmod +x custom-command.sh
895 899 $ cat > test-testdir-path.t <<EOF
896 900 > $ custom-command.sh
897 901 > hello world
898 902 > EOF
899 903 $ rt test-testdir-path.t
900 904 .
901 905 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
902 906
903 907 #endif
904 908
905 909 test support for --allow-slow-tests
906 910 $ cat > test-very-slow-test.t <<EOF
907 911 > #require slow
908 912 > $ echo pass
909 913 > pass
910 914 > EOF
911 915 $ rt test-very-slow-test.t
912 916 s
913 917 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
914 918 # Ran 0 tests, 1 skipped, 0 warned, 0 failed.
915 919 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
916 920 .
917 921 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
918 922
919 923 support for running a test outside the current directory
920 924 $ mkdir nonlocal
921 925 $ cat > nonlocal/test-is-not-here.t << EOF
922 926 > $ echo pass
923 927 > pass
924 928 > EOF
925 929 $ rt nonlocal/test-is-not-here.t
926 930 .
927 931 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
928 932
929 933 support for bisecting failed tests automatically
930 934 $ hg init bisect
931 935 $ cd bisect
932 936 $ cat >> test-bisect.t <<EOF
933 937 > $ echo pass
934 938 > pass
935 939 > EOF
936 940 $ hg add test-bisect.t
937 941 $ hg ci -m 'good'
938 942 $ cat >> test-bisect.t <<EOF
939 943 > $ echo pass
940 944 > fail
941 945 > EOF
942 946 $ hg ci -m 'bad'
943 947 $ rt --known-good-rev=0 test-bisect.t
944 948
945 949 --- $TESTTMP/anothertests/bisect/test-bisect.t
946 950 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
947 951 @@ -1,4 +1,4 @@
948 952 $ echo pass
949 953 pass
950 954 $ echo pass
951 955 - fail
952 956 + pass
953 957
954 958 ERROR: test-bisect.t output changed
955 959 !
956 960 Failed test-bisect.t: output changed
957 961 test-bisect.t broken by 72cbf122d116 (bad)
958 962 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
959 963 python hash seed: * (glob)
960 964 [1]
961 965
962 966 $ cd ..
963 967
964 968 Test a broken #if statement doesn't break run-tests threading.
965 969 ==============================================================
966 970 $ mkdir broken
967 971 $ cd broken
968 972 $ cat > test-broken.t <<EOF
969 973 > true
970 974 > #if notarealhghavefeature
971 975 > $ false
972 976 > #endif
973 977 > EOF
974 978 $ for f in 1 2 3 4 ; do
975 979 > cat > test-works-$f.t <<EOF
976 980 > This is test case $f
977 981 > $ sleep 1
978 982 > EOF
979 983 > done
980 984 $ rt -j 2
981 985 ....
982 986 # Ran 5 tests, 0 skipped, 0 warned, 0 failed.
983 987 skipped: unknown feature: notarealhghavefeature
984 988
985 989 $ cd ..
986 990 $ rm -rf broken
987 991
988 992 Test cases in .t files
989 993 ======================
990 994 $ mkdir cases
991 995 $ cd cases
992 996 $ cat > test-cases-abc.t <<'EOF'
993 997 > #testcases A B C
994 998 > $ V=B
995 999 > #if A
996 1000 > $ V=A
997 1001 > #endif
998 1002 > #if C
999 1003 > $ V=C
1000 1004 > #endif
1001 1005 > $ echo $V | sed 's/A/C/'
1002 1006 > C
1003 1007 > #if C
1004 1008 > $ [ $V = C ]
1005 1009 > #endif
1006 1010 > #if A
1007 1011 > $ [ $V = C ]
1008 1012 > [1]
1009 1013 > #endif
1010 1014 > #if no-C
1011 1015 > $ [ $V = C ]
1012 1016 > [1]
1013 1017 > #endif
1014 1018 > $ [ $V = D ]
1015 1019 > [1]
1016 1020 > EOF
1017 1021 $ rt
1018 1022 .
1019 1023 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1020 1024 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1021 1025 @@ -7,7 +7,7 @@
1022 1026 $ V=C
1023 1027 #endif
1024 1028 $ echo $V | sed 's/A/C/'
1025 1029 - C
1026 1030 + B
1027 1031 #if C
1028 1032 $ [ $V = C ]
1029 1033 #endif
1030 1034
1031 1035 ERROR: test-cases-abc.t (case B) output changed
1032 1036 !.
1033 1037 Failed test-cases-abc.t (case B): output changed
1034 1038 # Ran 3 tests, 0 skipped, 0 warned, 1 failed.
1035 1039 python hash seed: * (glob)
1036 1040 [1]
1037 1041
1038 1042 --restart works
1039 1043
1040 1044 $ rt --restart
1041 1045
1042 1046 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1043 1047 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1044 1048 @@ -7,7 +7,7 @@
1045 1049 $ V=C
1046 1050 #endif
1047 1051 $ echo $V | sed 's/A/C/'
1048 1052 - C
1049 1053 + B
1050 1054 #if C
1051 1055 $ [ $V = C ]
1052 1056 #endif
1053 1057
1054 1058 ERROR: test-cases-abc.t (case B) output changed
1055 1059 !.
1056 1060 Failed test-cases-abc.t (case B): output changed
1057 1061 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
1058 1062 python hash seed: * (glob)
1059 1063 [1]
General Comments 0
You need to be logged in to leave comments. Login now