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