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