##// END OF EJS Templates
run-tests: add color to the progress output...
marmoute -
r52730:7933bcb0 default
parent child Browse files
Show More
@@ -1,4082 +1,4146
1 1 #!/usr/bin/env python3
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Olivia Mackall <olivia@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
47 47 import argparse
48 48 import collections
49 49 import contextlib
50 50 import difflib
51 51
52 52 import errno
53 53 import functools
54 54 import json
55 55 import multiprocessing
56 56 import os
57 57 import platform
58 58 import queue
59 59 import random
60 60 import re
61 61 import shlex
62 62 import shutil
63 63 import signal
64 64 import socket
65 65 import subprocess
66 66 import sys
67 67 import sysconfig
68 68 import tempfile
69 69 import threading
70 70 import time
71 71 import unittest
72 72 import uuid
73 73 import xml.dom.minidom as minidom
74 74
75 75
76 76 if sys.version_info < (3, 5, 0):
77 77 print(
78 78 '%s is only supported on Python 3.5+, not %s'
79 79 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
80 80 )
81 81 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
82 82
83 83 MACOS = sys.platform == 'darwin'
84 84 WINDOWS = os.name == r'nt'
85 85 shellquote = shlex.quote
86 86
87 87
88 88 processlock = threading.Lock()
89 89
90 90 pygmentspresent = False
91 91 try: # is pygments installed
92 92 import pygments
93 93 import pygments.lexers as lexers
94 94 import pygments.lexer as lexer
95 95 import pygments.formatters as formatters
96 96 import pygments.token as token
97 97 import pygments.style as style
98 98
99 99 if WINDOWS:
100 100 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
101 101 sys.path.append(hgpath)
102 102 try:
103 103 from mercurial import win32 # pytype: disable=import-error
104 104
105 105 # Don't check the result code because it fails on heptapod, but
106 106 # something is able to convert to color anyway.
107 107 win32.enablevtmode()
108 108 finally:
109 109 sys.path = sys.path[:-1]
110 110
111 111 pygmentspresent = True
112 112 difflexer = lexers.DiffLexer()
113 113 terminal256formatter = formatters.Terminal256Formatter()
114 114 except ImportError:
115 115 pass
116 116
117 progress_type = {}
118
117 119 if pygmentspresent:
120 _T_ERROR = token.string_to_tokentype("Token.Generic.Error")
121 _T_FAILED = token.string_to_tokentype("Token.Generic.Failed")
122 _T_FNAME = token.string_to_tokentype("Token.Generic.FName")
123 _T_IGNORED = token.string_to_tokentype("Token.Generic.Ignored")
124 _T_SKIPPED = token.string_to_tokentype("Token.Generic.Skipped")
125 _T_SNAME = token.string_to_tokentype("Token.Generic.SName")
126 _T_SKIPPED_DOT = token.string_to_tokentype("Token.Generic.SkippedDot")
127 _T_SUCCESS = token.string_to_tokentype("Token.Generic.Success")
128 _T_TIMEDOUT = token.string_to_tokentype("Token.Generic.TimedOut")
118 129
119 130 class TestRunnerStyle(style.Style):
120 131 default_style = ""
121 skipped = token.string_to_tokentype("Token.Generic.Skipped")
122 failed = token.string_to_tokentype("Token.Generic.Failed")
123 skippedname = token.string_to_tokentype("Token.Generic.SName")
124 failedname = token.string_to_tokentype("Token.Generic.FName")
125 132 styles = {
126 skipped: '#e5e5e5',
127 skippedname: '#00ffff',
128 failed: '#7f0000',
129 failedname: '#ff0000',
133 _T_ERROR: '#cd00cd',
134 _T_FAILED: '#7f0000',
135 _T_FNAME: '#ff0000',
136 _T_IGNORED: '#cdcd00',
137 _T_SKIPPED: '#e5e5e5',
138 _T_SNAME: '#00ffff',
139 _T_SKIPPED_DOT: '#00ffff',
140 _T_SUCCESS: '#00cd00',
141 _T_TIMEDOUT: '#ff00ff',
130 142 }
131 143
132 144 class TestRunnerLexer(lexer.RegexLexer):
133 145 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
134 146 tokens = {
135 147 'root': [
136 (r'^Skipped', token.Generic.Skipped, 'skipped'),
137 (r'^Failed ', token.Generic.Failed, 'failed'),
138 (r'^ERROR: ', token.Generic.Failed, 'failed'),
148 (r'^Skipped', _T_SKIPPED, 'skipped'),
149 (r'^Failed ', _T_FAILED, 'failed'),
150 (r'^ERROR: ', _T_FAILED, 'failed'),
139 151 ],
140 152 'skipped': [
141 (testpattern, token.Generic.SName),
142 (r':.*', token.Generic.Skipped),
153 (testpattern, _T_SNAME),
154 (r':.*', _T_SKIPPED),
143 155 ],
144 156 'failed': [
145 (testpattern, token.Generic.FName),
146 (r'(:| ).*', token.Generic.Failed),
157 (testpattern, _T_FNAME),
158 (r'(:| ).*', _T_FAILED),
159 ],
160 }
161
162 progress_type['.'] = _T_SUCCESS
163 progress_type['s'] = _T_SKIPPED_DOT
164 progress_type['i'] = _T_IGNORED
165 progress_type['!'] = _T_FAILED
166 progress_type['E'] = _T_ERROR
167 progress_type['t'] = _T_TIMEDOUT
168
169 class progressLexer(lexer.RegexLexer):
170 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
171 tokens = {
172 'root': [
173 (r'^Skipped', _T_SKIPPED, 'skipped'),
174 (r'^Failed ', _T_FAILED, 'failed'),
175 (r'^ERROR: ', _T_FAILED, 'failed'),
176 ],
177 'skipped': [
178 (testpattern, _T_SNAME),
179 (r':.*', _T_SKIPPED),
180 ],
181 'failed': [
182 (testpattern, _T_FNAME),
183 (r'(:| ).*', _T_FAILED),
147 184 ],
148 185 }
149 186
150 187 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
151 188 runnerlexer = TestRunnerLexer()
152 189
153 190 origenviron = os.environ.copy()
154 191
155 192
156 193 def _sys2bytes(p):
157 194 if p is None:
158 195 return p
159 196 return p.encode('utf-8')
160 197
161 198
162 199 def _bytes2sys(p):
163 200 if p is None:
164 201 return p
165 202 return p.decode('utf-8')
166 203
167 204
168 205 original_env = os.environ.copy()
169 206 osenvironb = getattr(os, 'environb', None)
170 207 if osenvironb is None:
171 208 # Windows lacks os.environb, for instance. A proxy over the real thing
172 209 # instead of a copy allows the environment to be updated via bytes on
173 210 # all platforms.
174 211 class environbytes:
175 212 def __init__(self, strenv):
176 213 self.__len__ = strenv.__len__
177 214 self.clear = strenv.clear
178 215 self._strenv = strenv
179 216
180 217 def __getitem__(self, k):
181 218 v = self._strenv.__getitem__(_bytes2sys(k))
182 219 return _sys2bytes(v)
183 220
184 221 def __setitem__(self, k, v):
185 222 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
186 223
187 224 def __delitem__(self, k):
188 225 self._strenv.__delitem__(_bytes2sys(k))
189 226
190 227 def __contains__(self, k):
191 228 return self._strenv.__contains__(_bytes2sys(k))
192 229
193 230 def __iter__(self):
194 231 return iter([_sys2bytes(k) for k in iter(self._strenv)])
195 232
196 233 def get(self, k, default=None):
197 234 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
198 235 return _sys2bytes(v)
199 236
200 237 def pop(self, k, default=None):
201 238 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
202 239 return _sys2bytes(v)
203 240
204 241 osenvironb = environbytes(os.environ)
205 242
206 243 getcwdb = getattr(os, 'getcwdb')
207 244 if not getcwdb or WINDOWS:
208 245 getcwdb = lambda: _sys2bytes(os.getcwd())
209 246
210 247
211 248 if WINDOWS:
212 249 _getcwdb = getcwdb
213 250
214 251 def getcwdb():
215 252 cwd = _getcwdb()
216 253 if re.match(b'^[a-z]:', cwd):
217 254 # os.getcwd() is inconsistent on the capitalization of the drive
218 255 # letter, so adjust it. see https://bugs.python.org/issue40368
219 256 cwd = cwd[0:1].upper() + cwd[1:]
220 257 return cwd
221 258
222 259
223 260 # For Windows support
224 261 wifexited = getattr(os, "WIFEXITED", lambda x: False)
225 262
226 263
227 264 # Whether to use IPv6
228 265 def checksocketfamily(name, port=20058):
229 266 """return true if we can listen on localhost using family=name
230 267
231 268 name should be either 'AF_INET', or 'AF_INET6'.
232 269 port being used is okay - EADDRINUSE is considered as successful.
233 270 """
234 271 family = getattr(socket, name, None)
235 272 if family is None:
236 273 return False
237 274 try:
238 275 s = socket.socket(family, socket.SOCK_STREAM)
239 276 s.bind(('localhost', port))
240 277 s.close()
241 278 return True
242 279 except (socket.error, OSError) as exc:
243 280 if exc.errno == errno.EADDRINUSE:
244 281 return True
245 282 elif exc.errno in (
246 283 errno.EADDRNOTAVAIL,
247 284 errno.EPROTONOSUPPORT,
248 285 errno.EAFNOSUPPORT,
249 286 ):
250 287 return False
251 288 else:
252 289 raise
253 290 else:
254 291 return False
255 292
256 293
257 294 # useipv6 will be set by parseargs
258 295 useipv6 = None
259 296
260 297
261 298 def checkportisavailable(port):
262 299 """return true if a port seems free to bind on localhost"""
263 300 if useipv6:
264 301 family = socket.AF_INET6
265 302 else:
266 303 family = socket.AF_INET
267 304 try:
268 305 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
269 306 s.bind(('localhost', port))
270 307 return True
271 308 except PermissionError:
272 309 return False
273 310 except socket.error as exc:
274 311 if WINDOWS and exc.errno == errno.WSAEACCES:
275 312 return False
276 313 if exc.errno not in (
277 314 errno.EADDRINUSE,
278 315 errno.EADDRNOTAVAIL,
279 316 errno.EPROTONOSUPPORT,
280 317 ):
281 318 raise
282 319 return False
283 320
284 321
285 322 closefds = os.name == 'posix'
286 323
287 324
288 325 def Popen4(cmd, wd, timeout, env=None):
289 326 processlock.acquire()
290 327 p = subprocess.Popen(
291 328 _bytes2sys(cmd),
292 329 shell=True,
293 330 bufsize=-1,
294 331 cwd=_bytes2sys(wd),
295 332 env=env,
296 333 close_fds=closefds,
297 334 stdin=subprocess.PIPE,
298 335 stdout=subprocess.PIPE,
299 336 stderr=subprocess.STDOUT,
300 337 )
301 338 processlock.release()
302 339
303 340 p.fromchild = p.stdout
304 341 p.tochild = p.stdin
305 342 p.childerr = p.stderr
306 343
307 344 p.timeout = False
308 345 if timeout:
309 346
310 347 def t():
311 348 start = time.time()
312 349 while time.time() - start < timeout and p.returncode is None:
313 350 time.sleep(0.1)
314 351 p.timeout = True
315 352 vlog('# Timout reached for process %d' % p.pid)
316 353 if p.returncode is None:
317 354 terminate(p)
318 355
319 356 threading.Thread(target=t).start()
320 357
321 358 return p
322 359
323 360
324 361 if sys.executable:
325 362 sysexecutable = sys.executable
326 363 elif os.environ.get('PYTHONEXECUTABLE'):
327 364 sysexecutable = os.environ['PYTHONEXECUTABLE']
328 365 elif os.environ.get('PYTHON'):
329 366 sysexecutable = os.environ['PYTHON']
330 367 else:
331 368 raise AssertionError('Could not find Python interpreter')
332 369
333 370 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
334 371 IMPL_PATH = b'PYTHONPATH'
335 372 if 'java' in sys.platform:
336 373 IMPL_PATH = b'JYTHONPATH'
337 374
338 375 default_defaults = {
339 376 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
340 377 'timeout': ('HGTEST_TIMEOUT', 360),
341 378 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
342 379 'port': ('HGTEST_PORT', 20059),
343 380 'shell': ('HGTEST_SHELL', 'sh'),
344 381 }
345 382
346 383 defaults = default_defaults.copy()
347 384
348 385
349 386 def canonpath(path):
350 387 return os.path.realpath(os.path.expanduser(path))
351 388
352 389
353 390 def which(exe):
354 391 # shutil.which only accept bytes from 3.8
355 392 cmd = _bytes2sys(exe)
356 393 real_exec = shutil.which(cmd)
357 394 return _sys2bytes(real_exec)
358 395
359 396
360 397 def parselistfiles(files, listtype, warn=True):
361 398 entries = dict()
362 399 for filename in files:
363 400 try:
364 401 path = os.path.expanduser(os.path.expandvars(filename))
365 402 f = open(path, "rb")
366 403 except FileNotFoundError:
367 404 if warn:
368 405 print("warning: no such %s file: %s" % (listtype, filename))
369 406 continue
370 407
371 408 for line in f.readlines():
372 409 line = line.split(b'#', 1)[0].strip()
373 410 if line:
374 411 # Ensure path entries are compatible with os.path.relpath()
375 412 entries[os.path.normpath(line)] = filename
376 413
377 414 f.close()
378 415 return entries
379 416
380 417
381 418 def parsettestcases(path):
382 419 """read a .t test file, return a set of test case names
383 420
384 421 If path does not exist, return an empty set.
385 422 """
386 423 cases = []
387 424 try:
388 425 with open(path, 'rb') as f:
389 426 for l in f:
390 427 if l.startswith(b'#testcases '):
391 428 cases.append(sorted(l[11:].split()))
392 429 except FileNotFoundError:
393 430 pass
394 431 return cases
395 432
396 433
397 434 def getparser():
398 435 """Obtain the OptionParser used by the CLI."""
399 436 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
400 437
401 438 selection = parser.add_argument_group('Test Selection')
402 439 selection.add_argument(
403 440 '--allow-slow-tests',
404 441 action='store_true',
405 442 help='allow extremely slow tests',
406 443 )
407 444 selection.add_argument(
408 445 "--blacklist",
409 446 action="append",
410 447 help="skip tests listed in the specified blacklist file",
411 448 )
412 449 selection.add_argument(
413 450 "--changed",
414 451 help="run tests that are changed in parent rev or working directory",
415 452 )
416 453 selection.add_argument(
417 454 "-k", "--keywords", help="run tests matching keywords"
418 455 )
419 456 selection.add_argument(
420 457 "-r", "--retest", action="store_true", help="retest failed tests"
421 458 )
422 459 selection.add_argument(
423 460 "--test-list",
424 461 action="append",
425 462 help="read tests to run from the specified file",
426 463 )
427 464 selection.add_argument(
428 465 "--whitelist",
429 466 action="append",
430 467 help="always run tests listed in the specified whitelist file",
431 468 )
432 469 selection.add_argument(
433 470 'tests', metavar='TESTS', nargs='*', help='Tests to run'
434 471 )
435 472
436 473 harness = parser.add_argument_group('Test Harness Behavior')
437 474 harness.add_argument(
438 475 '--bisect-repo',
439 476 metavar='bisect_repo',
440 477 help=(
441 478 "Path of a repo to bisect. Use together with " "--known-good-rev"
442 479 ),
443 480 )
444 481 harness.add_argument(
445 482 "-d",
446 483 "--debug",
447 484 action="store_true",
448 485 help="debug mode: write output of test scripts to console"
449 486 " rather than capturing and diffing it (disables timeout)",
450 487 )
451 488 harness.add_argument(
452 489 "-f",
453 490 "--first",
454 491 action="store_true",
455 492 help="exit on the first test failure",
456 493 )
457 494 harness.add_argument(
458 495 "-i",
459 496 "--interactive",
460 497 action="store_true",
461 498 help="prompt to accept changed output",
462 499 )
463 500 harness.add_argument(
464 501 "-j",
465 502 "--jobs",
466 503 type=int,
467 504 help="number of jobs to run in parallel"
468 505 " (default: $%s or %d)" % defaults['jobs'],
469 506 )
470 507 harness.add_argument(
471 508 "--keep-tmpdir",
472 509 action="store_true",
473 510 help="keep temporary directory after running tests",
474 511 )
475 512 harness.add_argument(
476 513 '--known-good-rev',
477 514 metavar="known_good_rev",
478 515 help=(
479 516 "Automatically bisect any failures using this "
480 517 "revision as a known-good revision."
481 518 ),
482 519 )
483 520 harness.add_argument(
484 521 "--list-tests",
485 522 action="store_true",
486 523 help="list tests instead of running them",
487 524 )
488 525 harness.add_argument(
489 526 "--loop", action="store_true", help="loop tests repeatedly"
490 527 )
491 528 harness.add_argument(
492 529 '--random', action="store_true", help='run tests in random order'
493 530 )
494 531 harness.add_argument(
495 532 '--order-by-runtime',
496 533 action="store_true",
497 534 help='run slowest tests first, according to .testtimes',
498 535 )
499 536 harness.add_argument(
500 537 "-p",
501 538 "--port",
502 539 type=int,
503 540 help="port on which servers should listen"
504 541 " (default: $%s or %d)" % defaults['port'],
505 542 )
506 543 harness.add_argument(
507 544 '--profile-runner',
508 545 action='store_true',
509 546 help='run statprof on run-tests',
510 547 )
511 548 harness.add_argument(
512 549 "-R", "--restart", action="store_true", help="restart at last error"
513 550 )
514 551 harness.add_argument(
515 552 "--runs-per-test",
516 553 type=int,
517 554 dest="runs_per_test",
518 555 help="run each test N times (default=1)",
519 556 default=1,
520 557 )
521 558 harness.add_argument(
522 559 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
523 560 )
524 561 harness.add_argument(
525 562 '--showchannels', action='store_true', help='show scheduling channels'
526 563 )
527 564 harness.add_argument(
528 565 "--slowtimeout",
529 566 type=int,
530 567 help="kill errant slow tests after SLOWTIMEOUT seconds"
531 568 " (default: $%s or %d)" % defaults['slowtimeout'],
532 569 )
533 570 harness.add_argument(
534 571 "-t",
535 572 "--timeout",
536 573 type=int,
537 574 help="kill errant tests after TIMEOUT seconds"
538 575 " (default: $%s or %d)" % defaults['timeout'],
539 576 )
540 577 harness.add_argument(
541 578 "--tmpdir",
542 579 help="run tests in the given temporary directory"
543 580 " (implies --keep-tmpdir)",
544 581 )
545 582 harness.add_argument(
546 583 "-v", "--verbose", action="store_true", help="output verbose messages"
547 584 )
548 585
549 586 hgconf = parser.add_argument_group('Mercurial Configuration')
550 587 hgconf.add_argument(
551 588 "--chg",
552 589 action="store_true",
553 590 help="install and use chg wrapper in place of hg",
554 591 )
555 592 hgconf.add_argument(
556 593 "--chg-debug",
557 594 action="store_true",
558 595 help="show chg debug logs",
559 596 )
560 597 hgconf.add_argument(
561 598 "--rhg",
562 599 action="store_true",
563 600 help="install and use rhg Rust implementation in place of hg",
564 601 )
565 602 hgconf.add_argument(
566 603 "--pyoxidized",
567 604 action="store_true",
568 605 help="build the hg binary using pyoxidizer",
569 606 )
570 607 hgconf.add_argument("--compiler", help="compiler to build with")
571 608 hgconf.add_argument(
572 609 '--extra-config-opt',
573 610 action="append",
574 611 default=[],
575 612 help='set the given config opt in the test hgrc',
576 613 )
577 614 hgconf.add_argument(
578 615 "-l",
579 616 "--local",
580 617 action="store_true",
581 618 help="shortcut for --with-hg=<testdir>/../hg, "
582 619 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
583 620 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
584 621 )
585 622 hgconf.add_argument(
586 623 "--ipv6",
587 624 action="store_true",
588 625 help="prefer IPv6 to IPv4 for network related tests",
589 626 )
590 627 hgconf.add_argument(
591 628 "--pure",
592 629 action="store_true",
593 630 help="use pure Python code instead of C extensions",
594 631 )
595 632 hgconf.add_argument(
596 633 "--rust",
597 634 action="store_true",
598 635 help="use Rust code alongside C extensions",
599 636 )
600 637 hgconf.add_argument(
601 638 "--no-rust",
602 639 action="store_true",
603 640 help="do not use Rust code even if compiled",
604 641 )
605 642 hgconf.add_argument(
606 643 "--with-chg",
607 644 metavar="CHG",
608 645 help="use specified chg wrapper in place of hg",
609 646 )
610 647 hgconf.add_argument(
611 648 "--with-rhg",
612 649 metavar="RHG",
613 650 help="use specified rhg Rust implementation in place of hg",
614 651 )
615 652 hgconf.add_argument(
616 653 "--with-hg",
617 654 metavar="HG",
618 655 help="test using specified hg script rather than a "
619 656 "temporary installation",
620 657 )
621 658
622 659 reporting = parser.add_argument_group('Results Reporting')
623 660 reporting.add_argument(
624 661 "-C",
625 662 "--annotate",
626 663 action="store_true",
627 664 help="output files annotated with coverage",
628 665 )
629 666 reporting.add_argument(
630 667 "--color",
631 668 choices=["always", "auto", "never"],
632 669 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
633 670 help="colorisation: always|auto|never (default: auto)",
634 671 )
635 672 reporting.add_argument(
636 673 "-c",
637 674 "--cover",
638 675 action="store_true",
639 676 help="print a test coverage report",
640 677 )
641 678 reporting.add_argument(
642 679 '--exceptions',
643 680 action='store_true',
644 681 help='log all exceptions and generate an exception report',
645 682 )
646 683 reporting.add_argument(
647 684 "-H",
648 685 "--htmlcov",
649 686 action="store_true",
650 687 help="create an HTML report of the coverage of the files",
651 688 )
652 689 reporting.add_argument(
653 690 "--json",
654 691 action="store_true",
655 692 help="store test result data in 'report.json' file",
656 693 )
657 694 reporting.add_argument(
658 695 "--outputdir",
659 696 help="directory to write error logs to (default=test directory)",
660 697 )
661 698 reporting.add_argument(
662 699 "-n", "--nodiff", action="store_true", help="skip showing test changes"
663 700 )
664 701 reporting.add_argument(
665 702 "-S",
666 703 "--noskips",
667 704 action="store_true",
668 705 help="don't report skip tests verbosely",
669 706 )
670 707 reporting.add_argument(
671 708 "--time", action="store_true", help="time how long each test takes"
672 709 )
673 710 reporting.add_argument("--view", help="external diff viewer")
674 711 reporting.add_argument(
675 712 "--xunit", help="record xunit results at specified path"
676 713 )
677 714
678 715 for option, (envvar, default) in defaults.items():
679 716 defaults[option] = type(default)(os.environ.get(envvar, default))
680 717 parser.set_defaults(**defaults)
681 718
682 719 return parser
683 720
684 721
685 722 def parseargs(args, parser):
686 723 """Parse arguments with our OptionParser and validate results."""
687 724 options = parser.parse_args(args)
688 725
689 726 # jython is always pure
690 727 if 'java' in sys.platform or '__pypy__' in sys.modules:
691 728 options.pure = True
692 729
693 730 if platform.python_implementation() != 'CPython' and options.rust:
694 731 parser.error('Rust extensions are only available with CPython')
695 732
696 733 if options.pure and options.rust:
697 734 parser.error('--rust cannot be used with --pure')
698 735
699 736 if options.rust and options.no_rust:
700 737 parser.error('--rust cannot be used with --no-rust')
701 738
702 739 if options.local:
703 740 if options.with_hg or options.with_rhg or options.with_chg:
704 741 parser.error(
705 742 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
706 743 )
707 744 if options.pyoxidized:
708 745 parser.error('--pyoxidized does not work with --local (yet)')
709 746 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
710 747 reporootdir = os.path.dirname(testdir)
711 748 pathandattrs = [(b'hg', 'with_hg')]
712 749 if options.chg:
713 750 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
714 751 if options.rhg:
715 752 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
716 753 for relpath, attr in pathandattrs:
717 754 binpath = os.path.join(reporootdir, relpath)
718 755 if not (WINDOWS or os.access(binpath, os.X_OK)):
719 756 parser.error(
720 757 '--local specified, but %r not found or '
721 758 'not executable' % binpath
722 759 )
723 760 setattr(options, attr, _bytes2sys(binpath))
724 761
725 762 if options.with_hg:
726 763 options.with_hg = canonpath(_sys2bytes(options.with_hg))
727 764 if not (
728 765 os.path.isfile(options.with_hg)
729 766 and os.access(options.with_hg, os.X_OK)
730 767 ):
731 768 parser.error('--with-hg must specify an executable hg script')
732 769 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
733 770 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
734 771 msg %= _bytes2sys(os.path.basename(options.with_hg))
735 772 sys.stderr.write(msg)
736 773 sys.stderr.flush()
737 774
738 775 if (options.chg or options.with_chg) and WINDOWS:
739 776 parser.error('chg does not work on %s' % os.name)
740 777 if (options.rhg or options.with_rhg) and WINDOWS:
741 778 parser.error('rhg does not work on %s' % os.name)
742 779 if options.pyoxidized and not (MACOS or WINDOWS):
743 780 parser.error('--pyoxidized is currently macOS and Windows only')
744 781 if options.with_chg:
745 782 options.chg = False # no installation to temporary location
746 783 options.with_chg = canonpath(_sys2bytes(options.with_chg))
747 784 if not (
748 785 os.path.isfile(options.with_chg)
749 786 and os.access(options.with_chg, os.X_OK)
750 787 ):
751 788 parser.error('--with-chg must specify a chg executable')
752 789 if options.with_rhg:
753 790 options.rhg = False # no installation to temporary location
754 791 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
755 792 if not (
756 793 os.path.isfile(options.with_rhg)
757 794 and os.access(options.with_rhg, os.X_OK)
758 795 ):
759 796 parser.error('--with-rhg must specify a rhg executable')
760 797 if options.chg and options.with_hg:
761 798 # chg shares installation location with hg
762 799 parser.error(
763 800 '--chg does not work when --with-hg is specified '
764 801 '(use --with-chg instead)'
765 802 )
766 803 if options.rhg and options.with_hg:
767 804 # rhg shares installation location with hg
768 805 parser.error(
769 806 '--rhg does not work when --with-hg is specified '
770 807 '(use --with-rhg instead)'
771 808 )
772 809 if options.rhg and options.chg:
773 810 parser.error('--rhg and --chg do not work together')
774 811
775 812 if options.color == 'always' and not pygmentspresent:
776 813 sys.stderr.write(
777 814 'warning: --color=always ignored because '
778 815 'pygments is not installed\n'
779 816 )
780 817
781 818 if options.bisect_repo and not options.known_good_rev:
782 819 parser.error("--bisect-repo cannot be used without --known-good-rev")
783 820
784 821 global useipv6
785 822 if options.ipv6:
786 823 useipv6 = checksocketfamily('AF_INET6')
787 824 else:
788 825 # only use IPv6 if IPv4 is unavailable and IPv6 is available
789 826 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
790 827 'AF_INET6'
791 828 )
792 829
793 830 options.anycoverage = options.cover or options.annotate or options.htmlcov
794 831 if options.anycoverage:
795 832 try:
796 833 import coverage
797 834
798 835 coverage.__version__ # silence unused import warning
799 836 except ImportError:
800 837 parser.error('coverage options now require the coverage package')
801 838
802 839 if options.anycoverage and options.local:
803 840 # this needs some path mangling somewhere, I guess
804 841 parser.error(
805 842 "sorry, coverage options do not work when --local " "is specified"
806 843 )
807 844
808 845 if options.anycoverage and options.with_hg:
809 846 parser.error(
810 847 "sorry, coverage options do not work when --with-hg " "is specified"
811 848 )
812 849
813 850 global verbose
814 851 if options.verbose:
815 852 verbose = ''
816 853
817 854 if options.tmpdir:
818 855 options.tmpdir = canonpath(options.tmpdir)
819 856
820 857 if options.jobs < 1:
821 858 parser.error('--jobs must be positive')
822 859 if options.interactive and options.debug:
823 860 parser.error("-i/--interactive and -d/--debug are incompatible")
824 861 if options.debug:
825 862 if options.timeout != defaults['timeout']:
826 863 sys.stderr.write('warning: --timeout option ignored with --debug\n')
827 864 if options.slowtimeout != defaults['slowtimeout']:
828 865 sys.stderr.write(
829 866 'warning: --slowtimeout option ignored with --debug\n'
830 867 )
831 868 options.timeout = 0
832 869 options.slowtimeout = 0
833 870
834 871 if options.blacklist:
835 872 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
836 873 if options.whitelist:
837 874 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
838 875 else:
839 876 options.whitelisted = {}
840 877
841 878 if options.showchannels:
842 879 options.nodiff = True
843 880
844 881 return options
845 882
846 883
847 884 def rename(src, dst):
848 885 """Like os.rename(), trade atomicity and opened files friendliness
849 886 for existing destination support.
850 887 """
851 888 shutil.copy(src, dst)
852 889 os.remove(src)
853 890
854 891
855 892 def makecleanable(path):
856 893 """Try to fix directory permission recursively so that the entire tree
857 894 can be deleted"""
858 895 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
859 896 for d in dirnames:
860 897 p = os.path.join(dirpath, d)
861 898 try:
862 899 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
863 900 except OSError:
864 901 pass
865 902
866 903
867 904 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
868 905
869 906
870 907 def getdiff(expected, output, ref, err):
871 908 servefail = False
872 909 lines = []
873 910 for line in _unified_diff(expected, output, ref, err):
874 911 if line.startswith(b'+++') or line.startswith(b'---'):
875 912 line = line.replace(b'\\', b'/')
876 913 if line.endswith(b' \n'):
877 914 line = line[:-2] + b'\n'
878 915 lines.append(line)
879 916 if not servefail and line.startswith(
880 917 b'+ abort: child process failed to start'
881 918 ):
882 919 servefail = True
883 920
884 921 return servefail, lines
885 922
886 923
887 924 verbose = False
888 925
889 926
890 927 def vlog(*msg):
891 928 """Log only when in verbose mode."""
892 929 if verbose is False:
893 930 return
894 931
895 932 return log(*msg)
896 933
897 934
898 935 # Bytes that break XML even in a CDATA block: control characters 0-31
899 936 # sans \t, \n and \r
900 937 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
901 938
902 939 # Match feature conditionalized output lines in the form, capturing the feature
903 940 # list in group 2, and the preceeding line output in group 1:
904 941 #
905 942 # output..output (feature !)\n
906 943 optline = re.compile(br'(.*) \((.+?) !\)\n$')
907 944
908 945
909 946 def cdatasafe(data):
910 947 """Make a string safe to include in a CDATA block.
911 948
912 949 Certain control characters are illegal in a CDATA block, and
913 950 there's no way to include a ]]> in a CDATA either. This function
914 951 replaces illegal bytes with ? and adds a space between the ]] so
915 952 that it won't break the CDATA block.
916 953 """
917 954 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
918 955
919 956
920 957 def log(*msg):
921 958 """Log something to stdout.
922 959
923 960 Arguments are strings to print.
924 961 """
925 962 with iolock:
926 963 if verbose:
927 964 print(verbose, end=' ')
928 965 for m in msg:
929 966 print(m, end=' ')
930 967 print()
931 968 sys.stdout.flush()
932 969
933 970
934 971 def highlightdiff(line, color):
935 972 if not color:
936 973 return line
937 974 assert pygmentspresent
938 975 return pygments.highlight(
939 976 line.decode('latin1'), difflexer, terminal256formatter
940 977 ).encode('latin1')
941 978
942 979
943 980 def highlightmsg(msg, color):
944 981 if not color:
945 982 return msg
946 983 assert pygmentspresent
947 984 return pygments.highlight(msg, runnerlexer, runnerformatter)
948 985
949 986
987 def highlight_progress(progress, color):
988 if not color:
989 return progress
990 assert pygmentspresent
991 token = progress_type.get(progress)
992 if token is None:
993 return progress
994 style = runnerformatter.style_string.get(str(token))
995 if style is None:
996 return progress
997 else:
998 return style[0] + progress + style[1]
999
1000
950 1001 def terminate(proc):
951 1002 """Terminate subprocess"""
952 1003 vlog('# Terminating process %d' % proc.pid)
953 1004 try:
954 1005 proc.terminate()
955 1006 except OSError:
956 1007 pass
957 1008
958 1009
959 1010 def killdaemons(pidfile):
960 1011 import killdaemons as killmod
961 1012
962 1013 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
963 1014
964 1015
965 1016 # sysconfig is not thread-safe (https://github.com/python/cpython/issues/92452)
966 1017 sysconfiglock = threading.Lock()
967 1018
968 1019
969 1020 class Test(unittest.TestCase):
970 1021 """Encapsulates a single, runnable test.
971 1022
972 1023 While this class conforms to the unittest.TestCase API, it differs in that
973 1024 instances need to be instantiated manually. (Typically, unittest.TestCase
974 1025 classes are instantiated automatically by scanning modules.)
975 1026 """
976 1027
977 1028 # Status code reserved for skipped tests (used by hghave).
978 1029 SKIPPED_STATUS = 80
979 1030
980 1031 def __init__(
981 1032 self,
982 1033 path,
983 1034 outputdir,
984 1035 tmpdir,
985 1036 keeptmpdir=False,
986 1037 debug=False,
987 1038 first=False,
988 1039 timeout=None,
989 1040 startport=None,
990 1041 extraconfigopts=None,
991 1042 shell=None,
992 1043 hgcommand=None,
993 1044 slowtimeout=None,
994 1045 usechg=False,
995 1046 chgdebug=False,
996 1047 useipv6=False,
997 1048 ):
998 1049 """Create a test from parameters.
999 1050
1000 1051 path is the full path to the file defining the test.
1001 1052
1002 1053 tmpdir is the main temporary directory to use for this test.
1003 1054
1004 1055 keeptmpdir determines whether to keep the test's temporary directory
1005 1056 after execution. It defaults to removal (False).
1006 1057
1007 1058 debug mode will make the test execute verbosely, with unfiltered
1008 1059 output.
1009 1060
1010 1061 timeout controls the maximum run time of the test. It is ignored when
1011 1062 debug is True. See slowtimeout for tests with #require slow.
1012 1063
1013 1064 slowtimeout overrides timeout if the test has #require slow.
1014 1065
1015 1066 startport controls the starting port number to use for this test. Each
1016 1067 test will reserve 3 port numbers for execution. It is the caller's
1017 1068 responsibility to allocate a non-overlapping port range to Test
1018 1069 instances.
1019 1070
1020 1071 extraconfigopts is an iterable of extra hgrc config options. Values
1021 1072 must have the form "key=value" (something understood by hgrc). Values
1022 1073 of the form "foo.key=value" will result in "[foo] key=value".
1023 1074
1024 1075 shell is the shell to execute tests in.
1025 1076 """
1026 1077 if timeout is None:
1027 1078 timeout = defaults['timeout']
1028 1079 if startport is None:
1029 1080 startport = defaults['port']
1030 1081 if slowtimeout is None:
1031 1082 slowtimeout = defaults['slowtimeout']
1032 1083 self.path = path
1033 1084 self.relpath = os.path.relpath(path)
1034 1085 self.bname = os.path.basename(path)
1035 1086 self.name = _bytes2sys(self.bname)
1036 1087 self._testdir = os.path.dirname(path)
1037 1088 self._outputdir = outputdir
1038 1089 self._tmpname = os.path.basename(path)
1039 1090 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1040 1091
1041 1092 self._threadtmp = tmpdir
1042 1093 self._keeptmpdir = keeptmpdir
1043 1094 self._debug = debug
1044 1095 self._first = first
1045 1096 self._timeout = timeout
1046 1097 self._slowtimeout = slowtimeout
1047 1098 self._startport = startport
1048 1099 self._extraconfigopts = extraconfigopts or []
1049 1100 self._shell = _sys2bytes(shell)
1050 1101 self._hgcommand = hgcommand or b'hg'
1051 1102 self._usechg = usechg
1052 1103 self._chgdebug = chgdebug
1053 1104 self._useipv6 = useipv6
1054 1105
1055 1106 self._aborted = False
1056 1107 self._daemonpids = []
1057 1108 self._finished = None
1058 1109 self._ret = None
1059 1110 self._out = None
1060 1111 self._skipped = None
1061 1112 self._testtmp = None
1062 1113 self._chgsockdir = None
1063 1114
1064 1115 self._refout = self.readrefout()
1065 1116
1066 1117 def readrefout(self):
1067 1118 """read reference output"""
1068 1119 # If we're not in --debug mode and reference output file exists,
1069 1120 # check test output against it.
1070 1121 if self._debug:
1071 1122 return None # to match "out is None"
1072 1123 elif os.path.exists(self.refpath):
1073 1124 with open(self.refpath, 'rb') as f:
1074 1125 return f.read().splitlines(True)
1075 1126 else:
1076 1127 return []
1077 1128
1078 1129 # needed to get base class __repr__ running
1079 1130 @property
1080 1131 def _testMethodName(self):
1081 1132 return self.name
1082 1133
1083 1134 def __str__(self):
1084 1135 return self.name
1085 1136
1086 1137 def shortDescription(self):
1087 1138 return self.name
1088 1139
1089 1140 def setUp(self):
1090 1141 """Tasks to perform before run()."""
1091 1142 self._finished = False
1092 1143 self._ret = None
1093 1144 self._out = None
1094 1145 self._skipped = None
1095 1146
1096 1147 try:
1097 1148 os.mkdir(self._threadtmp)
1098 1149 except FileExistsError:
1099 1150 pass
1100 1151
1101 1152 name = self._tmpname
1102 1153 self._testtmp = os.path.join(self._threadtmp, name)
1103 1154 os.mkdir(self._testtmp)
1104 1155
1105 1156 # Remove any previous output files.
1106 1157 if os.path.exists(self.errpath):
1107 1158 try:
1108 1159 os.remove(self.errpath)
1109 1160 except FileNotFoundError:
1110 1161 # We might have raced another test to clean up a .err file,
1111 1162 # so ignore FileNotFoundError when removing a previous .err
1112 1163 # file.
1113 1164 pass
1114 1165
1115 1166 if self._usechg:
1116 1167 self._chgsockdir = os.path.join(
1117 1168 self._threadtmp, b'%s.chgsock' % name
1118 1169 )
1119 1170 os.mkdir(self._chgsockdir)
1120 1171
1121 1172 def run(self, result):
1122 1173 """Run this test and report results against a TestResult instance."""
1123 1174 # This function is extremely similar to unittest.TestCase.run(). Once
1124 1175 # we require Python 2.7 (or at least its version of unittest), this
1125 1176 # function can largely go away.
1126 1177 self._result = result
1127 1178 result.startTest(self)
1128 1179 try:
1129 1180 try:
1130 1181 self.setUp()
1131 1182 except (KeyboardInterrupt, SystemExit):
1132 1183 self._aborted = True
1133 1184 raise
1134 1185 except Exception:
1135 1186 result.addError(self, sys.exc_info())
1136 1187 return
1137 1188
1138 1189 success = False
1139 1190 try:
1140 1191 self.runTest()
1141 1192 except KeyboardInterrupt:
1142 1193 self._aborted = True
1143 1194 raise
1144 1195 except unittest.SkipTest as e:
1145 1196 result.addSkip(self, str(e))
1146 1197 # The base class will have already counted this as a
1147 1198 # test we "ran", but we want to exclude skipped tests
1148 1199 # from those we count towards those run.
1149 1200 result.testsRun -= 1
1150 1201 except self.failureException as e:
1151 1202 # This differs from unittest in that we don't capture
1152 1203 # the stack trace. This is for historical reasons and
1153 1204 # this decision could be revisited in the future,
1154 1205 # especially for PythonTest instances.
1155 1206 if result.addFailure(self, str(e)):
1156 1207 success = True
1157 1208 except Exception:
1158 1209 result.addError(self, sys.exc_info())
1159 1210 else:
1160 1211 success = True
1161 1212
1162 1213 try:
1163 1214 self.tearDown()
1164 1215 except (KeyboardInterrupt, SystemExit):
1165 1216 self._aborted = True
1166 1217 raise
1167 1218 except Exception:
1168 1219 result.addError(self, sys.exc_info())
1169 1220 success = False
1170 1221
1171 1222 if success:
1172 1223 result.addSuccess(self)
1173 1224 finally:
1174 1225 result.stopTest(self, interrupted=self._aborted)
1175 1226
1176 1227 def runTest(self):
1177 1228 """Run this test instance.
1178 1229
1179 1230 This will return a tuple describing the result of the test.
1180 1231 """
1181 1232 env = self._getenv()
1182 1233 self._genrestoreenv(env)
1183 1234 self._daemonpids.append(env['DAEMON_PIDS'])
1184 1235 self._createhgrc(env['HGRCPATH'])
1185 1236
1186 1237 vlog('# Test', self.name)
1187 1238
1188 1239 ret, out = self._run(env)
1189 1240 self._finished = True
1190 1241 self._ret = ret
1191 1242 self._out = out
1192 1243
1193 1244 def describe(ret):
1194 1245 if ret < 0:
1195 1246 return 'killed by signal: %d' % -ret
1196 1247 return 'returned error code %d' % ret
1197 1248
1198 1249 self._skipped = False
1199 1250
1200 1251 if ret == self.SKIPPED_STATUS:
1201 1252 if out is None: # Debug mode, nothing to parse.
1202 1253 missing = ['unknown']
1203 1254 failed = None
1204 1255 else:
1205 1256 missing, failed = TTest.parsehghaveoutput(out)
1206 1257
1207 1258 if not missing:
1208 1259 missing = ['skipped']
1209 1260
1210 1261 if failed:
1211 1262 self.fail('hg have failed checking for %s' % failed[-1])
1212 1263 else:
1213 1264 self._skipped = True
1214 1265 raise unittest.SkipTest(missing[-1])
1215 1266 elif ret == 'timeout':
1216 1267 self.fail('timed out')
1217 1268 elif ret is False:
1218 1269 self.fail('no result code from test')
1219 1270 elif out != self._refout:
1220 1271 # Diff generation may rely on written .err file.
1221 1272 if (
1222 1273 (ret != 0 or out != self._refout)
1223 1274 and not self._skipped
1224 1275 and not self._debug
1225 1276 ):
1226 1277 with open(self.errpath, 'wb') as f:
1227 1278 for line in out:
1228 1279 f.write(line)
1229 1280
1230 1281 # The result object handles diff calculation for us.
1231 1282 with firstlock:
1232 1283 if self._result.addOutputMismatch(self, ret, out, self._refout):
1233 1284 # change was accepted, skip failing
1234 1285 return
1235 1286 if self._first:
1236 1287 global firsterror
1237 1288 firsterror = True
1238 1289
1239 1290 if ret:
1240 1291 msg = 'output changed and ' + describe(ret)
1241 1292 else:
1242 1293 msg = 'output changed'
1243 1294
1244 1295 self.fail(msg)
1245 1296 elif ret:
1246 1297 self.fail(describe(ret))
1247 1298
1248 1299 def tearDown(self):
1249 1300 """Tasks to perform after run()."""
1250 1301 for entry in self._daemonpids:
1251 1302 killdaemons(entry)
1252 1303 self._daemonpids = []
1253 1304
1254 1305 if self._keeptmpdir:
1255 1306 log(
1256 1307 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1257 1308 % (
1258 1309 _bytes2sys(self._testtmp),
1259 1310 _bytes2sys(self._threadtmp),
1260 1311 )
1261 1312 )
1262 1313 else:
1263 1314 try:
1264 1315 shutil.rmtree(self._testtmp)
1265 1316 except OSError:
1266 1317 # unreadable directory may be left in $TESTTMP; fix permission
1267 1318 # and try again
1268 1319 makecleanable(self._testtmp)
1269 1320 shutil.rmtree(self._testtmp, True)
1270 1321 shutil.rmtree(self._threadtmp, True)
1271 1322
1272 1323 if self._usechg:
1273 1324 # chgservers will stop automatically after they find the socket
1274 1325 # files are deleted
1275 1326 shutil.rmtree(self._chgsockdir, True)
1276 1327
1277 1328 if (
1278 1329 (self._ret != 0 or self._out != self._refout)
1279 1330 and not self._skipped
1280 1331 and not self._debug
1281 1332 and self._out
1282 1333 ):
1283 1334 with open(self.errpath, 'wb') as f:
1284 1335 for line in self._out:
1285 1336 f.write(line)
1286 1337
1287 1338 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1288 1339
1289 1340 def _run(self, env):
1290 1341 # This should be implemented in child classes to run tests.
1291 1342 raise unittest.SkipTest('unknown test type')
1292 1343
1293 1344 def abort(self):
1294 1345 """Terminate execution of this test."""
1295 1346 self._aborted = True
1296 1347
1297 1348 def _portmap(self, i):
1298 1349 offset = b'' if i == 0 else b'%d' % i
1299 1350 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1300 1351
1301 1352 def _getreplacements(self):
1302 1353 """Obtain a mapping of text replacements to apply to test output.
1303 1354
1304 1355 Test output needs to be normalized so it can be compared to expected
1305 1356 output. This function defines how some of that normalization will
1306 1357 occur.
1307 1358 """
1308 1359 r = [
1309 1360 # This list should be parallel to defineport in _getenv
1310 1361 self._portmap(0),
1311 1362 self._portmap(1),
1312 1363 self._portmap(2),
1313 1364 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1314 1365 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1315 1366 ]
1316 1367 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1317 1368 if WINDOWS:
1318 1369 # JSON output escapes backslashes in Windows paths, so also catch a
1319 1370 # double-escape.
1320 1371 replaced = self._testtmp.replace(b'\\', br'\\')
1321 1372 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1322 1373
1323 1374 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1324 1375
1325 1376 if os.path.exists(replacementfile):
1326 1377 data = {}
1327 1378 with open(replacementfile, mode='rb') as source:
1328 1379 # the intermediate 'compile' step help with debugging
1329 1380 code = compile(source.read(), replacementfile, 'exec')
1330 1381 exec(code, data)
1331 1382 for value in data.get('substitutions', ()):
1332 1383 if len(value) != 2:
1333 1384 msg = 'malformatted substitution in %s: %r'
1334 1385 msg %= (replacementfile, value)
1335 1386 raise ValueError(msg)
1336 1387 r.append(value)
1337 1388 return r
1338 1389
1339 1390 def _escapepath(self, p):
1340 1391 if WINDOWS:
1341 1392 return b''.join(
1342 1393 c.isalpha()
1343 1394 and b'[%s%s]' % (c.lower(), c.upper())
1344 1395 or c in b'/\\'
1345 1396 and br'[/\\]'
1346 1397 or c.isdigit()
1347 1398 and c
1348 1399 or b'\\' + c
1349 1400 for c in [p[i : i + 1] for i in range(len(p))]
1350 1401 )
1351 1402 else:
1352 1403 return re.escape(p)
1353 1404
1354 1405 def _localip(self):
1355 1406 if self._useipv6:
1356 1407 return b'::1'
1357 1408 else:
1358 1409 return b'127.0.0.1'
1359 1410
1360 1411 def _genrestoreenv(self, testenv):
1361 1412 """Generate a script that can be used by tests to restore the original
1362 1413 environment."""
1363 1414 # Put the restoreenv script inside self._threadtmp
1364 1415 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1365 1416 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1366 1417
1367 1418 # Only restore environment variable names that the shell allows
1368 1419 # us to export.
1369 1420 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1370 1421
1371 1422 # Do not restore these variables; otherwise tests would fail.
1372 1423 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1373 1424
1374 1425 with open(scriptpath, 'w') as envf:
1375 1426 for name, value in origenviron.items():
1376 1427 if not name_regex.match(name):
1377 1428 # Skip environment variables with unusual names not
1378 1429 # allowed by most shells.
1379 1430 continue
1380 1431 if name in reqnames:
1381 1432 continue
1382 1433 envf.write('%s=%s\n' % (name, shellquote(value)))
1383 1434
1384 1435 for name in testenv:
1385 1436 if name in origenviron or name in reqnames:
1386 1437 continue
1387 1438 envf.write('unset %s\n' % (name,))
1388 1439
1389 1440 def _getenv(self):
1390 1441 """Obtain environment variables to use during test execution."""
1391 1442
1392 1443 def defineport(i):
1393 1444 offset = '' if i == 0 else '%s' % i
1394 1445 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1395 1446
1396 1447 env = os.environ.copy()
1397 1448 with sysconfiglock:
1398 1449 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1399 1450 env['HGEMITWARNINGS'] = '1'
1400 1451 env['TESTTMP'] = _bytes2sys(self._testtmp)
1401 1452 # the FORWARD_SLASH version is useful when running `sh` on non unix
1402 1453 # system (e.g. Windows)
1403 1454 env['TESTTMP_FORWARD_SLASH'] = env['TESTTMP'].replace(os.sep, '/')
1404 1455 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1405 1456 env['HGTEST_UUIDFILE'] = uid_file
1406 1457 env['TESTNAME'] = self.name
1407 1458 env['HOME'] = _bytes2sys(self._testtmp)
1408 1459 if WINDOWS:
1409 1460 env['REALUSERPROFILE'] = env['USERPROFILE']
1410 1461 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1411 1462 env['USERPROFILE'] = env['HOME']
1412 1463 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1413 1464 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1414 1465 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1415 1466 # This number should match portneeded in _getport
1416 1467 for port in range(3):
1417 1468 # This list should be parallel to _portmap in _getreplacements
1418 1469 defineport(port)
1419 1470 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1420 1471 env["DAEMON_PIDS"] = _bytes2sys(
1421 1472 os.path.join(self._threadtmp, b'daemon.pids')
1422 1473 )
1423 1474 env["HGEDITOR"] = (
1424 1475 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1425 1476 )
1426 1477 env["HGUSER"] = "test"
1427 1478 env["HGENCODING"] = "ascii"
1428 1479 env["HGENCODINGMODE"] = "strict"
1429 1480 env["HGHOSTNAME"] = "test-hostname"
1430 1481 env['HGIPV6'] = str(int(self._useipv6))
1431 1482 # See contrib/catapipe.py for how to use this functionality.
1432 1483 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1433 1484 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1434 1485 # non-test one in as a default, otherwise set to devnull
1435 1486 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1436 1487 'HGCATAPULTSERVERPIPE', os.devnull
1437 1488 )
1438 1489
1439 1490 extraextensions = []
1440 1491 for opt in self._extraconfigopts:
1441 1492 section, key = opt.split('.', 1)
1442 1493 if section != 'extensions':
1443 1494 continue
1444 1495 name = key.split('=', 1)[0]
1445 1496 extraextensions.append(name)
1446 1497
1447 1498 if extraextensions:
1448 1499 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1449 1500
1450 1501 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1451 1502 # IP addresses.
1452 1503 env['LOCALIP'] = _bytes2sys(self._localip())
1453 1504
1454 1505 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1455 1506 # but this is needed for testing python instances like dummyssh,
1456 1507 # dummysmtpd.py, and dumbhttp.py.
1457 1508 if WINDOWS:
1458 1509 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1459 1510
1460 1511 # Modified HOME in test environment can confuse Rust tools. So set
1461 1512 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1462 1513 # present and these variables aren't already defined.
1463 1514 cargo_home_path = os.path.expanduser('~/.cargo')
1464 1515 rustup_home_path = os.path.expanduser('~/.rustup')
1465 1516
1466 1517 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1467 1518 env['CARGO_HOME'] = cargo_home_path
1468 1519 if (
1469 1520 os.path.exists(rustup_home_path)
1470 1521 and b'RUSTUP_HOME' not in osenvironb
1471 1522 ):
1472 1523 env['RUSTUP_HOME'] = rustup_home_path
1473 1524
1474 1525 # Reset some environment variables to well-known values so that
1475 1526 # the tests produce repeatable output.
1476 1527 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1477 1528 env['TZ'] = 'GMT'
1478 1529 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1479 1530 env['COLUMNS'] = '80'
1480 1531 env['TERM'] = 'xterm'
1481 1532
1482 1533 dropped = [
1483 1534 'CDPATH',
1484 1535 'CHGDEBUG',
1485 1536 'EDITOR',
1486 1537 'GREP_OPTIONS',
1487 1538 'HG',
1488 1539 'HGMERGE',
1489 1540 'HGPLAIN',
1490 1541 'HGPLAINEXCEPT',
1491 1542 'HGPROF',
1492 1543 'http_proxy',
1493 1544 'no_proxy',
1494 1545 'NO_PROXY',
1495 1546 'PAGER',
1496 1547 'VISUAL',
1497 1548 ]
1498 1549
1499 1550 for k in dropped:
1500 1551 if k in env:
1501 1552 del env[k]
1502 1553
1503 1554 # unset env related to hooks
1504 1555 for k in list(env):
1505 1556 if k.startswith('HG_'):
1506 1557 del env[k]
1507 1558
1508 1559 if self._usechg:
1509 1560 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1510 1561 if self._chgdebug:
1511 1562 env['CHGDEBUG'] = 'true'
1512 1563
1513 1564 return env
1514 1565
1515 1566 def _createhgrc(self, path):
1516 1567 """Create an hgrc file for this test."""
1517 1568 with open(path, 'wb') as hgrc:
1518 1569 hgrc.write(b'[ui]\n')
1519 1570 hgrc.write(b'slash = True\n')
1520 1571 hgrc.write(b'interactive = False\n')
1521 1572 hgrc.write(b'detailed-exit-code = True\n')
1522 1573 hgrc.write(b'merge = internal:merge\n')
1523 1574 hgrc.write(b'mergemarkers = detailed\n')
1524 1575 hgrc.write(b'promptecho = True\n')
1525 1576 dummyssh = os.path.join(self._testdir, b'dummyssh')
1526 1577 hgrc.write(b'ssh = "%s" "%s"\n' % (PYTHON, dummyssh))
1527 1578 hgrc.write(b'timeout.warn=15\n')
1528 1579 hgrc.write(b'[chgserver]\n')
1529 1580 hgrc.write(b'idletimeout=60\n')
1530 1581 hgrc.write(b'[defaults]\n')
1531 1582 hgrc.write(b'[devel]\n')
1532 1583 hgrc.write(b'all-warnings = true\n')
1533 1584 hgrc.write(b'default-date = 0 0\n')
1534 1585 hgrc.write(b'[largefiles]\n')
1535 1586 hgrc.write(
1536 1587 b'usercache = %s\n'
1537 1588 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1538 1589 )
1539 1590 hgrc.write(b'[lfs]\n')
1540 1591 hgrc.write(
1541 1592 b'usercache = %s\n'
1542 1593 % (os.path.join(self._testtmp, b'.cache/lfs'))
1543 1594 )
1544 1595 hgrc.write(b'[web]\n')
1545 1596 hgrc.write(b'address = localhost\n')
1546 1597 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1547 1598 hgrc.write(b'server-header = testing stub value\n')
1548 1599
1549 1600 for opt in self._extraconfigopts:
1550 1601 section, key = _sys2bytes(opt).split(b'.', 1)
1551 1602 assert b'=' in key, (
1552 1603 'extra config opt %s must ' 'have an = for assignment' % opt
1553 1604 )
1554 1605 hgrc.write(b'[%s]\n%s\n' % (section, key))
1555 1606
1556 1607 def fail(self, msg):
1557 1608 # unittest differentiates between errored and failed.
1558 1609 # Failed is denoted by AssertionError (by default at least).
1559 1610 raise AssertionError(msg)
1560 1611
1561 1612 def _runcommand(self, cmd, env, normalizenewlines=False):
1562 1613 """Run command in a sub-process, capturing the output (stdout and
1563 1614 stderr).
1564 1615
1565 1616 Return a tuple (exitcode, output). output is None in debug mode.
1566 1617 """
1567 1618 if self._debug:
1568 1619 proc = subprocess.Popen(
1569 1620 _bytes2sys(cmd),
1570 1621 shell=True,
1571 1622 close_fds=closefds,
1572 1623 cwd=_bytes2sys(self._testtmp),
1573 1624 env=env,
1574 1625 )
1575 1626 ret = proc.wait()
1576 1627 return (ret, None)
1577 1628
1578 1629 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1579 1630
1580 1631 def cleanup():
1581 1632 terminate(proc)
1582 1633 ret = proc.wait()
1583 1634 if ret == 0:
1584 1635 ret = signal.SIGTERM << 8
1585 1636 killdaemons(env['DAEMON_PIDS'])
1586 1637 return ret
1587 1638
1588 1639 proc.tochild.close()
1589 1640
1590 1641 try:
1591 1642 output = proc.fromchild.read()
1592 1643 except KeyboardInterrupt:
1593 1644 vlog('# Handling keyboard interrupt')
1594 1645 cleanup()
1595 1646 raise
1596 1647
1597 1648 ret = proc.wait()
1598 1649 if wifexited(ret):
1599 1650 ret = os.WEXITSTATUS(ret)
1600 1651
1601 1652 if proc.timeout:
1602 1653 ret = 'timeout'
1603 1654
1604 1655 if ret:
1605 1656 killdaemons(env['DAEMON_PIDS'])
1606 1657
1607 1658 for s, r in self._getreplacements():
1608 1659 output = re.sub(s, r, output)
1609 1660
1610 1661 if normalizenewlines:
1611 1662 output = output.replace(b'\r\n', b'\n')
1612 1663
1613 1664 return ret, output.splitlines(True)
1614 1665
1615 1666
1616 1667 class PythonTest(Test):
1617 1668 """A Python-based test."""
1618 1669
1619 1670 @property
1620 1671 def refpath(self):
1621 1672 return os.path.join(self._testdir, b'%s.out' % self.bname)
1622 1673
1623 1674 def _run(self, env):
1624 1675 # Quote the python(3) executable for Windows
1625 1676 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1626 1677 vlog("# Running", cmd.decode("utf-8"))
1627 1678 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1628 1679 if self._aborted:
1629 1680 raise KeyboardInterrupt()
1630 1681
1631 1682 return result
1632 1683
1633 1684
1634 1685 # Some glob patterns apply only in some circumstances, so the script
1635 1686 # might want to remove (glob) annotations that otherwise should be
1636 1687 # retained.
1637 1688 checkcodeglobpats = [
1638 1689 # On Windows it looks like \ doesn't require a (glob), but we know
1639 1690 # better.
1640 1691 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1641 1692 re.compile(br'^moving \S+/.*[^)]$'),
1642 1693 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1643 1694 # Not all platforms have 127.0.0.1 as loopback (though most do),
1644 1695 # so we always glob that too.
1645 1696 re.compile(br'.*\$LOCALIP.*$'),
1646 1697 ]
1647 1698
1648 1699 bchr = lambda x: bytes([x])
1649 1700
1650 1701 WARN_UNDEFINED = 1
1651 1702 WARN_YES = 2
1652 1703 WARN_NO = 3
1653 1704
1654 1705 MARK_OPTIONAL = b" (?)\n"
1655 1706
1656 1707
1657 1708 def isoptional(line):
1658 1709 return line.endswith(MARK_OPTIONAL)
1659 1710
1660 1711
1661 1712 class TTest(Test):
1662 1713 """A "t test" is a test backed by a .t file."""
1663 1714
1664 1715 SKIPPED_PREFIX = b'skipped: '
1665 1716 FAILED_PREFIX = b'hghave check failed: '
1666 1717 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1667 1718
1668 1719 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1669 1720 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1670 1721 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1671 1722
1672 1723 def __init__(self, path, *args, **kwds):
1673 1724 # accept an extra "case" parameter
1674 1725 case = kwds.pop('case', [])
1675 1726 self._case = case
1676 1727 self._allcases = {x for y in parsettestcases(path) for x in y}
1677 1728 super(TTest, self).__init__(path, *args, **kwds)
1678 1729 if case:
1679 1730 casepath = b'#'.join(case)
1680 1731 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1681 1732 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1682 1733 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1683 1734 self._have = {}
1684 1735
1685 1736 @property
1686 1737 def refpath(self):
1687 1738 return os.path.join(self._testdir, self.bname)
1688 1739
1689 1740 def _run(self, env):
1690 1741 with open(self.path, 'rb') as f:
1691 1742 lines = f.readlines()
1692 1743
1693 1744 # .t file is both reference output and the test input, keep reference
1694 1745 # output updated with the the test input. This avoids some race
1695 1746 # conditions where the reference output does not match the actual test.
1696 1747 if self._refout is not None:
1697 1748 self._refout = lines
1698 1749
1699 1750 salt, script, after, expected = self._parsetest(lines)
1700 1751
1701 1752 # Write out the generated script.
1702 1753 fname = b'%s.sh' % self._testtmp
1703 1754 with open(fname, 'wb') as f:
1704 1755 for l in script:
1705 1756 f.write(l)
1706 1757
1707 1758 cmd = b'%s "%s"' % (self._shell, fname)
1708 1759 vlog("# Running", cmd.decode("utf-8"))
1709 1760
1710 1761 exitcode, output = self._runcommand(cmd, env)
1711 1762
1712 1763 if self._aborted:
1713 1764 raise KeyboardInterrupt()
1714 1765
1715 1766 # Do not merge output if skipped. Return hghave message instead.
1716 1767 # Similarly, with --debug, output is None.
1717 1768 if exitcode == self.SKIPPED_STATUS or output is None:
1718 1769 return exitcode, output
1719 1770
1720 1771 return self._processoutput(exitcode, output, salt, after, expected)
1721 1772
1722 1773 def _hghave(self, reqs):
1723 1774 allreqs = b' '.join(reqs)
1724 1775
1725 1776 self._detectslow(reqs)
1726 1777
1727 1778 if allreqs in self._have:
1728 1779 return self._have.get(allreqs)
1729 1780
1730 1781 # TODO do something smarter when all other uses of hghave are gone.
1731 1782 runtestdir = osenvironb[b'RUNTESTDIR']
1732 1783 tdir = runtestdir.replace(b'\\', b'/')
1733 1784 proc = Popen4(
1734 1785 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1735 1786 self._testtmp,
1736 1787 0,
1737 1788 self._getenv(),
1738 1789 )
1739 1790 stdout, stderr = proc.communicate()
1740 1791 ret = proc.wait()
1741 1792 if wifexited(ret):
1742 1793 ret = os.WEXITSTATUS(ret)
1743 1794 if ret == 2:
1744 1795 print(stdout.decode('utf-8'))
1745 1796 sys.exit(1)
1746 1797
1747 1798 if ret != 0:
1748 1799 self._have[allreqs] = (False, stdout)
1749 1800 return False, stdout
1750 1801
1751 1802 self._have[allreqs] = (True, None)
1752 1803 return True, None
1753 1804
1754 1805 def _detectslow(self, reqs):
1755 1806 """update the timeout of slow test when appropriate"""
1756 1807 if b'slow' in reqs:
1757 1808 self._timeout = self._slowtimeout
1758 1809
1759 1810 def _iftest(self, args):
1760 1811 # implements "#if"
1761 1812 reqs = []
1762 1813 for arg in args:
1763 1814 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1764 1815 if arg[3:] in self._case:
1765 1816 return False
1766 1817 elif arg in self._allcases:
1767 1818 if arg not in self._case:
1768 1819 return False
1769 1820 else:
1770 1821 reqs.append(arg)
1771 1822 self._detectslow(reqs)
1772 1823 return self._hghave(reqs)[0]
1773 1824
1774 1825 def _parsetest(self, lines):
1775 1826 # We generate a shell script which outputs unique markers to line
1776 1827 # up script results with our source. These markers include input
1777 1828 # line number and the last return code.
1778 1829 salt = b"SALT%d" % time.time()
1779 1830
1780 1831 def addsalt(line, inpython):
1781 1832 if inpython:
1782 1833 script.append(b'%s %d 0\n' % (salt, line))
1783 1834 else:
1784 1835 script.append(b'echo %s %d $?\n' % (salt, line))
1785 1836
1786 1837 activetrace = []
1787 1838 session = str(uuid.uuid4()).encode('ascii')
1788 1839 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1789 1840 'HGCATAPULTSERVERPIPE'
1790 1841 )
1791 1842
1792 1843 def toggletrace(cmd=None):
1793 1844 if not hgcatapult or hgcatapult == os.devnull:
1794 1845 return
1795 1846
1796 1847 if activetrace:
1797 1848 script.append(
1798 1849 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1799 1850 % (session, activetrace[0])
1800 1851 )
1801 1852 if cmd is None:
1802 1853 return
1803 1854
1804 1855 if isinstance(cmd, str):
1805 1856 quoted = shellquote(cmd.strip())
1806 1857 else:
1807 1858 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1808 1859 quoted = quoted.replace(b'\\', b'\\\\')
1809 1860 script.append(
1810 1861 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1811 1862 % (session, quoted)
1812 1863 )
1813 1864 activetrace[0:] = [quoted]
1814 1865
1815 1866 script = []
1816 1867
1817 1868 # After we run the shell script, we re-unify the script output
1818 1869 # with non-active parts of the source, with synchronization by our
1819 1870 # SALT line number markers. The after table contains the non-active
1820 1871 # components, ordered by line number.
1821 1872 after = {}
1822 1873
1823 1874 # Expected shell script output.
1824 1875 expected = {}
1825 1876
1826 1877 pos = prepos = -1
1827 1878
1828 1879 # The current stack of conditionnal section.
1829 1880 # Each relevant conditionnal section can have the following value:
1830 1881 # - True: we should run this block
1831 1882 # - False: we should skip this block
1832 1883 # - None: The parent block is skipped,
1833 1884 # (no branch of this one will ever run)
1834 1885 condition_stack = []
1835 1886
1836 1887 def run_line():
1837 1888 """return True if the current line should be run"""
1838 1889 if not condition_stack:
1839 1890 return True
1840 1891 return bool(condition_stack[-1])
1841 1892
1842 1893 def push_conditional_block(should_run):
1843 1894 """Push a new conditional context, with its initial state
1844 1895
1845 1896 i.e. entry a #if block"""
1846 1897 if not run_line():
1847 1898 condition_stack.append(None)
1848 1899 else:
1849 1900 condition_stack.append(should_run)
1850 1901
1851 1902 def flip_conditional():
1852 1903 """reverse the current condition state
1853 1904
1854 1905 i.e. enter a #else
1855 1906 """
1856 1907 assert condition_stack
1857 1908 if condition_stack[-1] is not None:
1858 1909 condition_stack[-1] = not condition_stack[-1]
1859 1910
1860 1911 def pop_conditional():
1861 1912 """exit the current skipping context
1862 1913
1863 1914 i.e. reach the #endif"""
1864 1915 assert condition_stack
1865 1916 condition_stack.pop()
1866 1917
1867 1918 # We keep track of whether or not we're in a Python block so we
1868 1919 # can generate the surrounding doctest magic.
1869 1920 inpython = False
1870 1921
1871 1922 if self._debug:
1872 1923 script.append(b'set -x\n')
1873 1924 if os.getenv('MSYSTEM'):
1874 1925 script.append(b'alias pwd="pwd -W"\n')
1875 1926
1876 1927 if hgcatapult and hgcatapult != os.devnull:
1877 1928 hgcatapult = hgcatapult.encode('utf8')
1878 1929 cataname = self.name.encode('utf8')
1879 1930
1880 1931 # Kludge: use a while loop to keep the pipe from getting
1881 1932 # closed by our echo commands. The still-running file gets
1882 1933 # reaped at the end of the script, which causes the while
1883 1934 # loop to exit and closes the pipe. Sigh.
1884 1935 script.append(
1885 1936 b'rtendtracing() {\n'
1886 1937 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1887 1938 b' rm -f "$TESTTMP/.still-running"\n'
1888 1939 b'}\n'
1889 1940 b'trap "rtendtracing" 0\n'
1890 1941 b'touch "$TESTTMP/.still-running"\n'
1891 1942 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1892 1943 b'> %(catapult)s &\n'
1893 1944 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1894 1945 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1895 1946 % {
1896 1947 b'name': cataname,
1897 1948 b'session': session,
1898 1949 b'catapult': hgcatapult,
1899 1950 }
1900 1951 )
1901 1952
1902 1953 if self._case:
1903 1954 casestr = b'#'.join(self._case)
1904 1955 if isinstance(casestr, str):
1905 1956 quoted = shellquote(casestr)
1906 1957 else:
1907 1958 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1908 1959 script.append(b'TESTCASE=%s\n' % quoted)
1909 1960 script.append(b'export TESTCASE\n')
1910 1961
1911 1962 n = 0
1912 1963 for n, l in enumerate(lines):
1913 1964 if not l.endswith(b'\n'):
1914 1965 l += b'\n'
1915 1966 if l.startswith(b'#require'):
1916 1967 lsplit = l.split()
1917 1968 if len(lsplit) < 2 or lsplit[0] != b'#require':
1918 1969 after.setdefault(pos, []).append(
1919 1970 b' !!! invalid #require\n'
1920 1971 )
1921 1972 if run_line():
1922 1973 haveresult, message = self._hghave(lsplit[1:])
1923 1974 if not haveresult:
1924 1975 script = [b'echo "%s"\nexit 80\n' % message]
1925 1976 break
1926 1977 after.setdefault(pos, []).append(l)
1927 1978 elif l.startswith(b'#if'):
1928 1979 lsplit = l.split()
1929 1980 if len(lsplit) < 2 or lsplit[0] != b'#if':
1930 1981 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1931 1982 push_conditional_block(self._iftest(lsplit[1:]))
1932 1983 after.setdefault(pos, []).append(l)
1933 1984 elif l.startswith(b'#else'):
1934 1985 if not condition_stack:
1935 1986 after.setdefault(pos, []).append(b' !!! missing #if\n')
1936 1987 flip_conditional()
1937 1988 after.setdefault(pos, []).append(l)
1938 1989 elif l.startswith(b'#endif'):
1939 1990 if not condition_stack:
1940 1991 after.setdefault(pos, []).append(b' !!! missing #if\n')
1941 1992 pop_conditional()
1942 1993 after.setdefault(pos, []).append(l)
1943 1994 elif not run_line():
1944 1995 after.setdefault(pos, []).append(l)
1945 1996 elif l.startswith(b' >>> '): # python inlines
1946 1997 after.setdefault(pos, []).append(l)
1947 1998 prepos = pos
1948 1999 pos = n
1949 2000 if not inpython:
1950 2001 # We've just entered a Python block. Add the header.
1951 2002 inpython = True
1952 2003 addsalt(prepos, False) # Make sure we report the exit code.
1953 2004 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1954 2005 addsalt(n, True)
1955 2006 script.append(l[2:])
1956 2007 elif l.startswith(b' ... '): # python inlines
1957 2008 after.setdefault(prepos, []).append(l)
1958 2009 script.append(l[2:])
1959 2010 elif l.startswith(b' $ '): # commands
1960 2011 if inpython:
1961 2012 script.append(b'EOF\n')
1962 2013 inpython = False
1963 2014 after.setdefault(pos, []).append(l)
1964 2015 prepos = pos
1965 2016 pos = n
1966 2017 addsalt(n, False)
1967 2018 rawcmd = l[4:]
1968 2019 cmd = rawcmd.split()
1969 2020 toggletrace(rawcmd)
1970 2021 if len(cmd) == 2 and cmd[0] == b'cd':
1971 2022 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1972 2023 script.append(rawcmd)
1973 2024 elif l.startswith(b' > '): # continuations
1974 2025 after.setdefault(prepos, []).append(l)
1975 2026 script.append(l[4:])
1976 2027 elif l.startswith(b' '): # results
1977 2028 # Queue up a list of expected results.
1978 2029 expected.setdefault(pos, []).append(l[2:])
1979 2030 else:
1980 2031 if inpython:
1981 2032 script.append(b'EOF\n')
1982 2033 inpython = False
1983 2034 # Non-command/result. Queue up for merged output.
1984 2035 after.setdefault(pos, []).append(l)
1985 2036
1986 2037 if inpython:
1987 2038 script.append(b'EOF\n')
1988 2039 if condition_stack:
1989 2040 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1990 2041 addsalt(n + 1, False)
1991 2042 # Need to end any current per-command trace
1992 2043 if activetrace:
1993 2044 toggletrace()
1994 2045 return salt, script, after, expected
1995 2046
1996 2047 def _processoutput(self, exitcode, output, salt, after, expected):
1997 2048 # Merge the script output back into a unified test.
1998 2049 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1999 2050 if exitcode != 0:
2000 2051 warnonly = WARN_NO
2001 2052
2002 2053 pos = -1
2003 2054 postout = []
2004 2055 for out_rawline in output:
2005 2056 out_line, cmd_line = out_rawline, None
2006 2057 if salt in out_rawline:
2007 2058 out_line, cmd_line = out_rawline.split(salt, 1)
2008 2059
2009 2060 pos, postout, warnonly = self._process_out_line(
2010 2061 out_line, pos, postout, expected, warnonly
2011 2062 )
2012 2063 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
2013 2064
2014 2065 if pos in after:
2015 2066 postout += after.pop(pos)
2016 2067
2017 2068 if warnonly == WARN_YES:
2018 2069 exitcode = False # Set exitcode to warned.
2019 2070
2020 2071 return exitcode, postout
2021 2072
2022 2073 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
2023 2074 while out_line:
2024 2075 if not out_line.endswith(b'\n'):
2025 2076 out_line += b' (no-eol)\n'
2026 2077
2027 2078 # Find the expected output at the current position.
2028 2079 els = [None]
2029 2080 if expected.get(pos, None):
2030 2081 els = expected[pos]
2031 2082
2032 2083 optional = []
2033 2084 for i, el in enumerate(els):
2034 2085 r = False
2035 2086 if el:
2036 2087 r, exact = self.linematch(el, out_line)
2037 2088 if isinstance(r, str):
2038 2089 if r == '-glob':
2039 2090 out_line = ''.join(el.rsplit(' (glob)', 1))
2040 2091 r = '' # Warn only this line.
2041 2092 elif r == "retry":
2042 2093 postout.append(b' ' + el)
2043 2094 else:
2044 2095 log('\ninfo, unknown linematch result: %r\n' % r)
2045 2096 r = False
2046 2097 if r:
2047 2098 els.pop(i)
2048 2099 break
2049 2100 if el:
2050 2101 if isoptional(el):
2051 2102 optional.append(i)
2052 2103 else:
2053 2104 m = optline.match(el)
2054 2105 if m:
2055 2106 conditions = [c for c in m.group(2).split(b' ')]
2056 2107
2057 2108 if not self._iftest(conditions):
2058 2109 optional.append(i)
2059 2110 if exact:
2060 2111 # Don't allow line to be matches against a later
2061 2112 # line in the output
2062 2113 els.pop(i)
2063 2114 break
2064 2115
2065 2116 if r:
2066 2117 if r == "retry":
2067 2118 continue
2068 2119 # clean up any optional leftovers
2069 2120 for i in optional:
2070 2121 postout.append(b' ' + els[i])
2071 2122 for i in reversed(optional):
2072 2123 del els[i]
2073 2124 postout.append(b' ' + el)
2074 2125 else:
2075 2126 if self.NEEDESCAPE(out_line):
2076 2127 out_line = TTest._stringescape(
2077 2128 b'%s (esc)\n' % out_line.rstrip(b'\n')
2078 2129 )
2079 2130 postout.append(b' ' + out_line) # Let diff deal with it.
2080 2131 if r != '': # If line failed.
2081 2132 warnonly = WARN_NO
2082 2133 elif warnonly == WARN_UNDEFINED:
2083 2134 warnonly = WARN_YES
2084 2135 break
2085 2136 else:
2086 2137 # clean up any optional leftovers
2087 2138 while expected.get(pos, None):
2088 2139 el = expected[pos].pop(0)
2089 2140 if el:
2090 2141 if not isoptional(el):
2091 2142 m = optline.match(el)
2092 2143 if m:
2093 2144 conditions = [c for c in m.group(2).split(b' ')]
2094 2145
2095 2146 if self._iftest(conditions):
2096 2147 # Don't append as optional line
2097 2148 continue
2098 2149 else:
2099 2150 continue
2100 2151 postout.append(b' ' + el)
2101 2152 return pos, postout, warnonly
2102 2153
2103 2154 def _process_cmd_line(self, cmd_line, pos, postout, after):
2104 2155 """process a "command" part of a line from unified test output"""
2105 2156 if cmd_line:
2106 2157 # Add on last return code.
2107 2158 ret = int(cmd_line.split()[1])
2108 2159 if ret != 0:
2109 2160 postout.append(b' [%d]\n' % ret)
2110 2161 if pos in after:
2111 2162 # Merge in non-active test bits.
2112 2163 postout += after.pop(pos)
2113 2164 pos = int(cmd_line.split()[0])
2114 2165 return pos, postout
2115 2166
2116 2167 @staticmethod
2117 2168 def rematch(el, l):
2118 2169 try:
2119 2170 # parse any flags at the beginning of the regex. Only 'i' is
2120 2171 # supported right now, but this should be easy to extend.
2121 2172 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2122 2173 flags = flags or b''
2123 2174 el = flags + b'(?:' + el + b')'
2124 2175 # use \Z to ensure that the regex matches to the end of the string
2125 2176 if WINDOWS:
2126 2177 return re.match(el + br'\r?\n\Z', l)
2127 2178 return re.match(el + br'\n\Z', l)
2128 2179 except re.error:
2129 2180 # el is an invalid regex
2130 2181 return False
2131 2182
2132 2183 @staticmethod
2133 2184 def globmatch(el, l):
2134 2185 # The only supported special characters are * and ? plus / which also
2135 2186 # matches \ on windows. Escaping of these characters is supported.
2136 2187 if el + b'\n' == l:
2137 2188 if os.altsep:
2138 2189 # matching on "/" is not needed for this line
2139 2190 for pat in checkcodeglobpats:
2140 2191 if pat.match(el):
2141 2192 return True
2142 2193 return b'-glob'
2143 2194 return True
2144 2195 el = el.replace(b'$LOCALIP', b'*')
2145 2196 i, n = 0, len(el)
2146 2197 res = b''
2147 2198 while i < n:
2148 2199 c = el[i : i + 1]
2149 2200 i += 1
2150 2201 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2151 2202 res += el[i - 1 : i + 1]
2152 2203 i += 1
2153 2204 elif c == b'*':
2154 2205 res += b'.*'
2155 2206 elif c == b'?':
2156 2207 res += b'.'
2157 2208 elif c == b'/' and os.altsep:
2158 2209 res += b'[/\\\\]'
2159 2210 else:
2160 2211 res += re.escape(c)
2161 2212 return TTest.rematch(res, l)
2162 2213
2163 2214 def linematch(self, el, l):
2164 2215 if el == l: # perfect match (fast)
2165 2216 return True, True
2166 2217 retry = False
2167 2218 if isoptional(el):
2168 2219 retry = "retry"
2169 2220 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2170 2221 else:
2171 2222 m = optline.match(el)
2172 2223 if m:
2173 2224 conditions = [c for c in m.group(2).split(b' ')]
2174 2225
2175 2226 el = m.group(1) + b"\n"
2176 2227 if not self._iftest(conditions):
2177 2228 # listed feature missing, should not match
2178 2229 return "retry", False
2179 2230
2180 2231 if el.endswith(b" (esc)\n"):
2181 2232 el = el[:-7].decode('unicode_escape') + '\n'
2182 2233 el = el.encode('latin-1')
2183 2234 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2184 2235 return True, True
2185 2236 if el.endswith(b" (re)\n"):
2186 2237 return (TTest.rematch(el[:-6], l) or retry), False
2187 2238 if el.endswith(b" (glob)\n"):
2188 2239 # ignore '(glob)' added to l by 'replacements'
2189 2240 if l.endswith(b" (glob)\n"):
2190 2241 l = l[:-8] + b"\n"
2191 2242 return (TTest.globmatch(el[:-8], l) or retry), False
2192 2243 if os.altsep:
2193 2244 _l = l.replace(b'\\', b'/')
2194 2245 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2195 2246 return True, True
2196 2247 return retry, True
2197 2248
2198 2249 @staticmethod
2199 2250 def parsehghaveoutput(lines):
2200 2251 """Parse hghave log lines.
2201 2252
2202 2253 Return tuple of lists (missing, failed):
2203 2254 * the missing/unknown features
2204 2255 * the features for which existence check failed"""
2205 2256 missing = []
2206 2257 failed = []
2207 2258 for line in lines:
2208 2259 if line.startswith(TTest.SKIPPED_PREFIX):
2209 2260 line = line.splitlines()[0]
2210 2261 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2211 2262 elif line.startswith(TTest.FAILED_PREFIX):
2212 2263 line = line.splitlines()[0]
2213 2264 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2214 2265
2215 2266 return missing, failed
2216 2267
2217 2268 @staticmethod
2218 2269 def _escapef(m):
2219 2270 return TTest.ESCAPEMAP[m.group(0)]
2220 2271
2221 2272 @staticmethod
2222 2273 def _stringescape(s):
2223 2274 return TTest.ESCAPESUB(TTest._escapef, s)
2224 2275
2225 2276
2226 2277 iolock = threading.RLock()
2227 2278 firstlock = threading.RLock()
2228 2279 firsterror = False
2229 2280
2230 2281 base_class = unittest.TextTestResult
2231 2282
2232 2283
2233 2284 class TestResult(base_class):
2234 2285 """Holds results when executing via unittest."""
2235 2286
2236 2287 def __init__(self, options, *args, **kwargs):
2237 2288 super(TestResult, self).__init__(*args, **kwargs)
2238 2289
2239 2290 self._options = options
2240 2291
2241 2292 # unittest.TestResult didn't have skipped until 2.7. We need to
2242 2293 # polyfill it.
2243 2294 self.skipped = []
2244 2295
2245 2296 # We have a custom "ignored" result that isn't present in any Python
2246 2297 # unittest implementation. It is very similar to skipped. It may make
2247 2298 # sense to map it into skip some day.
2248 2299 self.ignored = []
2249 2300
2250 2301 self.times = []
2251 2302 self._firststarttime = None
2252 2303 # Data stored for the benefit of generating xunit reports.
2253 2304 self.successes = []
2254 2305 self.faildata = {}
2255 2306
2256 2307 if options.color == 'auto':
2257 2308 isatty = self.stream.isatty()
2258 2309 # For some reason, redirecting stdout on Windows disables the ANSI
2259 2310 # color processing of stderr, which is what is used to print the
2260 2311 # output. Therefore, both must be tty on Windows to enable color.
2261 2312 if WINDOWS:
2262 2313 isatty = isatty and sys.stdout.isatty()
2263 2314 self.color = pygmentspresent and isatty
2264 2315 elif options.color == 'never':
2265 2316 self.color = False
2266 2317 else: # 'always', for testing purposes
2267 2318 self.color = pygmentspresent
2268 2319
2320 def _write_dot(self, progress):
2321 """write an item of the "dot" progress"""
2322 formated = highlight_progress(progress, self.color)
2323 self.stream.write(formated)
2324 self.stream.flush()
2325
2269 2326 def onStart(self, test):
2270 2327 """Can be overriden by custom TestResult"""
2271 2328
2272 2329 def onEnd(self):
2273 2330 """Can be overriden by custom TestResult"""
2274 2331
2275 2332 def addFailure(self, test, reason):
2276 2333 self.failures.append((test, reason))
2277 2334
2278 2335 if self._options.first:
2279 2336 self.stop()
2280 2337 else:
2281 2338 with iolock:
2282 2339 if reason == "timed out":
2283 self.stream.write('t')
2340 self._write_dot('t')
2284 2341 else:
2285 2342 if not self._options.nodiff:
2286 2343 self.stream.write('\n')
2287 2344 # Exclude the '\n' from highlighting to lex correctly
2288 2345 formatted = 'ERROR: %s output changed\n' % test
2289 2346 self.stream.write(highlightmsg(formatted, self.color))
2290 self.stream.write('!')
2347 self._write_dot('!')
2291 2348
2292 2349 self.stream.flush()
2293 2350
2294 2351 def addSuccess(self, test):
2295 2352 with iolock:
2296 super(TestResult, self).addSuccess(test)
2353 # bypass the TextTestResult method as do deal with the output ourself
2354 super(base_class, self).addSuccess(test)
2355 if self.showAll:
2356 self._write_status(test, "ok")
2357 elif self.dots:
2358 self._write_dot('.')
2297 2359 self.successes.append(test)
2298 2360
2299 2361 def addError(self, test, err):
2300 super(TestResult, self).addError(test, err)
2362 super(base_class, self).addError(test, err)
2363 if self.showAll:
2364 self._write_status(test, "ERROR")
2365 elif self.dots:
2366 self._write_dot('E')
2301 2367 if self._options.first:
2302 2368 self.stop()
2303 2369
2304 2370 # Polyfill.
2305 2371 def addSkip(self, test, reason):
2306 2372 self.skipped.append((test, reason))
2307 2373 with iolock:
2308 2374 if self.showAll:
2309 2375 self.stream.writeln('skipped %s' % reason)
2310 2376 else:
2311 self.stream.write('s')
2312 self.stream.flush()
2377 self._write_dot('s')
2313 2378
2314 2379 def addIgnore(self, test, reason):
2315 2380 self.ignored.append((test, reason))
2316 2381 with iolock:
2317 2382 if self.showAll:
2318 2383 self.stream.writeln('ignored %s' % reason)
2319 2384 else:
2320 2385 if reason not in ('not retesting', "doesn't match keyword"):
2321 self.stream.write('i')
2386 self._write_dot('i')
2322 2387 else:
2323 2388 self.testsRun += 1
2324 self.stream.flush()
2325 2389
2326 2390 def addOutputMismatch(self, test, ret, got, expected):
2327 2391 """Record a mismatch in test output for a particular test."""
2328 2392 if self.shouldStop or firsterror:
2329 2393 # don't print, some other test case already failed and
2330 2394 # printed, we're just stale and probably failed due to our
2331 2395 # temp dir getting cleaned up.
2332 2396 return
2333 2397
2334 2398 accepted = False
2335 2399 lines = []
2336 2400
2337 2401 with iolock:
2338 2402 if self._options.nodiff:
2339 2403 pass
2340 2404 elif self._options.view:
2341 2405 v = self._options.view
2342 2406 subprocess.call(
2343 2407 r'"%s" "%s" "%s"'
2344 2408 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2345 2409 shell=True,
2346 2410 )
2347 2411 else:
2348 2412 servefail, lines = getdiff(
2349 2413 expected, got, test.refpath, test.errpath
2350 2414 )
2351 2415 self.stream.write('\n')
2352 2416 for line in lines:
2353 2417 line = highlightdiff(line, self.color)
2354 2418 self.stream.flush()
2355 2419 self.stream.buffer.write(line)
2356 2420 self.stream.buffer.flush()
2357 2421
2358 2422 if servefail:
2359 2423 raise test.failureException(
2360 2424 'server failed to start (HGPORT=%s)' % test._startport
2361 2425 )
2362 2426
2363 2427 # handle interactive prompt without releasing iolock
2364 2428 if self._options.interactive:
2365 2429 if test.readrefout() != expected:
2366 2430 self.stream.write(
2367 2431 'Reference output has changed (run again to prompt '
2368 2432 'changes)'
2369 2433 )
2370 2434 else:
2371 2435 self.stream.write('Accept this change? [y/N] ')
2372 2436 self.stream.flush()
2373 2437 answer = sys.stdin.readline().strip()
2374 2438 if answer.lower() in ('y', 'yes'):
2375 2439 if test.path.endswith(b'.t'):
2376 2440 rename(test.errpath, test.path)
2377 2441 else:
2378 2442 rename(test.errpath, b'%s.out' % test.path)
2379 2443 accepted = True
2380 2444 if not accepted:
2381 2445 self.faildata[test.name] = b''.join(lines)
2382 2446
2383 2447 return accepted
2384 2448
2385 2449 def startTest(self, test):
2386 2450 super(TestResult, self).startTest(test)
2387 2451
2388 2452 # os.times module computes the user time and system time spent by
2389 2453 # child's processes along with real elapsed time taken by a process.
2390 2454 # This module has one limitation. It can only work for Linux user
2391 2455 # and not for Windows. Hence why we fall back to another function
2392 2456 # for wall time calculations.
2393 2457 test.started_times = os.times()
2394 2458 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2395 2459 test.started_time = time.time()
2396 2460 if self._firststarttime is None: # thread racy but irrelevant
2397 2461 self._firststarttime = test.started_time
2398 2462
2399 2463 def stopTest(self, test, interrupted=False):
2400 2464 super(TestResult, self).stopTest(test)
2401 2465
2402 2466 test.stopped_times = os.times()
2403 2467 stopped_time = time.time()
2404 2468
2405 2469 starttime = test.started_times
2406 2470 endtime = test.stopped_times
2407 2471 origin = self._firststarttime
2408 2472 self.times.append(
2409 2473 (
2410 2474 test.name,
2411 2475 endtime[2] - starttime[2], # user space CPU time
2412 2476 endtime[3] - starttime[3], # sys space CPU time
2413 2477 stopped_time - test.started_time, # real time
2414 2478 test.started_time - origin, # start date in run context
2415 2479 stopped_time - origin, # end date in run context
2416 2480 )
2417 2481 )
2418 2482
2419 2483 if interrupted:
2420 2484 with iolock:
2421 2485 self.stream.writeln(
2422 2486 'INTERRUPTED: %s (after %d seconds)'
2423 2487 % (test.name, self.times[-1][3])
2424 2488 )
2425 2489
2426 2490
2427 2491 def getTestResult():
2428 2492 """
2429 2493 Returns the relevant test result
2430 2494 """
2431 2495 if "CUSTOM_TEST_RESULT" in os.environ:
2432 2496 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2433 2497 return testresultmodule.TestResult
2434 2498 else:
2435 2499 return TestResult
2436 2500
2437 2501
2438 2502 class TestSuite(unittest.TestSuite):
2439 2503 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2440 2504
2441 2505 def __init__(
2442 2506 self,
2443 2507 testdir,
2444 2508 jobs=1,
2445 2509 whitelist=None,
2446 2510 blacklist=None,
2447 2511 keywords=None,
2448 2512 loop=False,
2449 2513 runs_per_test=1,
2450 2514 loadtest=None,
2451 2515 showchannels=False,
2452 2516 *args,
2453 2517 **kwargs
2454 2518 ):
2455 2519 """Create a new instance that can run tests with a configuration.
2456 2520
2457 2521 testdir specifies the directory where tests are executed from. This
2458 2522 is typically the ``tests`` directory from Mercurial's source
2459 2523 repository.
2460 2524
2461 2525 jobs specifies the number of jobs to run concurrently. Each test
2462 2526 executes on its own thread. Tests actually spawn new processes, so
2463 2527 state mutation should not be an issue.
2464 2528
2465 2529 If there is only one job, it will use the main thread.
2466 2530
2467 2531 whitelist and blacklist denote tests that have been whitelisted and
2468 2532 blacklisted, respectively. These arguments don't belong in TestSuite.
2469 2533 Instead, whitelist and blacklist should be handled by the thing that
2470 2534 populates the TestSuite with tests. They are present to preserve
2471 2535 backwards compatible behavior which reports skipped tests as part
2472 2536 of the results.
2473 2537
2474 2538 keywords denotes key words that will be used to filter which tests
2475 2539 to execute. This arguably belongs outside of TestSuite.
2476 2540
2477 2541 loop denotes whether to loop over tests forever.
2478 2542 """
2479 2543 super(TestSuite, self).__init__(*args, **kwargs)
2480 2544
2481 2545 self._jobs = jobs
2482 2546 self._whitelist = whitelist
2483 2547 self._blacklist = blacklist
2484 2548 self._keywords = keywords
2485 2549 self._loop = loop
2486 2550 self._runs_per_test = runs_per_test
2487 2551 self._loadtest = loadtest
2488 2552 self._showchannels = showchannels
2489 2553
2490 2554 def run(self, result):
2491 2555 # We have a number of filters that need to be applied. We do this
2492 2556 # here instead of inside Test because it makes the running logic for
2493 2557 # Test simpler.
2494 2558 tests = []
2495 2559 num_tests = [0]
2496 2560 for test in self._tests:
2497 2561
2498 2562 def get():
2499 2563 num_tests[0] += 1
2500 2564 if getattr(test, 'should_reload', False):
2501 2565 return self._loadtest(test, num_tests[0])
2502 2566 return test
2503 2567
2504 2568 if not os.path.exists(test.path):
2505 2569 result.addSkip(test, "Doesn't exist")
2506 2570 continue
2507 2571
2508 2572 is_whitelisted = self._whitelist and (
2509 2573 test.relpath in self._whitelist or test.bname in self._whitelist
2510 2574 )
2511 2575 if not is_whitelisted:
2512 2576 is_blacklisted = self._blacklist and (
2513 2577 test.relpath in self._blacklist
2514 2578 or test.bname in self._blacklist
2515 2579 )
2516 2580 if is_blacklisted:
2517 2581 result.addSkip(test, 'blacklisted')
2518 2582 continue
2519 2583 if self._keywords:
2520 2584 with open(test.path, 'rb') as f:
2521 2585 t = f.read().lower() + test.bname.lower()
2522 2586 ignored = False
2523 2587 for k in self._keywords.lower().split():
2524 2588 if k not in t:
2525 2589 result.addIgnore(test, "doesn't match keyword")
2526 2590 ignored = True
2527 2591 break
2528 2592
2529 2593 if ignored:
2530 2594 continue
2531 2595 for _ in range(self._runs_per_test):
2532 2596 tests.append(get())
2533 2597
2534 2598 runtests = list(tests)
2535 2599 done = queue.Queue()
2536 2600 running = 0
2537 2601
2538 2602 channels_lock = threading.Lock()
2539 2603 channels = [""] * self._jobs
2540 2604
2541 2605 def job(test, result):
2542 2606 with channels_lock:
2543 2607 for n, v in enumerate(channels):
2544 2608 if not v:
2545 2609 channel = n
2546 2610 break
2547 2611 else:
2548 2612 raise ValueError('Could not find output channel')
2549 2613 channels[channel] = "=" + test.name[5:].split(".")[0]
2550 2614
2551 2615 r = None
2552 2616 try:
2553 2617 test(result)
2554 2618 except KeyboardInterrupt:
2555 2619 pass
2556 2620 except: # re-raises
2557 2621 r = ('!', test, 'run-test raised an error, see traceback')
2558 2622 raise
2559 2623 finally:
2560 2624 try:
2561 2625 channels[channel] = ''
2562 2626 except IndexError:
2563 2627 pass
2564 2628 done.put(r)
2565 2629
2566 2630 def stat():
2567 2631 count = 0
2568 2632 while channels:
2569 2633 d = '\n%03s ' % count
2570 2634 for n, v in enumerate(channels):
2571 2635 if v:
2572 2636 d += v[0]
2573 2637 channels[n] = v[1:] or '.'
2574 2638 else:
2575 2639 d += ' '
2576 2640 d += ' '
2577 2641 with iolock:
2578 2642 sys.stdout.write(d + ' ')
2579 2643 sys.stdout.flush()
2580 2644 for x in range(10):
2581 2645 if channels:
2582 2646 time.sleep(0.1)
2583 2647 count += 1
2584 2648
2585 2649 stoppedearly = False
2586 2650
2587 2651 if self._showchannels:
2588 2652 statthread = threading.Thread(target=stat, name="stat")
2589 2653 statthread.start()
2590 2654
2591 2655 try:
2592 2656 while tests or running:
2593 2657 if not done.empty() or running == self._jobs or not tests:
2594 2658 try:
2595 2659 done.get(True, 1)
2596 2660 running -= 1
2597 2661 if result and result.shouldStop:
2598 2662 stoppedearly = True
2599 2663 break
2600 2664 except queue.Empty:
2601 2665 continue
2602 2666 if tests and not running == self._jobs:
2603 2667 test = tests.pop(0)
2604 2668 if self._loop:
2605 2669 if getattr(test, 'should_reload', False):
2606 2670 num_tests[0] += 1
2607 2671 tests.append(self._loadtest(test, num_tests[0]))
2608 2672 else:
2609 2673 tests.append(test)
2610 2674 if self._jobs == 1:
2611 2675 job(test, result)
2612 2676 else:
2613 2677 t = threading.Thread(
2614 2678 target=job, name=test.name, args=(test, result)
2615 2679 )
2616 2680 t.start()
2617 2681 running += 1
2618 2682
2619 2683 # If we stop early we still need to wait on started tests to
2620 2684 # finish. Otherwise, there is a race between the test completing
2621 2685 # and the test's cleanup code running. This could result in the
2622 2686 # test reporting incorrect.
2623 2687 if stoppedearly:
2624 2688 while running:
2625 2689 try:
2626 2690 done.get(True, 1)
2627 2691 running -= 1
2628 2692 except queue.Empty:
2629 2693 continue
2630 2694 except KeyboardInterrupt:
2631 2695 for test in runtests:
2632 2696 test.abort()
2633 2697
2634 2698 channels = []
2635 2699
2636 2700 return result
2637 2701
2638 2702
2639 2703 # Save the most recent 5 wall-clock runtimes of each test to a
2640 2704 # human-readable text file named .testtimes. Tests are sorted
2641 2705 # alphabetically, while times for each test are listed from oldest to
2642 2706 # newest.
2643 2707
2644 2708
2645 2709 def loadtimes(outputdir):
2646 2710 times = []
2647 2711 try:
2648 2712 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2649 2713 for line in fp:
2650 2714 m = re.match('(.*?) ([0-9. ]+)', line)
2651 2715 times.append(
2652 2716 (m.group(1), [float(t) for t in m.group(2).split()])
2653 2717 )
2654 2718 except FileNotFoundError:
2655 2719 pass
2656 2720 return times
2657 2721
2658 2722
2659 2723 def savetimes(outputdir, result):
2660 2724 saved = dict(loadtimes(outputdir))
2661 2725 maxruns = 5
2662 2726 skipped = {str(t[0]) for t in result.skipped}
2663 2727 for tdata in result.times:
2664 2728 test, real = tdata[0], tdata[3]
2665 2729 if test not in skipped:
2666 2730 ts = saved.setdefault(test, [])
2667 2731 ts.append(real)
2668 2732 ts[:] = ts[-maxruns:]
2669 2733
2670 2734 fd, tmpname = tempfile.mkstemp(
2671 2735 prefix=b'.testtimes', dir=outputdir, text=True
2672 2736 )
2673 2737 with os.fdopen(fd, 'w') as fp:
2674 2738 for name, ts in sorted(saved.items()):
2675 2739 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2676 2740 timepath = os.path.join(outputdir, b'.testtimes')
2677 2741 try:
2678 2742 os.unlink(timepath)
2679 2743 except OSError:
2680 2744 pass
2681 2745 try:
2682 2746 os.rename(tmpname, timepath)
2683 2747 except OSError:
2684 2748 pass
2685 2749
2686 2750
2687 2751 class TextTestRunner(unittest.TextTestRunner):
2688 2752 """Custom unittest test runner that uses appropriate settings."""
2689 2753
2690 2754 def __init__(self, runner, *args, **kwargs):
2691 2755 super(TextTestRunner, self).__init__(*args, **kwargs)
2692 2756
2693 2757 self._runner = runner
2694 2758
2695 2759 self._result = getTestResult()(
2696 2760 self._runner.options, self.stream, self.descriptions, self.verbosity
2697 2761 )
2698 2762
2699 2763 def listtests(self, test):
2700 2764 test = sorted(test, key=lambda t: t.name)
2701 2765
2702 2766 self._result.onStart(test)
2703 2767
2704 2768 for t in test:
2705 2769 print(t.name)
2706 2770 self._result.addSuccess(t)
2707 2771
2708 2772 if self._runner.options.xunit:
2709 2773 with open(self._runner.options.xunit, "wb") as xuf:
2710 2774 self._writexunit(self._result, xuf)
2711 2775
2712 2776 if self._runner.options.json:
2713 2777 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2714 2778 with open(jsonpath, 'w') as fp:
2715 2779 self._writejson(self._result, fp)
2716 2780
2717 2781 return self._result
2718 2782
2719 2783 def run(self, test):
2720 2784 self._result.onStart(test)
2721 2785 test(self._result)
2722 2786
2723 2787 failed = len(self._result.failures)
2724 2788 skipped = len(self._result.skipped)
2725 2789 ignored = len(self._result.ignored)
2726 2790
2727 2791 with iolock:
2728 2792 self.stream.writeln('')
2729 2793
2730 2794 if not self._runner.options.noskips:
2731 2795 for test, msg in sorted(
2732 2796 self._result.skipped, key=lambda s: s[0].name
2733 2797 ):
2734 2798 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2735 2799 msg = highlightmsg(formatted, self._result.color)
2736 2800 self.stream.write(msg)
2737 2801 for test, msg in sorted(
2738 2802 self._result.failures, key=lambda f: f[0].name
2739 2803 ):
2740 2804 formatted = 'Failed %s: %s\n' % (test.name, msg)
2741 2805 self.stream.write(highlightmsg(formatted, self._result.color))
2742 2806 for test, msg in sorted(
2743 2807 self._result.errors, key=lambda e: e[0].name
2744 2808 ):
2745 2809 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2746 2810
2747 2811 if self._runner.options.xunit:
2748 2812 with open(self._runner.options.xunit, "wb") as xuf:
2749 2813 self._writexunit(self._result, xuf)
2750 2814
2751 2815 if self._runner.options.json:
2752 2816 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2753 2817 with open(jsonpath, 'w') as fp:
2754 2818 self._writejson(self._result, fp)
2755 2819
2756 2820 self._runner._checkhglib('Tested')
2757 2821
2758 2822 savetimes(self._runner._outputdir, self._result)
2759 2823
2760 2824 if failed and self._runner.options.known_good_rev:
2761 2825 self._bisecttests(t for t, m in self._result.failures)
2762 2826 self.stream.writeln(
2763 2827 '# Ran %d tests, %d skipped, %d failed.'
2764 2828 % (self._result.testsRun, skipped + ignored, failed)
2765 2829 )
2766 2830 if failed:
2767 2831 self.stream.writeln(
2768 2832 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2769 2833 )
2770 2834 if self._runner.options.time:
2771 2835 self.printtimes(self._result.times)
2772 2836
2773 2837 if self._runner.options.exceptions:
2774 2838 exceptions = aggregateexceptions(
2775 2839 os.path.join(self._runner._outputdir, b'exceptions')
2776 2840 )
2777 2841
2778 2842 self.stream.writeln('Exceptions Report:')
2779 2843 self.stream.writeln(
2780 2844 '%d total from %d frames'
2781 2845 % (exceptions['total'], len(exceptions['exceptioncounts']))
2782 2846 )
2783 2847 combined = exceptions['combined']
2784 2848 for key in sorted(combined, key=combined.get, reverse=True):
2785 2849 frame, line, exc = key
2786 2850 totalcount, testcount, leastcount, leasttest = combined[key]
2787 2851
2788 2852 self.stream.writeln(
2789 2853 '%d (%d tests)\t%s: %s (%s - %d total)'
2790 2854 % (
2791 2855 totalcount,
2792 2856 testcount,
2793 2857 frame,
2794 2858 exc,
2795 2859 leasttest,
2796 2860 leastcount,
2797 2861 )
2798 2862 )
2799 2863
2800 2864 self.stream.flush()
2801 2865
2802 2866 return self._result
2803 2867
2804 2868 def _bisecttests(self, tests):
2805 2869 bisectcmd = ['hg', 'bisect']
2806 2870 bisectrepo = self._runner.options.bisect_repo
2807 2871 if bisectrepo:
2808 2872 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2809 2873
2810 2874 def pread(args):
2811 2875 env = os.environ.copy()
2812 2876 env['HGPLAIN'] = '1'
2813 2877 p = subprocess.Popen(
2814 2878 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2815 2879 )
2816 2880 data = p.stdout.read()
2817 2881 p.wait()
2818 2882 return data
2819 2883
2820 2884 for test in tests:
2821 2885 pread(bisectcmd + ['--reset']),
2822 2886 pread(bisectcmd + ['--bad', '.'])
2823 2887 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2824 2888 # TODO: we probably need to forward more options
2825 2889 # that alter hg's behavior inside the tests.
2826 2890 opts = ''
2827 2891 withhg = self._runner.options.with_hg
2828 2892 if withhg:
2829 2893 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2830 2894 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2831 2895 data = pread(bisectcmd + ['--command', rtc])
2832 2896 m = re.search(
2833 2897 (
2834 2898 br'\nThe first (?P<goodbad>bad|good) revision '
2835 2899 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2836 2900 br'summary: +(?P<summary>[^\n]+)\n'
2837 2901 ),
2838 2902 data,
2839 2903 (re.MULTILINE | re.DOTALL),
2840 2904 )
2841 2905 if m is None:
2842 2906 self.stream.writeln(
2843 2907 'Failed to identify failure point for %s' % test
2844 2908 )
2845 2909 continue
2846 2910 dat = m.groupdict()
2847 2911 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2848 2912 self.stream.writeln(
2849 2913 '%s %s by %s (%s)'
2850 2914 % (
2851 2915 test,
2852 2916 verb,
2853 2917 dat['node'].decode('ascii'),
2854 2918 dat['summary'].decode('utf8', 'ignore'),
2855 2919 )
2856 2920 )
2857 2921
2858 2922 def printtimes(self, times):
2859 2923 # iolock held by run
2860 2924 self.stream.writeln('# Producing time report')
2861 2925 times.sort(key=lambda t: (t[3]))
2862 2926 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2863 2927 self.stream.writeln(
2864 2928 '%-7s %-7s %-7s %-7s %-7s %s'
2865 2929 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2866 2930 )
2867 2931 for tdata in times:
2868 2932 test = tdata[0]
2869 2933 cuser, csys, real, start, end = tdata[1:6]
2870 2934 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2871 2935
2872 2936 @staticmethod
2873 2937 def _writexunit(result, outf):
2874 2938 # See http://llg.cubic.org/docs/junit/ for a reference.
2875 2939 timesd = {t[0]: t[3] for t in result.times}
2876 2940 doc = minidom.Document()
2877 2941 s = doc.createElement('testsuite')
2878 2942 s.setAttribute('errors', "0") # TODO
2879 2943 s.setAttribute('failures', str(len(result.failures)))
2880 2944 s.setAttribute('name', 'run-tests')
2881 2945 s.setAttribute(
2882 2946 'skipped', str(len(result.skipped) + len(result.ignored))
2883 2947 )
2884 2948 s.setAttribute('tests', str(result.testsRun))
2885 2949 doc.appendChild(s)
2886 2950 for tc in result.successes:
2887 2951 t = doc.createElement('testcase')
2888 2952 t.setAttribute('name', tc.name)
2889 2953 tctime = timesd.get(tc.name)
2890 2954 if tctime is not None:
2891 2955 t.setAttribute('time', '%.3f' % tctime)
2892 2956 s.appendChild(t)
2893 2957 for tc, err in sorted(result.faildata.items()):
2894 2958 t = doc.createElement('testcase')
2895 2959 t.setAttribute('name', tc)
2896 2960 tctime = timesd.get(tc)
2897 2961 if tctime is not None:
2898 2962 t.setAttribute('time', '%.3f' % tctime)
2899 2963 # createCDATASection expects a unicode or it will
2900 2964 # convert using default conversion rules, which will
2901 2965 # fail if string isn't ASCII.
2902 2966 err = cdatasafe(err).decode('utf-8', 'replace')
2903 2967 cd = doc.createCDATASection(err)
2904 2968 # Use 'failure' here instead of 'error' to match errors = 0,
2905 2969 # failures = len(result.failures) in the testsuite element.
2906 2970 failelem = doc.createElement('failure')
2907 2971 failelem.setAttribute('message', 'output changed')
2908 2972 failelem.setAttribute('type', 'output-mismatch')
2909 2973 failelem.appendChild(cd)
2910 2974 t.appendChild(failelem)
2911 2975 s.appendChild(t)
2912 2976 for tc, message in result.skipped:
2913 2977 # According to the schema, 'skipped' has no attributes. So store
2914 2978 # the skip message as a text node instead.
2915 2979 t = doc.createElement('testcase')
2916 2980 t.setAttribute('name', tc.name)
2917 2981 binmessage = message.encode('utf-8')
2918 2982 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2919 2983 cd = doc.createCDATASection(message)
2920 2984 skipelem = doc.createElement('skipped')
2921 2985 skipelem.appendChild(cd)
2922 2986 t.appendChild(skipelem)
2923 2987 s.appendChild(t)
2924 2988 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2925 2989
2926 2990 @staticmethod
2927 2991 def _writejson(result, outf):
2928 2992 timesd = {}
2929 2993 for tdata in result.times:
2930 2994 test = tdata[0]
2931 2995 timesd[test] = tdata[1:]
2932 2996
2933 2997 outcome = {}
2934 2998 groups = [
2935 2999 ('success', ((tc, None) for tc in result.successes)),
2936 3000 ('failure', result.failures),
2937 3001 ('skip', result.skipped),
2938 3002 ]
2939 3003 for res, testcases in groups:
2940 3004 for tc, __ in testcases:
2941 3005 if tc.name in timesd:
2942 3006 diff = result.faildata.get(tc.name, b'')
2943 3007 try:
2944 3008 diff = diff.decode('unicode_escape')
2945 3009 except UnicodeDecodeError as e:
2946 3010 diff = '%r decoding diff, sorry' % e
2947 3011 tres = {
2948 3012 'result': res,
2949 3013 'time': ('%0.3f' % timesd[tc.name][2]),
2950 3014 'cuser': ('%0.3f' % timesd[tc.name][0]),
2951 3015 'csys': ('%0.3f' % timesd[tc.name][1]),
2952 3016 'start': ('%0.3f' % timesd[tc.name][3]),
2953 3017 'end': ('%0.3f' % timesd[tc.name][4]),
2954 3018 'diff': diff,
2955 3019 }
2956 3020 else:
2957 3021 # blacklisted test
2958 3022 tres = {'result': res}
2959 3023
2960 3024 outcome[tc.name] = tres
2961 3025 jsonout = json.dumps(
2962 3026 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2963 3027 )
2964 3028 outf.writelines(("testreport =", jsonout))
2965 3029
2966 3030
2967 3031 def sorttests(testdescs, previoustimes, shuffle=False):
2968 3032 """Do an in-place sort of tests."""
2969 3033 if shuffle:
2970 3034 random.shuffle(testdescs)
2971 3035 return
2972 3036
2973 3037 if previoustimes:
2974 3038
2975 3039 def sortkey(f):
2976 3040 f = f['path']
2977 3041 if f in previoustimes:
2978 3042 # Use most recent time as estimate
2979 3043 return -(previoustimes[f][-1])
2980 3044 else:
2981 3045 # Default to a rather arbitrary value of 1 second for new tests
2982 3046 return -1.0
2983 3047
2984 3048 else:
2985 3049 # keywords for slow tests
2986 3050 slow = {
2987 3051 b'svn': 10,
2988 3052 b'cvs': 10,
2989 3053 b'hghave': 10,
2990 3054 b'largefiles-update': 10,
2991 3055 b'run-tests': 10,
2992 3056 b'corruption': 10,
2993 3057 b'race': 10,
2994 3058 b'i18n': 10,
2995 3059 b'check': 100,
2996 3060 b'gendoc': 100,
2997 3061 b'contrib-perf': 200,
2998 3062 b'merge-combination': 100,
2999 3063 }
3000 3064 perf = {}
3001 3065
3002 3066 def sortkey(f):
3003 3067 # run largest tests first, as they tend to take the longest
3004 3068 f = f['path']
3005 3069 try:
3006 3070 return perf[f]
3007 3071 except KeyError:
3008 3072 try:
3009 3073 val = -os.stat(f).st_size
3010 3074 except FileNotFoundError:
3011 3075 perf[f] = -1e9 # file does not exist, tell early
3012 3076 return -1e9
3013 3077 for kw, mul in slow.items():
3014 3078 if kw in f:
3015 3079 val *= mul
3016 3080 if f.endswith(b'.py'):
3017 3081 val /= 10.0
3018 3082 perf[f] = val / 1000.0
3019 3083 return perf[f]
3020 3084
3021 3085 testdescs.sort(key=sortkey)
3022 3086
3023 3087
3024 3088 class TestRunner:
3025 3089 """Holds context for executing tests.
3026 3090
3027 3091 Tests rely on a lot of state. This object holds it for them.
3028 3092 """
3029 3093
3030 3094 # Programs required to run tests.
3031 3095 REQUIREDTOOLS = [
3032 3096 b'diff',
3033 3097 b'grep',
3034 3098 b'unzip',
3035 3099 b'gunzip',
3036 3100 b'bunzip2',
3037 3101 b'sed',
3038 3102 ]
3039 3103
3040 3104 # Maps file extensions to test class.
3041 3105 TESTTYPES = [
3042 3106 (b'.py', PythonTest),
3043 3107 (b'.t', TTest),
3044 3108 ]
3045 3109
3046 3110 def __init__(self):
3047 3111 self.options = None
3048 3112 self._hgroot = None
3049 3113 self._testdir = None
3050 3114 self._outputdir = None
3051 3115 self._hgtmp = None
3052 3116 self._installdir = None
3053 3117 self._bindir = None
3054 3118 # a place for run-tests.py to generate executable it needs
3055 3119 self._custom_bin_dir = None
3056 3120 self._pythondir = None
3057 3121 # True if we had to infer the pythondir from --with-hg
3058 3122 self._pythondir_inferred = False
3059 3123 self._coveragefile = None
3060 3124 self._createdfiles = []
3061 3125 self._hgcommand = None
3062 3126 self._hgpath = None
3063 3127 self._portoffset = 0
3064 3128 self._ports = {}
3065 3129
3066 3130 def run(self, args, parser=None):
3067 3131 """Run the test suite."""
3068 3132 oldmask = os.umask(0o22)
3069 3133 try:
3070 3134 parser = parser or getparser()
3071 3135 options = parseargs(args, parser)
3072 3136 tests = [_sys2bytes(a) for a in options.tests]
3073 3137 if options.test_list is not None:
3074 3138 for listfile in options.test_list:
3075 3139 with open(listfile, 'rb') as f:
3076 3140 tests.extend(t for t in f.read().splitlines() if t)
3077 3141 self.options = options
3078 3142
3079 3143 self._checktools()
3080 3144 testdescs = self.findtests(tests)
3081 3145 if options.profile_runner:
3082 3146 import statprof
3083 3147
3084 3148 statprof.start()
3085 3149 result = self._run(testdescs)
3086 3150 if options.profile_runner:
3087 3151 statprof.stop()
3088 3152 statprof.display()
3089 3153 return result
3090 3154
3091 3155 finally:
3092 3156 os.umask(oldmask)
3093 3157
3094 3158 def _run(self, testdescs):
3095 3159 testdir = getcwdb()
3096 3160 # assume all tests in same folder for now
3097 3161 if testdescs:
3098 3162 pathname = os.path.dirname(testdescs[0]['path'])
3099 3163 if pathname:
3100 3164 testdir = os.path.join(testdir, pathname)
3101 3165 self._testdir = osenvironb[b'TESTDIR'] = testdir
3102 3166 osenvironb[b'TESTDIR_FORWARD_SLASH'] = osenvironb[b'TESTDIR'].replace(
3103 3167 os.sep.encode('ascii'), b'/'
3104 3168 )
3105 3169
3106 3170 if self.options.outputdir:
3107 3171 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3108 3172 else:
3109 3173 self._outputdir = getcwdb()
3110 3174 if testdescs and pathname:
3111 3175 self._outputdir = os.path.join(self._outputdir, pathname)
3112 3176 previoustimes = {}
3113 3177 if self.options.order_by_runtime:
3114 3178 previoustimes = dict(loadtimes(self._outputdir))
3115 3179 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3116 3180
3117 3181 if 'PYTHONHASHSEED' not in os.environ:
3118 3182 # use a random python hash seed all the time
3119 3183 # we do the randomness ourself to know what seed is used
3120 3184 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3121 3185
3122 3186 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3123 3187 # by default, causing thrashing on high-cpu-count systems.
3124 3188 # Setting its limit to 3 during tests should still let us uncover
3125 3189 # multi-threading bugs while keeping the thrashing reasonable.
3126 3190 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3127 3191
3128 3192 if self.options.tmpdir:
3129 3193 self.options.keep_tmpdir = True
3130 3194 tmpdir = _sys2bytes(self.options.tmpdir)
3131 3195 if os.path.exists(tmpdir):
3132 3196 # Meaning of tmpdir has changed since 1.3: we used to create
3133 3197 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3134 3198 # tmpdir already exists.
3135 3199 print("error: temp dir %r already exists" % tmpdir)
3136 3200 return 1
3137 3201
3138 3202 os.makedirs(tmpdir)
3139 3203 else:
3140 3204 d = None
3141 3205 if WINDOWS:
3142 3206 # without this, we get the default temp dir location, but
3143 3207 # in all lowercase, which causes troubles with paths (issue3490)
3144 3208 d = osenvironb.get(b'TMP', None)
3145 3209 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3146 3210
3147 3211 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3148 3212
3149 3213 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3150 3214 os.makedirs(self._custom_bin_dir)
3151 3215
3152 3216 # detect and enforce an alternative way to specify rust extension usage
3153 3217 if (
3154 3218 not (self.options.pure or self.options.rust or self.options.no_rust)
3155 3219 and os.environ.get("HGWITHRUSTEXT") == "cpython"
3156 3220 ):
3157 3221 self.options.rust = True
3158 3222
3159 3223 if self.options.with_hg:
3160 3224 self._installdir = None
3161 3225 whg = self.options.with_hg
3162 3226 self._bindir = os.path.dirname(os.path.realpath(whg))
3163 3227 assert isinstance(self._bindir, bytes)
3164 3228 self._hgcommand = os.path.basename(whg)
3165 3229
3166 3230 normbin = os.path.normpath(os.path.abspath(whg))
3167 3231 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3168 3232
3169 3233 # Other Python scripts in the test harness need to
3170 3234 # `import mercurial`. If `hg` is a Python script, we assume
3171 3235 # the Mercurial modules are relative to its path and tell the tests
3172 3236 # to load Python modules from its directory.
3173 3237 with open(whg, 'rb') as fh:
3174 3238 initial = fh.read(1024)
3175 3239
3176 3240 if re.match(b'#!.*python', initial):
3177 3241 self._pythondir = self._bindir
3178 3242 # If it looks like our in-repo Rust binary, use the source root.
3179 3243 # This is a bit hacky. But rhg is still not supported outside the
3180 3244 # source directory. So until it is, do the simple thing.
3181 3245 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3182 3246 self._pythondir = os.path.dirname(self._testdir)
3183 3247 # Fall back to the legacy behavior.
3184 3248 else:
3185 3249 self._pythondir = self._bindir
3186 3250 self._pythondir_inferred = True
3187 3251
3188 3252 else:
3189 3253 self._installdir = os.path.join(self._hgtmp, b"install")
3190 3254 self._bindir = os.path.join(self._installdir, b"bin")
3191 3255 self._hgcommand = b'hg'
3192 3256 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3193 3257
3194 3258 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3195 3259 # a python script and feed it to python.exe. Legacy stdio is force
3196 3260 # enabled by hg.exe, and this is a more realistic way to launch hg
3197 3261 # anyway.
3198 3262 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3199 3263 self._hgcommand += b'.exe'
3200 3264
3201 3265 real_hg = os.path.join(self._bindir, self._hgcommand)
3202 3266 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3203 3267 # set CHGHG, then replace "hg" command by "chg"
3204 3268 chgbindir = self._bindir
3205 3269 if self.options.chg or self.options.with_chg:
3206 3270 osenvironb[b'CHG_INSTALLED_AS_HG'] = b'1'
3207 3271 osenvironb[b'CHGHG'] = real_hg
3208 3272 else:
3209 3273 # drop flag for hghave
3210 3274 osenvironb.pop(b'CHG_INSTALLED_AS_HG', None)
3211 3275 if self.options.chg:
3212 3276 self._hgcommand = b'chg'
3213 3277 elif self.options.with_chg:
3214 3278 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3215 3279 self._hgcommand = os.path.basename(self.options.with_chg)
3216 3280
3217 3281 # configure fallback and replace "hg" command by "rhg"
3218 3282 rhgbindir = self._bindir
3219 3283 if self.options.rhg or self.options.with_rhg:
3220 3284 # Affects hghave.py
3221 3285 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3222 3286 # Affects configuration. Alternatives would be setting configuration through
3223 3287 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3224 3288 # `--config` but that disrupts tests that print command lines and check expected
3225 3289 # output.
3226 3290 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3227 3291 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3228 3292 else:
3229 3293 # drop flag for hghave
3230 3294 osenvironb.pop(b'RHG_INSTALLED_AS_HG', None)
3231 3295 if self.options.rhg:
3232 3296 self._hgcommand = b'rhg'
3233 3297 elif self.options.with_rhg:
3234 3298 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3235 3299 self._hgcommand = os.path.basename(self.options.with_rhg)
3236 3300
3237 3301 if self.options.pyoxidized:
3238 3302 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
3239 3303 reporootdir = os.path.dirname(testdir)
3240 3304 # XXX we should ideally install stuff instead of using the local build
3241 3305
3242 3306 exe = b'hg'
3243 3307 triple = b''
3244 3308
3245 3309 if WINDOWS:
3246 3310 triple = b'x86_64-pc-windows-msvc'
3247 3311 exe = b'hg.exe'
3248 3312 elif MACOS:
3249 3313 # TODO: support Apple silicon too
3250 3314 triple = b'x86_64-apple-darwin'
3251 3315
3252 3316 bin_path = b'build/pyoxidizer/%s/release/app/%s' % (triple, exe)
3253 3317 full_path = os.path.join(reporootdir, bin_path)
3254 3318 self._hgcommand = full_path
3255 3319 # Affects hghave.py
3256 3320 osenvironb[b'PYOXIDIZED_INSTALLED_AS_HG'] = b'1'
3257 3321 else:
3258 3322 osenvironb.pop(b'PYOXIDIZED_INSTALLED_AS_HG', None)
3259 3323
3260 3324 osenvironb[b"BINDIR"] = self._bindir
3261 3325 osenvironb[b"PYTHON"] = PYTHON
3262 3326
3263 3327 fileb = _sys2bytes(__file__)
3264 3328 runtestdir = os.path.abspath(os.path.dirname(fileb))
3265 3329 osenvironb[b'RUNTESTDIR'] = runtestdir
3266 3330 osenvironb[b'RUNTESTDIR_FORWARD_SLASH'] = runtestdir.replace(
3267 3331 os.sep.encode('ascii'), b'/'
3268 3332 )
3269 3333 sepb = _sys2bytes(os.pathsep)
3270 3334 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3271 3335 if os.path.islink(__file__):
3272 3336 # test helper will likely be at the end of the symlink
3273 3337 realfile = os.path.realpath(fileb)
3274 3338 realdir = os.path.abspath(os.path.dirname(realfile))
3275 3339 path.insert(2, realdir)
3276 3340 if chgbindir != self._bindir:
3277 3341 path.insert(1, chgbindir)
3278 3342 if rhgbindir != self._bindir:
3279 3343 path.insert(1, rhgbindir)
3280 3344 if self._testdir != runtestdir:
3281 3345 path = [self._testdir] + path
3282 3346 path = [self._custom_bin_dir] + path
3283 3347 osenvironb[b"PATH"] = sepb.join(path)
3284 3348
3285 3349 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3286 3350 # can run .../tests/run-tests.py test-foo where test-foo
3287 3351 # adds an extension to HGRC. Also include run-test.py directory to
3288 3352 # import modules like heredoctest.
3289 3353 pypath = [self._pythondir, self._testdir, runtestdir]
3290 3354
3291 3355 # Setting PYTHONPATH with an activated venv causes the modules installed
3292 3356 # in it to be ignored. Therefore, include the related paths in sys.path
3293 3357 # in PYTHONPATH.
3294 3358 virtual_env = osenvironb.get(b"VIRTUAL_ENV")
3295 3359 if virtual_env:
3296 3360 virtual_env = os.path.join(virtual_env, b'')
3297 3361 for p in sys.path:
3298 3362 p = _sys2bytes(p)
3299 3363 if p.startswith(virtual_env):
3300 3364 pypath.append(p)
3301 3365
3302 3366 # We have to augment PYTHONPATH, rather than simply replacing
3303 3367 # it, in case external libraries are only available via current
3304 3368 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3305 3369 # are in /opt/subversion.)
3306 3370 oldpypath = osenvironb.get(IMPL_PATH)
3307 3371 if oldpypath:
3308 3372 pypath.append(oldpypath)
3309 3373 osenvironb[IMPL_PATH] = sepb.join(pypath)
3310 3374
3311 3375 if self.options.pure:
3312 3376 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3313 3377 os.environ["HGMODULEPOLICY"] = "py"
3314 3378 if self.options.rust:
3315 3379 os.environ["HGMODULEPOLICY"] = "rust+c"
3316 3380 if self.options.no_rust:
3317 3381 current_policy = os.environ.get("HGMODULEPOLICY", "")
3318 3382 if current_policy.startswith("rust+"):
3319 3383 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3320 3384 os.environ.pop("HGWITHRUSTEXT", None)
3321 3385
3322 3386 if self.options.allow_slow_tests:
3323 3387 os.environ["HGTEST_SLOW"] = "slow"
3324 3388 elif 'HGTEST_SLOW' in os.environ:
3325 3389 del os.environ['HGTEST_SLOW']
3326 3390
3327 3391 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3328 3392
3329 3393 if self.options.exceptions:
3330 3394 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3331 3395 try:
3332 3396 os.makedirs(exceptionsdir)
3333 3397 except FileExistsError:
3334 3398 pass
3335 3399
3336 3400 # Remove all existing exception reports.
3337 3401 for f in os.listdir(exceptionsdir):
3338 3402 os.unlink(os.path.join(exceptionsdir, f))
3339 3403
3340 3404 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3341 3405 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3342 3406 self.options.extra_config_opt.append(
3343 3407 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3344 3408 )
3345 3409
3346 3410 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3347 3411 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3348 3412 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3349 3413 vlog("# Using PATH", os.environ["PATH"])
3350 3414 vlog(
3351 3415 "# Using",
3352 3416 _bytes2sys(IMPL_PATH),
3353 3417 _bytes2sys(osenvironb[IMPL_PATH]),
3354 3418 )
3355 3419 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3356 3420
3357 3421 try:
3358 3422 return self._runtests(testdescs) or 0
3359 3423 finally:
3360 3424 time.sleep(0.1)
3361 3425 self._cleanup()
3362 3426
3363 3427 def findtests(self, args):
3364 3428 """Finds possible test files from arguments.
3365 3429
3366 3430 If you wish to inject custom tests into the test harness, this would
3367 3431 be a good function to monkeypatch or override in a derived class.
3368 3432 """
3369 3433 if not args:
3370 3434 if self.options.changed:
3371 3435 proc = Popen4(
3372 3436 b'hg st --rev "%s" -man0 .'
3373 3437 % _sys2bytes(self.options.changed),
3374 3438 None,
3375 3439 0,
3376 3440 )
3377 3441 stdout, stderr = proc.communicate()
3378 3442 args = stdout.strip(b'\0').split(b'\0')
3379 3443 else:
3380 3444 args = os.listdir(b'.')
3381 3445
3382 3446 expanded_args = []
3383 3447 for arg in args:
3384 3448 if os.path.isdir(arg):
3385 3449 if not arg.endswith(b'/'):
3386 3450 arg += b'/'
3387 3451 expanded_args.extend([arg + a for a in os.listdir(arg)])
3388 3452 else:
3389 3453 expanded_args.append(arg)
3390 3454 args = expanded_args
3391 3455
3392 3456 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3393 3457 tests = []
3394 3458 for t in args:
3395 3459 case = []
3396 3460
3397 3461 if not (
3398 3462 os.path.basename(t).startswith(b'test-')
3399 3463 and (t.endswith(b'.py') or t.endswith(b'.t'))
3400 3464 ):
3401 3465 m = testcasepattern.match(os.path.basename(t))
3402 3466 if m is not None:
3403 3467 t_basename, casestr = m.groups()
3404 3468 t = os.path.join(os.path.dirname(t), t_basename)
3405 3469 if casestr:
3406 3470 case = casestr.split(b'#')
3407 3471 else:
3408 3472 continue
3409 3473
3410 3474 if t.endswith(b'.t'):
3411 3475 # .t file may contain multiple test cases
3412 3476 casedimensions = parsettestcases(t)
3413 3477 if casedimensions:
3414 3478 cases = []
3415 3479
3416 3480 def addcases(case, casedimensions):
3417 3481 if not casedimensions:
3418 3482 cases.append(case)
3419 3483 else:
3420 3484 for c in casedimensions[0]:
3421 3485 addcases(case + [c], casedimensions[1:])
3422 3486
3423 3487 addcases([], casedimensions)
3424 3488 if case and case in cases:
3425 3489 cases = [case]
3426 3490 elif case:
3427 3491 # Ignore invalid cases
3428 3492 cases = []
3429 3493 else:
3430 3494 pass
3431 3495 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3432 3496 else:
3433 3497 tests.append({'path': t})
3434 3498 else:
3435 3499 tests.append({'path': t})
3436 3500
3437 3501 if self.options.retest:
3438 3502 retest_args = []
3439 3503 for test in tests:
3440 3504 errpath = self._geterrpath(test)
3441 3505 if os.path.exists(errpath):
3442 3506 retest_args.append(test)
3443 3507 tests = retest_args
3444 3508 return tests
3445 3509
3446 3510 def _runtests(self, testdescs):
3447 3511 def _reloadtest(test, i):
3448 3512 # convert a test back to its description dict
3449 3513 desc = {'path': test.path}
3450 3514 case = getattr(test, '_case', [])
3451 3515 if case:
3452 3516 desc['case'] = case
3453 3517 return self._gettest(desc, i)
3454 3518
3455 3519 try:
3456 3520 if self.options.restart:
3457 3521 orig = list(testdescs)
3458 3522 while testdescs:
3459 3523 desc = testdescs[0]
3460 3524 errpath = self._geterrpath(desc)
3461 3525 if os.path.exists(errpath):
3462 3526 break
3463 3527 testdescs.pop(0)
3464 3528 if not testdescs:
3465 3529 print("running all tests")
3466 3530 testdescs = orig
3467 3531
3468 3532 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3469 3533 num_tests = len(tests) * self.options.runs_per_test
3470 3534
3471 3535 jobs = min(num_tests, self.options.jobs)
3472 3536
3473 3537 failed = False
3474 3538 kws = self.options.keywords
3475 3539 if kws is not None:
3476 3540 kws = kws.encode('utf-8')
3477 3541
3478 3542 suite = TestSuite(
3479 3543 self._testdir,
3480 3544 jobs=jobs,
3481 3545 whitelist=self.options.whitelisted,
3482 3546 blacklist=self.options.blacklist,
3483 3547 keywords=kws,
3484 3548 loop=self.options.loop,
3485 3549 runs_per_test=self.options.runs_per_test,
3486 3550 showchannels=self.options.showchannels,
3487 3551 tests=tests,
3488 3552 loadtest=_reloadtest,
3489 3553 )
3490 3554 verbosity = 1
3491 3555 if self.options.list_tests:
3492 3556 verbosity = 0
3493 3557 elif self.options.verbose:
3494 3558 verbosity = 2
3495 3559 runner = TextTestRunner(self, verbosity=verbosity)
3496 3560
3497 3561 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3498 3562 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3499 3563
3500 3564 if self.options.list_tests:
3501 3565 result = runner.listtests(suite)
3502 3566 else:
3503 3567 install_start_time = time.monotonic()
3504 3568 self._usecorrectpython()
3505 3569 if self._installdir:
3506 3570 self._installhg()
3507 3571 self._checkhglib("Testing")
3508 3572 if self.options.chg:
3509 3573 assert self._installdir
3510 3574 self._installchg()
3511 3575 if self.options.rhg:
3512 3576 assert self._installdir
3513 3577 self._installrhg()
3514 3578 elif self.options.pyoxidized:
3515 3579 self._build_pyoxidized()
3516 3580 self._use_correct_mercurial()
3517 3581 install_end_time = time.monotonic()
3518 3582 if self._installdir:
3519 3583 msg = 'installed Mercurial in %.2f seconds'
3520 3584 msg %= install_end_time - install_start_time
3521 3585 log(msg)
3522 3586
3523 3587 log(
3524 3588 'running %d tests using %d parallel processes'
3525 3589 % (num_tests, jobs)
3526 3590 )
3527 3591
3528 3592 result = runner.run(suite)
3529 3593
3530 3594 if result.failures or result.errors:
3531 3595 failed = True
3532 3596
3533 3597 result.onEnd()
3534 3598
3535 3599 if self.options.anycoverage:
3536 3600 self._outputcoverage()
3537 3601 except KeyboardInterrupt:
3538 3602 failed = True
3539 3603 print("\ninterrupted!")
3540 3604
3541 3605 if failed:
3542 3606 return 1
3543 3607
3544 3608 def _geterrpath(self, test):
3545 3609 # test['path'] is a relative path
3546 3610 if 'case' in test:
3547 3611 # for multiple dimensions test cases
3548 3612 casestr = b'#'.join(test['case'])
3549 3613 errpath = b'%s#%s.err' % (test['path'], casestr)
3550 3614 else:
3551 3615 errpath = b'%s.err' % test['path']
3552 3616 if self.options.outputdir:
3553 3617 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3554 3618 errpath = os.path.join(self._outputdir, errpath)
3555 3619 return errpath
3556 3620
3557 3621 def _getport(self, count):
3558 3622 port = self._ports.get(count) # do we have a cached entry?
3559 3623 if port is None:
3560 3624 portneeded = 3
3561 3625 # above 100 tries we just give up and let test reports failure
3562 3626 for tries in range(100):
3563 3627 allfree = True
3564 3628 port = self.options.port + self._portoffset
3565 3629 for idx in range(portneeded):
3566 3630 if not checkportisavailable(port + idx):
3567 3631 allfree = False
3568 3632 break
3569 3633 self._portoffset += portneeded
3570 3634 if allfree:
3571 3635 break
3572 3636 self._ports[count] = port
3573 3637 return port
3574 3638
3575 3639 def _gettest(self, testdesc, count):
3576 3640 """Obtain a Test by looking at its filename.
3577 3641
3578 3642 Returns a Test instance. The Test may not be runnable if it doesn't
3579 3643 map to a known type.
3580 3644 """
3581 3645 path = testdesc['path']
3582 3646 lctest = path.lower()
3583 3647 testcls = Test
3584 3648
3585 3649 for ext, cls in self.TESTTYPES:
3586 3650 if lctest.endswith(ext):
3587 3651 testcls = cls
3588 3652 break
3589 3653
3590 3654 refpath = os.path.join(getcwdb(), path)
3591 3655 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3592 3656
3593 3657 # extra keyword parameters. 'case' is used by .t tests
3594 3658 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3595 3659
3596 3660 t = testcls(
3597 3661 refpath,
3598 3662 self._outputdir,
3599 3663 tmpdir,
3600 3664 keeptmpdir=self.options.keep_tmpdir,
3601 3665 debug=self.options.debug,
3602 3666 first=self.options.first,
3603 3667 timeout=self.options.timeout,
3604 3668 startport=self._getport(count),
3605 3669 extraconfigopts=self.options.extra_config_opt,
3606 3670 shell=self.options.shell,
3607 3671 hgcommand=self._hgcommand,
3608 3672 usechg=bool(self.options.with_chg or self.options.chg),
3609 3673 chgdebug=self.options.chg_debug,
3610 3674 useipv6=useipv6,
3611 3675 **kwds
3612 3676 )
3613 3677 t.should_reload = True
3614 3678 return t
3615 3679
3616 3680 def _cleanup(self):
3617 3681 """Clean up state from this test invocation."""
3618 3682 if self.options.keep_tmpdir:
3619 3683 return
3620 3684
3621 3685 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3622 3686 shutil.rmtree(self._hgtmp, True)
3623 3687 for f in self._createdfiles:
3624 3688 try:
3625 3689 os.remove(f)
3626 3690 except OSError:
3627 3691 pass
3628 3692
3629 3693 def _usecorrectpython(self):
3630 3694 """Configure the environment to use the appropriate Python in tests."""
3631 3695 # Tests must use the same interpreter as us or bad things will happen.
3632 3696 if WINDOWS:
3633 3697 pyexe_names = [b'python', b'python3', b'python.exe']
3634 3698 else:
3635 3699 pyexe_names = [b'python', b'python3']
3636 3700
3637 3701 # os.symlink() is a thing with py3 on Windows, but it requires
3638 3702 # Administrator rights.
3639 3703 if not WINDOWS and getattr(os, 'symlink', None):
3640 3704 msg = "# Making python executable in test path a symlink to '%s'"
3641 3705 msg %= sysexecutable
3642 3706 vlog(msg)
3643 3707 for pyexename in pyexe_names:
3644 3708 mypython = os.path.join(self._custom_bin_dir, pyexename)
3645 3709 try:
3646 3710 if os.readlink(mypython) == sysexecutable:
3647 3711 continue
3648 3712 os.unlink(mypython)
3649 3713 except FileNotFoundError:
3650 3714 pass
3651 3715 if self._findprogram(pyexename) != sysexecutable:
3652 3716 try:
3653 3717 os.symlink(sysexecutable, mypython)
3654 3718 self._createdfiles.append(mypython)
3655 3719 except FileExistsError:
3656 3720 # child processes may race, which is harmless
3657 3721 pass
3658 3722 elif WINDOWS and not os.getenv('MSYSTEM'):
3659 3723 raise AssertionError('cannot run test on Windows without MSYSTEM')
3660 3724 else:
3661 3725 # Generate explicit file instead of symlink
3662 3726 #
3663 3727 # This is especially important as Windows doesn't have
3664 3728 # `python3.exe`, and MSYS cannot understand the reparse point with
3665 3729 # that name provided by Microsoft. Create a simple script on PATH
3666 3730 # with that name that delegates to the py3 launcher so the shebang
3667 3731 # lines work.
3668 3732 esc_executable = _sys2bytes(shellquote(sysexecutable))
3669 3733 for pyexename in pyexe_names:
3670 3734 stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
3671 3735 with open(stub_exec_path, 'wb') as f:
3672 3736 f.write(b'#!/bin/sh\n')
3673 3737 f.write(b'%s "$@"\n' % esc_executable)
3674 3738
3675 3739 if WINDOWS:
3676 3740 # adjust the path to make sur the main python finds it own dll
3677 3741 path = os.environ['PATH'].split(os.pathsep)
3678 3742 main_exec_dir = os.path.dirname(sysexecutable)
3679 3743 extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
3680 3744
3681 3745 # Binaries installed by pip into the user area like pylint.exe may
3682 3746 # not be in PATH by default.
3683 3747 appdata = os.environ.get('APPDATA')
3684 3748 vi = sys.version_info
3685 3749 if appdata is not None:
3686 3750 python_dir = 'Python%d%d' % (vi[0], vi[1])
3687 3751 scripts_path = [appdata, 'Python', python_dir, 'Scripts']
3688 3752 scripts_dir = os.path.join(*scripts_path)
3689 3753 extra_paths.append(scripts_dir)
3690 3754
3691 3755 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3692 3756
3693 3757 def _use_correct_mercurial(self):
3694 3758 target_exec = os.path.join(self._custom_bin_dir, b'hg')
3695 3759 if self._hgcommand != b'hg':
3696 3760 # shutil.which only accept bytes from 3.8
3697 3761 real_exec = which(self._hgcommand)
3698 3762 if real_exec is None:
3699 3763 raise ValueError('could not find exec path for "%s"', real_exec)
3700 3764 if real_exec == target_exec:
3701 3765 # do not overwrite something with itself
3702 3766 return
3703 3767 if WINDOWS:
3704 3768 with open(target_exec, 'wb') as f:
3705 3769 f.write(b'#!/bin/sh\n')
3706 3770 escaped_exec = shellquote(_bytes2sys(real_exec))
3707 3771 f.write(b'%s "$@"\n' % _sys2bytes(escaped_exec))
3708 3772 else:
3709 3773 os.symlink(real_exec, target_exec)
3710 3774 self._createdfiles.append(target_exec)
3711 3775
3712 3776 def _installhg(self):
3713 3777 """Install hg into the test environment.
3714 3778
3715 3779 This will also configure hg with the appropriate testing settings.
3716 3780 """
3717 3781 vlog("# Performing temporary installation of HG")
3718 3782 installerrs = os.path.join(self._hgtmp, b"install.err")
3719 3783 compiler = ''
3720 3784 if self.options.compiler:
3721 3785 compiler = '--compiler ' + self.options.compiler
3722 3786 setup_opts = b""
3723 3787 if self.options.pure:
3724 3788 setup_opts = b"--pure"
3725 3789 elif self.options.rust:
3726 3790 setup_opts = b"--rust"
3727 3791 elif self.options.no_rust:
3728 3792 setup_opts = b"--no-rust"
3729 3793
3730 3794 # Run installer in hg root
3731 3795 compiler = _sys2bytes(compiler)
3732 3796 script = _sys2bytes(os.path.realpath(sys.argv[0]))
3733 3797 exe = _sys2bytes(sysexecutable)
3734 3798 hgroot = os.path.dirname(os.path.dirname(script))
3735 3799 self._hgroot = hgroot
3736 3800 os.chdir(hgroot)
3737 3801 nohome = b'--home=""'
3738 3802 if WINDOWS:
3739 3803 # The --home="" trick works only on OS where os.sep == '/'
3740 3804 # because of a distutils convert_path() fast-path. Avoid it at
3741 3805 # least on Windows for now, deal with .pydistutils.cfg bugs
3742 3806 # when they happen.
3743 3807 nohome = b''
3744 3808 cmd = (
3745 3809 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3746 3810 b' build %(compiler)s --build-base="%(base)s"'
3747 3811 b' install --force --prefix="%(prefix)s"'
3748 3812 b' --install-lib="%(libdir)s"'
3749 3813 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3750 3814 % {
3751 3815 b'exe': exe,
3752 3816 b'setup_opts': setup_opts,
3753 3817 b'compiler': compiler,
3754 3818 b'base': os.path.join(self._hgtmp, b"build"),
3755 3819 b'prefix': self._installdir,
3756 3820 b'libdir': self._pythondir,
3757 3821 b'bindir': self._bindir,
3758 3822 b'nohome': nohome,
3759 3823 b'logfile': installerrs,
3760 3824 }
3761 3825 )
3762 3826
3763 3827 # setuptools requires install directories to exist.
3764 3828 def makedirs(p):
3765 3829 try:
3766 3830 os.makedirs(p)
3767 3831 except FileExistsError:
3768 3832 pass
3769 3833
3770 3834 makedirs(self._pythondir)
3771 3835 makedirs(self._bindir)
3772 3836
3773 3837 vlog("# Running", cmd.decode("utf-8"))
3774 3838 if subprocess.call(_bytes2sys(cmd), shell=True, env=original_env) == 0:
3775 3839 if not self.options.verbose:
3776 3840 try:
3777 3841 os.remove(installerrs)
3778 3842 except FileNotFoundError:
3779 3843 pass
3780 3844 else:
3781 3845 with open(installerrs, 'rb') as f:
3782 3846 for line in f:
3783 3847 sys.stdout.buffer.write(line)
3784 3848 sys.exit(1)
3785 3849 os.chdir(self._testdir)
3786 3850
3787 3851 hgbat = os.path.join(self._bindir, b'hg.bat')
3788 3852 if os.path.isfile(hgbat):
3789 3853 # hg.bat expects to be put in bin/scripts while run-tests.py
3790 3854 # installation layout put it in bin/ directly. Fix it
3791 3855 with open(hgbat, 'rb') as f:
3792 3856 data = f.read()
3793 3857 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3794 3858 data = data.replace(
3795 3859 br'"%~dp0..\python" "%~dp0hg" %*',
3796 3860 b'"%~dp0python" "%~dp0hg" %*',
3797 3861 )
3798 3862 with open(hgbat, 'wb') as f:
3799 3863 f.write(data)
3800 3864 else:
3801 3865 print('WARNING: cannot fix hg.bat reference to python.exe')
3802 3866
3803 3867 if self.options.anycoverage:
3804 3868 custom = os.path.join(
3805 3869 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3806 3870 )
3807 3871 target = os.path.join(self._pythondir, b'sitecustomize.py')
3808 3872 vlog('# Installing coverage trigger to %s' % target)
3809 3873 shutil.copyfile(custom, target)
3810 3874 rc = os.path.join(self._testdir, b'.coveragerc')
3811 3875 vlog('# Installing coverage rc to %s' % rc)
3812 3876 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3813 3877 covdir = os.path.join(self._installdir, b'..', b'coverage')
3814 3878 try:
3815 3879 os.mkdir(covdir)
3816 3880 except FileExistsError:
3817 3881 pass
3818 3882
3819 3883 osenvironb[b'COVERAGE_DIR'] = covdir
3820 3884
3821 3885 def _checkhglib(self, verb):
3822 3886 """Ensure that the 'mercurial' package imported by python is
3823 3887 the one we expect it to be. If not, print a warning to stderr."""
3824 3888 if self._pythondir_inferred:
3825 3889 # The pythondir has been inferred from --with-hg flag.
3826 3890 # We cannot expect anything sensible here.
3827 3891 return
3828 3892 expecthg = os.path.join(self._pythondir, b'mercurial')
3829 3893 actualhg = self._gethgpath()
3830 3894 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3831 3895 sys.stderr.write(
3832 3896 'warning: %s with unexpected mercurial lib: %s\n'
3833 3897 ' (expected %s)\n' % (verb, actualhg, expecthg)
3834 3898 )
3835 3899
3836 3900 def _gethgpath(self):
3837 3901 """Return the path to the mercurial package that is actually found by
3838 3902 the current Python interpreter."""
3839 3903 if self._hgpath is not None:
3840 3904 return self._hgpath
3841 3905
3842 3906 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3843 3907 cmd = _bytes2sys(cmd % PYTHON)
3844 3908
3845 3909 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3846 3910 out, err = p.communicate()
3847 3911
3848 3912 self._hgpath = out.strip()
3849 3913
3850 3914 return self._hgpath
3851 3915
3852 3916 def _installchg(self):
3853 3917 """Install chg into the test environment"""
3854 3918 vlog('# Performing temporary installation of CHG')
3855 3919 assert os.path.dirname(self._bindir) == self._installdir
3856 3920 assert self._hgroot, 'must be called after _installhg()'
3857 3921 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3858 3922 b'make': b'make', # TODO: switch by option or environment?
3859 3923 b'prefix': self._installdir,
3860 3924 }
3861 3925 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3862 3926 vlog("# Running", cmd)
3863 3927 proc = subprocess.Popen(
3864 3928 cmd,
3865 3929 shell=True,
3866 3930 cwd=cwd,
3867 3931 stdin=subprocess.PIPE,
3868 3932 stdout=subprocess.PIPE,
3869 3933 stderr=subprocess.STDOUT,
3870 3934 )
3871 3935 out, _err = proc.communicate()
3872 3936 if proc.returncode != 0:
3873 3937 sys.stdout.buffer.write(out)
3874 3938 sys.exit(1)
3875 3939
3876 3940 def _installrhg(self):
3877 3941 """Install rhg into the test environment"""
3878 3942 vlog('# Performing temporary installation of rhg')
3879 3943 assert os.path.dirname(self._bindir) == self._installdir
3880 3944 assert self._hgroot, 'must be called after _installhg()'
3881 3945 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3882 3946 b'make': b'make', # TODO: switch by option or environment?
3883 3947 b'prefix': self._installdir,
3884 3948 }
3885 3949 cwd = self._hgroot
3886 3950 vlog("# Running", cmd)
3887 3951 proc = subprocess.Popen(
3888 3952 cmd,
3889 3953 shell=True,
3890 3954 cwd=cwd,
3891 3955 stdin=subprocess.PIPE,
3892 3956 stdout=subprocess.PIPE,
3893 3957 stderr=subprocess.STDOUT,
3894 3958 )
3895 3959 out, _err = proc.communicate()
3896 3960 if proc.returncode != 0:
3897 3961 sys.stdout.buffer.write(out)
3898 3962 sys.exit(1)
3899 3963
3900 3964 def _build_pyoxidized(self):
3901 3965 """build a pyoxidized version of mercurial into the test environment
3902 3966
3903 3967 Ideally this function would be `install_pyoxidier` and would both build
3904 3968 and install pyoxidier. However we are starting small to get pyoxidizer
3905 3969 build binary to testing quickly.
3906 3970 """
3907 3971 vlog('# build a pyoxidized version of Mercurial')
3908 3972 assert os.path.dirname(self._bindir) == self._installdir
3909 3973 assert self._hgroot, 'must be called after _installhg()'
3910 3974 target = b''
3911 3975 if WINDOWS:
3912 3976 target = b'windows'
3913 3977 elif MACOS:
3914 3978 target = b'macos'
3915 3979
3916 3980 cmd = b'"%(make)s" pyoxidizer-%(platform)s-tests' % {
3917 3981 b'make': b'make',
3918 3982 b'platform': target,
3919 3983 }
3920 3984 cwd = self._hgroot
3921 3985 vlog("# Running", cmd)
3922 3986 proc = subprocess.Popen(
3923 3987 _bytes2sys(cmd),
3924 3988 shell=True,
3925 3989 cwd=_bytes2sys(cwd),
3926 3990 stdin=subprocess.PIPE,
3927 3991 stdout=subprocess.PIPE,
3928 3992 stderr=subprocess.STDOUT,
3929 3993 )
3930 3994 out, _err = proc.communicate()
3931 3995 if proc.returncode != 0:
3932 3996 sys.stdout.buffer.write(out)
3933 3997 sys.exit(1)
3934 3998
3935 3999 cmd = _bytes2sys(b"%s debuginstall -Tjson" % self._hgcommand)
3936 4000 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3937 4001 out, err = p.communicate()
3938 4002
3939 4003 props = json.loads(out)[0]
3940 4004
3941 4005 # Affects hghave.py
3942 4006 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3943 4007 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3944 4008 if props["hgmodules"] == props["pythonexe"]:
3945 4009 osenvironb[b'PYOXIDIZED_IN_MEMORY_RSRC'] = b'1'
3946 4010 else:
3947 4011 osenvironb[b'PYOXIDIZED_FILESYSTEM_RSRC'] = b'1'
3948 4012
3949 4013 def _outputcoverage(self):
3950 4014 """Produce code coverage output."""
3951 4015 import coverage
3952 4016
3953 4017 coverage = coverage.coverage
3954 4018
3955 4019 vlog('# Producing coverage report')
3956 4020 # chdir is the easiest way to get short, relative paths in the
3957 4021 # output.
3958 4022 os.chdir(self._hgroot)
3959 4023 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3960 4024 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3961 4025
3962 4026 # Map install directory paths back to source directory.
3963 4027 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3964 4028
3965 4029 cov.combine()
3966 4030
3967 4031 omit = [
3968 4032 _bytes2sys(os.path.join(x, b'*'))
3969 4033 for x in [self._bindir, self._testdir]
3970 4034 ]
3971 4035 cov.report(ignore_errors=True, omit=omit)
3972 4036
3973 4037 if self.options.htmlcov:
3974 4038 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3975 4039 cov.html_report(directory=htmldir, omit=omit)
3976 4040 if self.options.annotate:
3977 4041 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3978 4042 if not os.path.isdir(adir):
3979 4043 os.mkdir(adir)
3980 4044 cov.annotate(directory=adir, omit=omit)
3981 4045
3982 4046 def _findprogram(self, program):
3983 4047 """Search PATH for a executable program"""
3984 4048 dpb = _sys2bytes(os.defpath)
3985 4049 sepb = _sys2bytes(os.pathsep)
3986 4050 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3987 4051 name = os.path.join(p, program)
3988 4052 if WINDOWS or os.access(name, os.X_OK):
3989 4053 return _bytes2sys(name)
3990 4054 return None
3991 4055
3992 4056 def _checktools(self):
3993 4057 """Ensure tools required to run tests are present."""
3994 4058 for p in self.REQUIREDTOOLS:
3995 4059 if WINDOWS and not p.endswith(b'.exe'):
3996 4060 p += b'.exe'
3997 4061 found = self._findprogram(p)
3998 4062 p = p.decode("utf-8")
3999 4063 if found:
4000 4064 vlog("# Found prerequisite", p, "at", found)
4001 4065 else:
4002 4066 print("WARNING: Did not find prerequisite tool: %s " % p)
4003 4067
4004 4068
4005 4069 def aggregateexceptions(path):
4006 4070 exceptioncounts = collections.Counter()
4007 4071 testsbyfailure = collections.defaultdict(set)
4008 4072 failuresbytest = collections.defaultdict(set)
4009 4073
4010 4074 for f in os.listdir(path):
4011 4075 with open(os.path.join(path, f), 'rb') as fh:
4012 4076 data = fh.read().split(b'\0')
4013 4077 if len(data) != 5:
4014 4078 continue
4015 4079
4016 4080 exc, mainframe, hgframe, hgline, testname = data
4017 4081 exc = exc.decode('utf-8')
4018 4082 mainframe = mainframe.decode('utf-8')
4019 4083 hgframe = hgframe.decode('utf-8')
4020 4084 hgline = hgline.decode('utf-8')
4021 4085 testname = testname.decode('utf-8')
4022 4086
4023 4087 key = (hgframe, hgline, exc)
4024 4088 exceptioncounts[key] += 1
4025 4089 testsbyfailure[key].add(testname)
4026 4090 failuresbytest[testname].add(key)
4027 4091
4028 4092 # Find test having fewest failures for each failure.
4029 4093 leastfailing = {}
4030 4094 for key, tests in testsbyfailure.items():
4031 4095 fewesttest = None
4032 4096 fewestcount = 99999999
4033 4097 for test in sorted(tests):
4034 4098 if len(failuresbytest[test]) < fewestcount:
4035 4099 fewesttest = test
4036 4100 fewestcount = len(failuresbytest[test])
4037 4101
4038 4102 leastfailing[key] = (fewestcount, fewesttest)
4039 4103
4040 4104 # Create a combined counter so we can sort by total occurrences and
4041 4105 # impacted tests.
4042 4106 combined = {}
4043 4107 for key in exceptioncounts:
4044 4108 combined[key] = (
4045 4109 exceptioncounts[key],
4046 4110 len(testsbyfailure[key]),
4047 4111 leastfailing[key][0],
4048 4112 leastfailing[key][1],
4049 4113 )
4050 4114
4051 4115 return {
4052 4116 'exceptioncounts': exceptioncounts,
4053 4117 'total': sum(exceptioncounts.values()),
4054 4118 'combined': combined,
4055 4119 'leastfailing': leastfailing,
4056 4120 'byfailure': testsbyfailure,
4057 4121 'bytest': failuresbytest,
4058 4122 }
4059 4123
4060 4124
4061 4125 if __name__ == '__main__':
4062 4126 if WINDOWS and not os.getenv('MSYSTEM'):
4063 4127 print('cannot run test on Windows without MSYSTEM', file=sys.stderr)
4064 4128 print(
4065 4129 '(if you need to do so contact the mercurial devs: '
4066 4130 'mercurial@mercurial-scm.org)',
4067 4131 file=sys.stderr,
4068 4132 )
4069 4133 sys.exit(255)
4070 4134
4071 4135 runner = TestRunner()
4072 4136
4073 4137 try:
4074 4138 import msvcrt
4075 4139
4076 4140 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
4077 4141 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4078 4142 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
4079 4143 except ImportError:
4080 4144 pass
4081 4145
4082 4146 sys.exit(runner.run(sys.argv[1:]))
@@ -1,2089 +1,2089
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 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
10 10 running 0 tests using 0 parallel processes
11 11
12 12 # Ran 0 tests, 0 skipped, 0 failed.
13 13
14 14 Define a helper to avoid the install step
15 15 =============
16 16 $ rt()
17 17 > {
18 18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j1 "$@"
19 19 > }
20 20
21 21 error paths
22 22
23 23 #if symlink
24 24 $ ln -s $TESTDIR/run-tests.py hg
25 25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
26 26 warning: --with-hg should specify an hg script, not: run-tests.py
27 27 running 0 tests using 0 parallel processes
28 28
29 29 # Ran 0 tests, 0 skipped, 0 failed.
30 30 $ rm hg
31 31 #endif
32 32
33 33 #if execbit
34 34 $ touch hg
35 35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
36 36 usage: run-tests.py [options] [tests]
37 37 run-tests.py: error: --with-hg must specify an executable hg script
38 38 [2]
39 39 $ rm hg
40 40 #endif
41 41
42 42 Features for testing optional lines
43 43 ===================================
44 44
45 45 $ cat > hghaveaddon.py <<EOF
46 46 > import hghave
47 47 > @hghave.check("custom", "custom hghave feature")
48 48 > def has_custom():
49 49 > return True
50 50 > @hghave.check("missing", "missing hghave feature")
51 51 > def has_missing():
52 52 > return False
53 53 > EOF
54 54
55 55 an empty test
56 56 =======================
57 57
58 58 $ touch test-empty.t
59 59 $ rt
60 60 running 1 tests using 1 parallel processes
61 61 .
62 62 # Ran 1 tests, 0 skipped, 0 failed.
63 63 $ rm test-empty.t
64 64
65 65 a succesful test
66 66 =======================
67 67
68 68 $ cat > test-success.t << EOF
69 69 > $ echo babar
70 70 > babar
71 71 > $ echo xyzzy
72 72 > dont_print (?)
73 73 > nothing[42]line (re) (?)
74 74 > never*happens (glob) (?)
75 75 > more_nothing (?)
76 76 > xyzzy
77 77 > nor this (?)
78 78 > $ printf 'abc\ndef\nxyz\n'
79 79 > 123 (?)
80 80 > abc
81 81 > def (?)
82 82 > 456 (?)
83 83 > xyz
84 84 > $ printf 'zyx\nwvu\ntsr\n'
85 85 > abc (?)
86 86 > zyx (custom !)
87 87 > wvu
88 88 > no_print (no-custom !)
89 89 > tsr (no-missing !)
90 90 > missing (missing !)
91 91 > EOF
92 92
93 93 $ rt
94 94 running 1 tests using 1 parallel processes
95 95 .
96 96 # Ran 1 tests, 0 skipped, 0 failed.
97 97
98 98 failing test
99 99 ==================
100 100
101 101 test churn with globs
102 102 $ cat > test-failure.t <<EOF
103 103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
104 104 > bar*bad (glob)
105 105 > bar*baz (glob)
106 106 > | fo (re)
107 107 > EOF
108 108 $ rt test-failure.t
109 109 running 1 tests using 1 parallel processes
110 110
111 111 --- $TESTTMP/test-failure.t
112 112 +++ $TESTTMP/test-failure.t.err
113 113 @@ -1,4 +1,4 @@
114 114 $ echo "bar-baz"; echo "bar-bad"; echo foo
115 115 + bar*baz (glob)
116 116 bar*bad (glob)
117 117 - bar*baz (glob)
118 118 - | fo (re)
119 119 + foo
120 120
121 121 ERROR: test-failure.t output changed
122 122 !
123 123 Failed test-failure.t: output changed
124 124 # Ran 1 tests, 0 skipped, 1 failed.
125 125 python hash seed: * (glob)
126 126 [1]
127 127
128 128 test how multiple globs gets matched with lines in output
129 129 $ cat > test-failure-globs.t <<EOF
130 130 > $ echo "context"; echo "context"; \
131 131 > echo "key: 1"; echo "value: not a"; \
132 132 > echo "key: 2"; echo "value: not b"; \
133 133 > echo "key: 3"; echo "value: c"; \
134 134 > echo "key: 4"; echo "value: d"
135 135 > context
136 136 > context
137 137 > key: 1
138 138 > value: a
139 139 > key: 2
140 140 > value: b
141 141 > key: 3
142 142 > value: * (glob)
143 143 > key: 4
144 144 > value: * (glob)
145 145 > EOF
146 146 $ rt test-failure-globs.t
147 147 running 1 tests using 1 parallel processes
148 148
149 149 --- $TESTTMP/test-failure-globs.t
150 150 +++ $TESTTMP/test-failure-globs.t.err
151 151 @@ -2,9 +2,9 @@
152 152 context
153 153 context
154 154 key: 1
155 155 - value: a
156 156 + value: not a
157 157 key: 2
158 158 - value: b
159 159 + value: not b
160 160 key: 3
161 161 value: * (glob)
162 162 key: 4
163 163
164 164 ERROR: test-failure-globs.t output changed
165 165 !
166 166 Failed test-failure-globs.t: output changed
167 167 # Ran 1 tests, 0 skipped, 1 failed.
168 168 python hash seed: * (glob)
169 169 [1]
170 170 $ rm test-failure-globs.t
171 171
172 172 test diff colorisation
173 173
174 174 #if no-windows pygments
175 175 $ rt test-failure.t --color always
176 176 running 1 tests using 1 parallel processes
177 177
178 178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
179 179 \x1b[38;5;28m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (pygments211 !)
180 180 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (no-pygments211 !)
181 181 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
182 182 \x1b[38;5;250m \x1b[39m $ echo "bar-baz"; echo "bar-bad"; echo foo (esc) (pygments211 !)
183 183 $ echo "bar-baz"; echo "bar-bad"; echo foo (no-pygments211 !)
184 184 \x1b[38;5;28m+ bar*baz (glob)\x1b[39m (esc) (pygments211 !)
185 185 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc) (no-pygments211 !)
186 186 \x1b[38;5;250m \x1b[39m bar*bad (glob) (esc) (pygments211 !)
187 187 bar*bad (glob) (no-pygments211 !)
188 188 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
189 189 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
190 190 \x1b[38;5;28m+ foo\x1b[39m (esc) (pygments211 !)
191 191 \x1b[38;5;34m+ foo\x1b[39m (esc) (no-pygments211 !)
192 192
193 193 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
194 !
194 \x1b[38;5;88m!\x1b[39m (esc)
195 195 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
196 196 # Ran 1 tests, 0 skipped, 1 failed.
197 197 python hash seed: * (glob)
198 198 [1]
199 199
200 200 $ rt test-failure.t 2> tmp.log
201 201 running 1 tests using 1 parallel processes
202 202 [1]
203 203 $ cat tmp.log
204 204
205 205 --- $TESTTMP/test-failure.t
206 206 +++ $TESTTMP/test-failure.t.err
207 207 @@ -1,4 +1,4 @@
208 208 $ echo "bar-baz"; echo "bar-bad"; echo foo
209 209 + bar*baz (glob)
210 210 bar*bad (glob)
211 211 - bar*baz (glob)
212 212 - | fo (re)
213 213 + foo
214 214
215 215 ERROR: test-failure.t output changed
216 216 !
217 217 Failed test-failure.t: output changed
218 218 # Ran 1 tests, 0 skipped, 1 failed.
219 219 python hash seed: * (glob)
220 220 #endif
221 221
222 222 $ cat > test-failure.t << EOF
223 223 > $ true
224 224 > should go away (true !)
225 225 > $ true
226 226 > should stay (false !)
227 227 >
228 228 > Should remove first line, not second or third
229 229 > $ echo 'testing'
230 230 > baz*foo (glob) (true !)
231 231 > foobar*foo (glob) (false !)
232 232 > te*ting (glob) (true !)
233 233 >
234 234 > Should keep first two lines, remove third and last
235 235 > $ echo 'testing'
236 236 > test.ng (re) (true !)
237 237 > foo.ar (re) (false !)
238 238 > b.r (re) (true !)
239 239 > missing (?)
240 240 > awol (true !)
241 241 >
242 242 > The "missing" line should stay, even though awol is dropped
243 243 > $ echo 'testing'
244 244 > test.ng (re) (true !)
245 245 > foo.ar (?)
246 246 > awol
247 247 > missing (?)
248 248 > EOF
249 249 $ rt test-failure.t
250 250 running 1 tests using 1 parallel processes
251 251
252 252 --- $TESTTMP/test-failure.t
253 253 +++ $TESTTMP/test-failure.t.err
254 254 @@ -1,11 +1,9 @@
255 255 $ true
256 256 - should go away (true !)
257 257 $ true
258 258 should stay (false !)
259 259
260 260 Should remove first line, not second or third
261 261 $ echo 'testing'
262 262 - baz*foo (glob) (true !)
263 263 foobar*foo (glob) (false !)
264 264 te*ting (glob) (true !)
265 265
266 266 foo.ar (re) (false !)
267 267 missing (?)
268 268 @@ -13,13 +11,10 @@
269 269 $ echo 'testing'
270 270 test.ng (re) (true !)
271 271 foo.ar (re) (false !)
272 272 - b.r (re) (true !)
273 273 missing (?)
274 274 - awol (true !)
275 275
276 276 The "missing" line should stay, even though awol is dropped
277 277 $ echo 'testing'
278 278 test.ng (re) (true !)
279 279 foo.ar (?)
280 280 - awol
281 281 missing (?)
282 282
283 283 ERROR: test-failure.t output changed
284 284 !
285 285 Failed test-failure.t: output changed
286 286 # Ran 1 tests, 0 skipped, 1 failed.
287 287 python hash seed: * (glob)
288 288 [1]
289 289
290 290 basic failing test
291 291 $ cat > test-failure.t << EOF
292 292 > $ echo babar
293 293 > rataxes
294 294 > This is a noop statement so that
295 295 > this test is still more bytes than success.
296 296 > pad pad pad pad............................................................
297 297 > pad pad pad pad............................................................
298 298 > pad pad pad pad............................................................
299 299 > pad pad pad pad............................................................
300 300 > pad pad pad pad............................................................
301 301 > pad pad pad pad............................................................
302 302 > EOF
303 303
304 304 >>> fh = open('test-failure-unicode.t', 'wb')
305 305 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
306 306 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
307 307
308 308 $ rt
309 309 running 3 tests using 1 parallel processes
310 310
311 311 --- $TESTTMP/test-failure.t
312 312 +++ $TESTTMP/test-failure.t.err
313 313 @@ -1,5 +1,5 @@
314 314 $ echo babar
315 315 - rataxes
316 316 + babar
317 317 This is a noop statement so that
318 318 this test is still more bytes than success.
319 319 pad pad pad pad............................................................
320 320
321 321 ERROR: test-failure.t output changed
322 322 !.
323 323 --- $TESTTMP/test-failure-unicode.t
324 324 +++ $TESTTMP/test-failure-unicode.t.err
325 325 @@ -1,2 +1,2 @@
326 326 $ echo babar\xce\xb1 (esc)
327 327 - l\xce\xb5\xce\xb5t (esc)
328 328 + babar\xce\xb1 (esc)
329 329
330 330 ERROR: test-failure-unicode.t output changed
331 331 !
332 332 Failed test-failure-unicode.t: output changed
333 333 Failed test-failure.t: output changed
334 334 # Ran 3 tests, 0 skipped, 2 failed.
335 335 python hash seed: * (glob)
336 336 [1]
337 337
338 338 test --outputdir
339 339 $ mkdir output
340 340 $ rt --outputdir output
341 341 running 3 tests using 1 parallel processes
342 342
343 343 --- $TESTTMP/test-failure.t
344 344 +++ $TESTTMP/output/test-failure.t.err
345 345 @@ -1,5 +1,5 @@
346 346 $ echo babar
347 347 - rataxes
348 348 + babar
349 349 This is a noop statement so that
350 350 this test is still more bytes than success.
351 351 pad pad pad pad............................................................
352 352
353 353 ERROR: test-failure.t output changed
354 354 !.
355 355 --- $TESTTMP/test-failure-unicode.t
356 356 +++ $TESTTMP/output/test-failure-unicode.t.err
357 357 @@ -1,2 +1,2 @@
358 358 $ echo babar\xce\xb1 (esc)
359 359 - l\xce\xb5\xce\xb5t (esc)
360 360 + babar\xce\xb1 (esc)
361 361
362 362 ERROR: test-failure-unicode.t output changed
363 363 !
364 364 Failed test-failure-unicode.t: output changed
365 365 Failed test-failure.t: output changed
366 366 # Ran 3 tests, 0 skipped, 2 failed.
367 367 python hash seed: * (glob)
368 368 [1]
369 369 $ ls -a output
370 370 .
371 371 ..
372 372 .testtimes
373 373 test-failure-unicode.t.err
374 374 test-failure.t.err
375 375
376 376 test --xunit support
377 377 $ rt --xunit=xunit.xml
378 378 running 3 tests using 1 parallel processes
379 379
380 380 --- $TESTTMP/test-failure.t
381 381 +++ $TESTTMP/test-failure.t.err
382 382 @@ -1,5 +1,5 @@
383 383 $ echo babar
384 384 - rataxes
385 385 + babar
386 386 This is a noop statement so that
387 387 this test is still more bytes than success.
388 388 pad pad pad pad............................................................
389 389
390 390 ERROR: test-failure.t output changed
391 391 !.
392 392 --- $TESTTMP/test-failure-unicode.t
393 393 +++ $TESTTMP/test-failure-unicode.t.err
394 394 @@ -1,2 +1,2 @@
395 395 $ echo babar\xce\xb1 (esc)
396 396 - l\xce\xb5\xce\xb5t (esc)
397 397 + babar\xce\xb1 (esc)
398 398
399 399 ERROR: test-failure-unicode.t output changed
400 400 !
401 401 Failed test-failure-unicode.t: output changed
402 402 Failed test-failure.t: output changed
403 403 # Ran 3 tests, 0 skipped, 2 failed.
404 404 python hash seed: * (glob)
405 405 [1]
406 406 $ cat xunit.xml
407 407 <?xml version="1.0" encoding="utf-8"?>
408 408 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
409 409 <testcase name="test-success.t" time="*"/> (glob)
410 410 <testcase name="test-failure-unicode.t" time="*"> (glob)
411 411 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
412 412 <failure message="output changed" type="output-mismatch"> (no-py38 !)
413 413 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
414 414 +++ $TESTTMP/test-failure-unicode.t.err
415 415 @@ -1,2 +1,2 @@
416 416 $ echo babar\xce\xb1 (esc)
417 417 - l\xce\xb5\xce\xb5t (esc)
418 418 + babar\xce\xb1 (esc)
419 419 ]]></failure> (py38 !)
420 420 ]]> </failure> (no-py38 !)
421 421 </testcase>
422 422 <testcase name="test-failure.t" time="*"> (glob)
423 423 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
424 424 <failure message="output changed" type="output-mismatch"> (no-py38 !)
425 425 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
426 426 +++ $TESTTMP/test-failure.t.err
427 427 @@ -1,5 +1,5 @@
428 428 $ echo babar
429 429 - rataxes
430 430 + babar
431 431 This is a noop statement so that
432 432 this test is still more bytes than success.
433 433 pad pad pad pad............................................................
434 434 ]]></failure> (py38 !)
435 435 ]]> </failure> (no-py38 !)
436 436 </testcase>
437 437 </testsuite>
438 438
439 439 $ cat .testtimes
440 440 test-empty.t * (glob)
441 441 test-failure-globs.t * (glob)
442 442 test-failure-unicode.t * (glob)
443 443 test-failure.t * (glob)
444 444 test-success.t * (glob)
445 445
446 446 $ rt --list-tests
447 447 test-failure-unicode.t
448 448 test-failure.t
449 449 test-success.t
450 450
451 451 $ rt --list-tests --json
452 452 test-failure-unicode.t
453 453 test-failure.t
454 454 test-success.t
455 455 $ cat report.json
456 456 testreport ={
457 457 "test-failure-unicode.t": {
458 458 "result": "success"
459 459 },
460 460 "test-failure.t": {
461 461 "result": "success"
462 462 },
463 463 "test-success.t": {
464 464 "result": "success"
465 465 }
466 466 } (no-eol)
467 467
468 468 $ rt --list-tests --xunit=xunit.xml
469 469 test-failure-unicode.t
470 470 test-failure.t
471 471 test-success.t
472 472 $ cat xunit.xml
473 473 <?xml version="1.0" encoding="utf-8"?>
474 474 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
475 475 <testcase name="test-failure-unicode.t"/>
476 476 <testcase name="test-failure.t"/>
477 477 <testcase name="test-success.t"/>
478 478 </testsuite>
479 479
480 480 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
481 481 test-failure-unicode.t
482 482 test-failure.t
483 483 $ cat output/report.json
484 484 testreport ={
485 485 "test-failure-unicode.t": {
486 486 "result": "success"
487 487 },
488 488 "test-failure.t": {
489 489 "result": "success"
490 490 }
491 491 } (no-eol)
492 492 $ cat xunit.xml
493 493 <?xml version="1.0" encoding="utf-8"?>
494 494 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
495 495 <testcase name="test-failure-unicode.t"/>
496 496 <testcase name="test-failure.t"/>
497 497 </testsuite>
498 498
499 499 $ rm test-failure-unicode.t
500 500
501 501 test for --retest
502 502 ====================
503 503
504 504 $ rt --retest
505 505 running 1 tests using 1 parallel processes
506 506
507 507 --- $TESTTMP/test-failure.t
508 508 +++ $TESTTMP/test-failure.t.err
509 509 @@ -1,5 +1,5 @@
510 510 $ echo babar
511 511 - rataxes
512 512 + babar
513 513 This is a noop statement so that
514 514 this test is still more bytes than success.
515 515 pad pad pad pad............................................................
516 516
517 517 ERROR: test-failure.t output changed
518 518 !
519 519 Failed test-failure.t: output changed
520 520 # Ran 1 tests, 0 skipped, 1 failed.
521 521 python hash seed: * (glob)
522 522 [1]
523 523
524 524 --retest works with --outputdir
525 525 $ rm -r output
526 526 $ mkdir output
527 527 $ mv test-failure.t.err output
528 528 $ rt --retest --outputdir output
529 529 running 1 tests using 1 parallel processes
530 530
531 531 --- $TESTTMP/test-failure.t
532 532 +++ $TESTTMP/output/test-failure.t.err
533 533 @@ -1,5 +1,5 @@
534 534 $ echo babar
535 535 - rataxes
536 536 + babar
537 537 This is a noop statement so that
538 538 this test is still more bytes than success.
539 539 pad pad pad pad............................................................
540 540
541 541 ERROR: test-failure.t output changed
542 542 !
543 543 Failed test-failure.t: output changed
544 544 # Ran 1 tests, 0 skipped, 1 failed.
545 545 python hash seed: * (glob)
546 546 [1]
547 547
548 548 Selecting Tests To Run
549 549 ======================
550 550
551 551 successful
552 552
553 553 $ rt test-success.t
554 554 running 1 tests using 1 parallel processes
555 555 .
556 556 # Ran 1 tests, 0 skipped, 0 failed.
557 557
558 558 success w/ keyword
559 559 $ rt -k xyzzy
560 560 running 2 tests using 1 parallel processes
561 561 .
562 562 # Ran 2 tests, 1 skipped, 0 failed.
563 563
564 564 failed
565 565
566 566 $ rt test-failure.t
567 567 running 1 tests using 1 parallel processes
568 568
569 569 --- $TESTTMP/test-failure.t
570 570 +++ $TESTTMP/test-failure.t.err
571 571 @@ -1,5 +1,5 @@
572 572 $ echo babar
573 573 - rataxes
574 574 + babar
575 575 This is a noop statement so that
576 576 this test is still more bytes than success.
577 577 pad pad pad pad............................................................
578 578
579 579 ERROR: test-failure.t output changed
580 580 !
581 581 Failed test-failure.t: output changed
582 582 # Ran 1 tests, 0 skipped, 1 failed.
583 583 python hash seed: * (glob)
584 584 [1]
585 585
586 586 failure w/ keyword
587 587 $ rt -k rataxes
588 588 running 2 tests using 1 parallel processes
589 589
590 590 --- $TESTTMP/test-failure.t
591 591 +++ $TESTTMP/test-failure.t.err
592 592 @@ -1,5 +1,5 @@
593 593 $ echo babar
594 594 - rataxes
595 595 + babar
596 596 This is a noop statement so that
597 597 this test is still more bytes than success.
598 598 pad pad pad pad............................................................
599 599
600 600 ERROR: test-failure.t output changed
601 601 !
602 602 Failed test-failure.t: output changed
603 603 # Ran 2 tests, 1 skipped, 1 failed.
604 604 python hash seed: * (glob)
605 605 [1]
606 606
607 607 Verify that when a process fails to start we show a useful message
608 608 ==================================================================
609 609
610 610 $ cat > test-serve-fail.t <<EOF
611 611 > $ echo 'abort: child process failed to start blah'
612 612 > EOF
613 613 $ rt test-serve-fail.t
614 614 running 1 tests using 1 parallel processes
615 615
616 616 --- $TESTTMP/test-serve-fail.t
617 617 +++ $TESTTMP/test-serve-fail.t.err
618 618 @@ -1* +1,2 @@ (glob)
619 619 $ echo 'abort: child process failed to start blah'
620 620 + abort: child process failed to start blah
621 621
622 622 ERROR: test-serve-fail.t output changed
623 623 !
624 624 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
625 625 # Ran 1 tests, 0 skipped, 1 failed.
626 626 python hash seed: * (glob)
627 627 [1]
628 628 $ rm test-serve-fail.t
629 629
630 630 Verify that we can try other ports
631 631 ===================================
632 632
633 633 Extensions aren't inherited by the invoked run-tests.py. An extension
634 634 introducing a repository requirement could cause this to fail. So we force
635 635 HGRCPATH to get a clean environment.
636 636
637 637 $ HGRCPATH= hg init inuse
638 638 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
639 639 $ cat blocks.pid >> $DAEMON_PIDS
640 640 $ cat > test-serve-inuse.t <<EOF
641 641 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
642 642 > $ cat hg.pid >> \$DAEMON_PIDS
643 643 > EOF
644 644 $ rt test-serve-inuse.t
645 645 running 1 tests using 1 parallel processes
646 646 .
647 647 # Ran 1 tests, 0 skipped, 0 failed.
648 648 $ rm test-serve-inuse.t
649 649 $ killdaemons.py $DAEMON_PIDS
650 650
651 651 Running In Debug Mode
652 652 ======================
653 653
654 654 $ rt --debug 2>&1 | grep -v pwd
655 655 running 2 tests using 1 parallel processes
656 656 + echo *SALT* 0 0 (glob)
657 657 *SALT* 0 0 (glob)
658 658 + echo babar
659 659 babar
660 660 + echo *SALT* 10 0 (glob)
661 661 *SALT* 10 0 (glob)
662 662 *+ echo *SALT* 0 0 (glob)
663 663 *SALT* 0 0 (glob)
664 664 + echo babar
665 665 babar
666 666 + echo *SALT* 2 0 (glob)
667 667 *SALT* 2 0 (glob)
668 668 + echo xyzzy
669 669 xyzzy
670 670 + echo *SALT* 9 0 (glob)
671 671 *SALT* 9 0 (glob)
672 672 + printf *abc\ndef\nxyz\n* (glob)
673 673 abc
674 674 def
675 675 xyz
676 676 + echo *SALT* 15 0 (glob)
677 677 *SALT* 15 0 (glob)
678 678 + printf *zyx\nwvu\ntsr\n* (glob)
679 679 zyx
680 680 wvu
681 681 tsr
682 682 + echo *SALT* 22 0 (glob)
683 683 *SALT* 22 0 (glob)
684 684 .
685 685 # Ran 2 tests, 0 skipped, 0 failed.
686 686
687 687 Parallel runs
688 688 ==============
689 689
690 690 (duplicate the failing test to get predictable output)
691 691 $ cp test-failure.t test-failure-copy.t
692 692
693 693 $ rt --jobs 2 test-failure*.t -n
694 694 running 2 tests using 2 parallel processes
695 695 !!
696 696 Failed test-failure*.t: output changed (glob)
697 697 Failed test-failure*.t: output changed (glob)
698 698 # Ran 2 tests, 0 skipped, 2 failed.
699 699 python hash seed: * (glob)
700 700 [1]
701 701
702 702 failures in parallel with --first should only print one failure
703 703 $ rt --jobs 2 --first test-failure*.t
704 704 running 2 tests using 2 parallel processes
705 705
706 706 --- $TESTTMP/test-failure*.t (glob)
707 707 +++ $TESTTMP/test-failure*.t.err (glob)
708 708 @@ -1,5 +1,5 @@
709 709 $ echo babar
710 710 - rataxes
711 711 + babar
712 712 This is a noop statement so that
713 713 this test is still more bytes than success.
714 714 pad pad pad pad............................................................
715 715
716 716 Failed test-failure*.t: output changed (glob)
717 717 Failed test-failure*.t: output changed (glob)
718 718 # Ran 2 tests, 0 skipped, 2 failed.
719 719 python hash seed: * (glob)
720 720 [1]
721 721
722 722
723 723 (delete the duplicated test file)
724 724 $ rm test-failure-copy.t
725 725
726 726 multiple runs per test should be parallelized
727 727
728 728 $ rt --jobs 2 --runs-per-test 2 test-success.t
729 729 running 2 tests using 2 parallel processes
730 730 ..
731 731 # Ran 2 tests, 0 skipped, 0 failed.
732 732
733 733 Interactive run
734 734 ===============
735 735
736 736 (backup the failing test)
737 737 $ cp test-failure.t backup
738 738
739 739 Refuse the fix
740 740
741 741 $ echo 'n' | rt -i
742 742 running 2 tests using 1 parallel processes
743 743
744 744 --- $TESTTMP/test-failure.t
745 745 +++ $TESTTMP/test-failure.t.err
746 746 @@ -1,5 +1,5 @@
747 747 $ echo babar
748 748 - rataxes
749 749 + babar
750 750 This is a noop statement so that
751 751 this test is still more bytes than success.
752 752 pad pad pad pad............................................................
753 753 Accept this change? [y/N]
754 754 ERROR: test-failure.t output changed
755 755 !.
756 756 Failed test-failure.t: output changed
757 757 # Ran 2 tests, 0 skipped, 1 failed.
758 758 python hash seed: * (glob)
759 759 [1]
760 760
761 761 $ cat test-failure.t
762 762 $ echo babar
763 763 rataxes
764 764 This is a noop statement so that
765 765 this test is still more bytes than success.
766 766 pad pad pad pad............................................................
767 767 pad pad pad pad............................................................
768 768 pad pad pad pad............................................................
769 769 pad pad pad pad............................................................
770 770 pad pad pad pad............................................................
771 771 pad pad pad pad............................................................
772 772
773 773 Interactive with custom view
774 774
775 775 $ echo 'n' | rt -i --view echo
776 776 running 2 tests using 1 parallel processes
777 777 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
778 778 Accept this change? [y/N]* (glob)
779 779 ERROR: test-failure.t output changed
780 780 !.
781 781 Failed test-failure.t: output changed
782 782 # Ran 2 tests, 0 skipped, 1 failed.
783 783 python hash seed: * (glob)
784 784 [1]
785 785
786 786 View the fix
787 787
788 788 $ echo 'y' | rt --view echo
789 789 running 2 tests using 1 parallel processes
790 790 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
791 791
792 792 ERROR: test-failure.t output changed
793 793 !.
794 794 Failed test-failure.t: output changed
795 795 # Ran 2 tests, 0 skipped, 1 failed.
796 796 python hash seed: * (glob)
797 797 [1]
798 798
799 799 Accept the fix
800 800
801 801 $ cat >> test-failure.t <<EOF
802 802 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
803 803 > saved backup bundle to \$TESTTMP/foo.hg
804 804 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
805 805 > saved backup bundle to $TESTTMP\\foo.hg
806 806 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
807 807 > saved backup bundle to \$TESTTMP/*.hg (glob)
808 808 > EOF
809 809 $ echo 'y' | rt -i 2>&1
810 810 running 2 tests using 1 parallel processes
811 811
812 812 --- $TESTTMP/test-failure.t
813 813 +++ $TESTTMP/test-failure.t.err
814 814 @@ -1,5 +1,5 @@
815 815 $ echo babar
816 816 - rataxes
817 817 + babar
818 818 This is a noop statement so that
819 819 this test is still more bytes than success.
820 820 pad pad pad pad............................................................
821 821 @@ -11,6 +11,6 @@
822 822 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
823 823 saved backup bundle to $TESTTMP/foo.hg
824 824 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
825 825 - saved backup bundle to $TESTTMP\foo.hg
826 826 + saved backup bundle to $TESTTMP/foo.hg
827 827 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
828 828 saved backup bundle to $TESTTMP/*.hg (glob)
829 829 Accept this change? [y/N] ..
830 830 # Ran 2 tests, 0 skipped, 0 failed.
831 831
832 832 $ sed -e 's,(glob)$,&<,g' test-failure.t
833 833 $ echo babar
834 834 babar
835 835 This is a noop statement so that
836 836 this test is still more bytes than success.
837 837 pad pad pad pad............................................................
838 838 pad pad pad pad............................................................
839 839 pad pad pad pad............................................................
840 840 pad pad pad pad............................................................
841 841 pad pad pad pad............................................................
842 842 pad pad pad pad............................................................
843 843 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
844 844 saved backup bundle to $TESTTMP/foo.hg
845 845 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
846 846 saved backup bundle to $TESTTMP/foo.hg
847 847 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
848 848 saved backup bundle to $TESTTMP/*.hg (glob)<
849 849
850 850 $ rm test-failure.t
851 851
852 852 Race condition - test file was modified when test is running
853 853
854 854 $ TESTRACEDIR=`pwd`
855 855 $ export TESTRACEDIR
856 856 $ cat > test-race.t <<EOF
857 857 > $ echo 1
858 858 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
859 859 > EOF
860 860
861 861 $ rt -i test-race.t
862 862 running 1 tests using 1 parallel processes
863 863
864 864 --- $TESTTMP/test-race.t
865 865 +++ $TESTTMP/test-race.t.err
866 866 @@ -1,2 +1,3 @@
867 867 $ echo 1
868 868 + 1
869 869 $ echo "# a new line" >> $TESTTMP/test-race.t
870 870 Reference output has changed (run again to prompt changes)
871 871 ERROR: test-race.t output changed
872 872 !
873 873 Failed test-race.t: output changed
874 874 # Ran 1 tests, 0 skipped, 1 failed.
875 875 python hash seed: * (glob)
876 876 [1]
877 877
878 878 $ rm test-race.t
879 879
880 880 When "#testcases" is used in .t files
881 881
882 882 $ cat >> test-cases.t <<EOF
883 883 > #testcases a b
884 884 > #if a
885 885 > $ echo 1
886 886 > #endif
887 887 > #if b
888 888 > $ echo 2
889 889 > #endif
890 890 > EOF
891 891
892 892 $ cat <<EOF | rt -i test-cases.t 2>&1
893 893 > y
894 894 > y
895 895 > EOF
896 896 running 2 tests using 1 parallel processes
897 897
898 898 --- $TESTTMP/test-cases.t
899 899 +++ $TESTTMP/test-cases.t#a.err
900 900 @@ -1,6 +1,7 @@
901 901 #testcases a b
902 902 #if a
903 903 $ echo 1
904 904 + 1
905 905 #endif
906 906 #if b
907 907 $ echo 2
908 908 Accept this change? [y/N] .
909 909 --- $TESTTMP/test-cases.t
910 910 +++ $TESTTMP/test-cases.t#b.err
911 911 @@ -5,4 +5,5 @@
912 912 #endif
913 913 #if b
914 914 $ echo 2
915 915 + 2
916 916 #endif
917 917 Accept this change? [y/N] .
918 918 # Ran 2 tests, 0 skipped, 0 failed.
919 919
920 920 $ cat test-cases.t
921 921 #testcases a b
922 922 #if a
923 923 $ echo 1
924 924 1
925 925 #endif
926 926 #if b
927 927 $ echo 2
928 928 2
929 929 #endif
930 930
931 931 $ cat >> test-cases.t <<'EOF'
932 932 > #if a
933 933 > $ NAME=A
934 934 > #else
935 935 > $ NAME=B
936 936 > #endif
937 937 > $ echo $NAME
938 938 > A (a !)
939 939 > B (b !)
940 940 > EOF
941 941 $ rt test-cases.t
942 942 running 2 tests using 1 parallel processes
943 943 ..
944 944 # Ran 2 tests, 0 skipped, 0 failed.
945 945
946 946 When using multiple dimensions of "#testcases" in .t files
947 947
948 948 $ cat > test-cases.t <<'EOF'
949 949 > #testcases a b
950 950 > #testcases c d
951 951 > #if a d
952 952 > $ echo $TESTCASE
953 953 > a#d
954 954 > #endif
955 955 > #if b c
956 956 > $ echo yes
957 957 > no
958 958 > #endif
959 959 > EOF
960 960 $ rt test-cases.t
961 961 running 4 tests using 1 parallel processes
962 962 ..
963 963 --- $TESTTMP/test-cases.t
964 964 +++ $TESTTMP/test-cases.t#b#c.err
965 965 @@ -6,5 +6,5 @@
966 966 #endif
967 967 #if b c
968 968 $ echo yes
969 969 - no
970 970 + yes
971 971 #endif
972 972
973 973 ERROR: test-cases.t#b#c output changed
974 974 !.
975 975 Failed test-cases.t#b#c: output changed
976 976 # Ran 4 tests, 0 skipped, 1 failed.
977 977 python hash seed: * (glob)
978 978 [1]
979 979
980 980 $ rt --retest
981 981 running 1 tests using 1 parallel processes
982 982
983 983 --- $TESTTMP/test-cases.t
984 984 +++ $TESTTMP/test-cases.t#b#c.err
985 985 @@ -6,5 +6,5 @@
986 986 #endif
987 987 #if b c
988 988 $ echo yes
989 989 - no
990 990 + yes
991 991 #endif
992 992
993 993 ERROR: test-cases.t#b#c output changed
994 994 !
995 995 Failed test-cases.t#b#c: output changed
996 996 # Ran 1 tests, 0 skipped, 1 failed.
997 997 python hash seed: * (glob)
998 998 [1]
999 999 $ rm test-cases.t#b#c.err
1000 1000 $ rm test-cases.t
1001 1001
1002 1002 (reinstall)
1003 1003 $ mv backup test-failure.t
1004 1004
1005 1005 No Diff
1006 1006 ===============
1007 1007
1008 1008 $ rt --nodiff
1009 1009 running 2 tests using 1 parallel processes
1010 1010 !.
1011 1011 Failed test-failure.t: output changed
1012 1012 # Ran 2 tests, 0 skipped, 1 failed.
1013 1013 python hash seed: * (glob)
1014 1014 [1]
1015 1015
1016 1016 test --tmpdir support
1017 1017 $ rt --tmpdir=$TESTTMP/keep test-success.t
1018 1018 running 1 tests using 1 parallel processes
1019 1019
1020 1020 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1021 1021 Keeping threadtmp dir: $TESTTMP/keep/child1
1022 1022 .
1023 1023 # Ran 1 tests, 0 skipped, 0 failed.
1024 1024
1025 1025 timeouts
1026 1026 ========
1027 1027 $ cat > test-timeout.t <<EOF
1028 1028 > $ sleep 2
1029 1029 > $ echo pass
1030 1030 > pass
1031 1031 > EOF
1032 1032 > echo '#require slow' > test-slow-timeout.t
1033 1033 > cat test-timeout.t >> test-slow-timeout.t
1034 1034 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1035 1035 running 2 tests using 1 parallel processes
1036 1036 st
1037 1037 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1038 1038 Failed test-timeout.t: timed out
1039 1039 # Ran 1 tests, 1 skipped, 1 failed.
1040 1040 python hash seed: * (glob)
1041 1041 [1]
1042 1042 $ rt --timeout=1 --slowtimeout=3 \
1043 1043 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1044 1044 running 2 tests using 1 parallel processes
1045 1045 .t
1046 1046 Failed test-timeout.t: timed out
1047 1047 # Ran 2 tests, 0 skipped, 1 failed.
1048 1048 python hash seed: * (glob)
1049 1049 [1]
1050 1050 $ rm test-timeout.t test-slow-timeout.t
1051 1051
1052 1052 test for --time
1053 1053 ==================
1054 1054
1055 1055 $ rt test-success.t --time
1056 1056 running 1 tests using 1 parallel processes
1057 1057 .
1058 1058 # Ran 1 tests, 0 skipped, 0 failed.
1059 1059 # Producing time report
1060 1060 start end cuser csys real Test
1061 1061 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1062 1062
1063 1063 test for --time with --job enabled
1064 1064 ====================================
1065 1065
1066 1066 $ rt test-success.t --time --jobs 2
1067 1067 running 1 tests using 1 parallel processes
1068 1068 .
1069 1069 # Ran 1 tests, 0 skipped, 0 failed.
1070 1070 # Producing time report
1071 1071 start end cuser csys real Test
1072 1072 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1073 1073
1074 1074 Skips
1075 1075 ================
1076 1076 $ cat > test-skip.t <<EOF
1077 1077 > $ echo xyzzy
1078 1078 > #if true
1079 1079 > #require false
1080 1080 > #end
1081 1081 > EOF
1082 1082 $ cat > test-noskip.t <<EOF
1083 1083 > #if false
1084 1084 > #require false
1085 1085 > #endif
1086 1086 > EOF
1087 1087 $ rt --nodiff
1088 1088 running 4 tests using 1 parallel processes
1089 1089 !.s.
1090 1090 Skipped test-skip.t: missing feature: nail clipper
1091 1091 Failed test-failure.t: output changed
1092 1092 # Ran 3 tests, 1 skipped, 1 failed.
1093 1093 python hash seed: * (glob)
1094 1094 [1]
1095 1095
1096 1096 $ rm test-noskip.t
1097 1097 $ rt --keyword xyzzy
1098 1098 running 3 tests using 1 parallel processes
1099 1099 .s
1100 1100 Skipped test-skip.t: missing feature: nail clipper
1101 1101 # Ran 2 tests, 2 skipped, 0 failed.
1102 1102
1103 1103 Skips with xml
1104 1104 $ rt --keyword xyzzy \
1105 1105 > --xunit=xunit.xml
1106 1106 running 3 tests using 1 parallel processes
1107 1107 .s
1108 1108 Skipped test-skip.t: missing feature: nail clipper
1109 1109 # Ran 2 tests, 2 skipped, 0 failed.
1110 1110 $ cat xunit.xml
1111 1111 <?xml version="1.0" encoding="utf-8"?>
1112 1112 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1113 1113 <testcase name="test-success.t" time="*"/> (glob)
1114 1114 <testcase name="test-skip.t">
1115 1115 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1116 1116 <skipped> (no-py38 !)
1117 1117 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1118 1118 </testcase>
1119 1119 </testsuite>
1120 1120
1121 1121 Missing skips or blacklisted skips don't count as executed:
1122 1122 $ mkdir tests
1123 1123 $ echo tests/test-failure.t > blacklist
1124 1124 $ cp test-failure.t tests
1125 1125 $ rt --blacklist=blacklist --json\
1126 1126 > tests/test-failure.t tests/test-bogus.t
1127 1127 running 2 tests using 1 parallel processes
1128 1128 ss
1129 1129 Skipped test-bogus.t: Doesn't exist
1130 1130 Skipped test-failure.t: blacklisted
1131 1131 # Ran 0 tests, 2 skipped, 0 failed.
1132 1132 $ cat tests/report.json
1133 1133 testreport ={
1134 1134 "test-bogus.t": {
1135 1135 "result": "skip"
1136 1136 },
1137 1137 "test-failure.t": {
1138 1138 "result": "skip"
1139 1139 }
1140 1140 } (no-eol)
1141 1141 $ rm -r tests
1142 1142 $ echo test-failure.t > blacklist
1143 1143
1144 1144 Whitelist trumps blacklist
1145 1145 $ echo test-failure.t > whitelist
1146 1146 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1147 1147 > test-failure.t test-bogus.t
1148 1148 running 2 tests using 1 parallel processes
1149 1149 s
1150 1150 --- $TESTTMP/test-failure.t
1151 1151 +++ $TESTTMP/test-failure.t.err
1152 1152 @@ -1,5 +1,5 @@
1153 1153 $ echo babar
1154 1154 - rataxes
1155 1155 + babar
1156 1156 This is a noop statement so that
1157 1157 this test is still more bytes than success.
1158 1158 pad pad pad pad............................................................
1159 1159
1160 1160 ERROR: test-failure.t output changed
1161 1161 !
1162 1162 Skipped test-bogus.t: Doesn't exist
1163 1163 Failed test-failure.t: output changed
1164 1164 # Ran 1 tests, 1 skipped, 1 failed.
1165 1165 python hash seed: * (glob)
1166 1166 [1]
1167 1167
1168 1168 Ensure that --test-list causes only the tests listed in that file to
1169 1169 be executed.
1170 1170 $ echo test-success.t >> onlytest
1171 1171 $ rt --test-list=onlytest
1172 1172 running 1 tests using 1 parallel processes
1173 1173 .
1174 1174 # Ran 1 tests, 0 skipped, 0 failed.
1175 1175 $ echo test-bogus.t >> anothertest
1176 1176 $ rt --test-list=onlytest --test-list=anothertest
1177 1177 running 2 tests using 1 parallel processes
1178 1178 s.
1179 1179 Skipped test-bogus.t: Doesn't exist
1180 1180 # Ran 1 tests, 1 skipped, 0 failed.
1181 1181 $ rm onlytest anothertest
1182 1182
1183 1183 test for --json
1184 1184 ==================
1185 1185
1186 1186 $ rt --json
1187 1187 running 3 tests using 1 parallel processes
1188 1188
1189 1189 --- $TESTTMP/test-failure.t
1190 1190 +++ $TESTTMP/test-failure.t.err
1191 1191 @@ -1,5 +1,5 @@
1192 1192 $ echo babar
1193 1193 - rataxes
1194 1194 + babar
1195 1195 This is a noop statement so that
1196 1196 this test is still more bytes than success.
1197 1197 pad pad pad pad............................................................
1198 1198
1199 1199 ERROR: test-failure.t output changed
1200 1200 !.s
1201 1201 Skipped test-skip.t: missing feature: nail clipper
1202 1202 Failed test-failure.t: output changed
1203 1203 # Ran 2 tests, 1 skipped, 1 failed.
1204 1204 python hash seed: * (glob)
1205 1205 [1]
1206 1206
1207 1207 $ cat report.json
1208 1208 testreport ={
1209 1209 "test-failure.t": [\{] (re)
1210 1210 "csys": "\s*\d+\.\d{3,4}", ? (re)
1211 1211 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1212 1212 "diff": "---.+\+\+\+.+", ? (re)
1213 1213 "end": "\s*\d+\.\d{3,4}", ? (re)
1214 1214 "result": "failure", ? (re)
1215 1215 "start": "\s*\d+\.\d{3,4}", ? (re)
1216 1216 "time": "\s*\d+\.\d{3,4}" (re)
1217 1217 }, ? (re)
1218 1218 "test-skip.t": {
1219 1219 "csys": "\s*\d+\.\d{3,4}", ? (re)
1220 1220 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1221 1221 "diff": "", ? (re)
1222 1222 "end": "\s*\d+\.\d{3,4}", ? (re)
1223 1223 "result": "skip", ? (re)
1224 1224 "start": "\s*\d+\.\d{3,4}", ? (re)
1225 1225 "time": "\s*\d+\.\d{3,4}" (re)
1226 1226 }, ? (re)
1227 1227 "test-success.t": [\{] (re)
1228 1228 "csys": "\s*\d+\.\d{3,4}", ? (re)
1229 1229 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1230 1230 "diff": "", ? (re)
1231 1231 "end": "\s*\d+\.\d{3,4}", ? (re)
1232 1232 "result": "success", ? (re)
1233 1233 "start": "\s*\d+\.\d{3,4}", ? (re)
1234 1234 "time": "\s*\d+\.\d{3,4}" (re)
1235 1235 }
1236 1236 } (no-eol)
1237 1237 --json with --outputdir
1238 1238
1239 1239 $ rm report.json
1240 1240 $ rm -r output
1241 1241 $ mkdir output
1242 1242 $ rt --json --outputdir output
1243 1243 running 3 tests using 1 parallel processes
1244 1244
1245 1245 --- $TESTTMP/test-failure.t
1246 1246 +++ $TESTTMP/output/test-failure.t.err
1247 1247 @@ -1,5 +1,5 @@
1248 1248 $ echo babar
1249 1249 - rataxes
1250 1250 + babar
1251 1251 This is a noop statement so that
1252 1252 this test is still more bytes than success.
1253 1253 pad pad pad pad............................................................
1254 1254
1255 1255 ERROR: test-failure.t output changed
1256 1256 !.s
1257 1257 Skipped test-skip.t: missing feature: nail clipper
1258 1258 Failed test-failure.t: output changed
1259 1259 # Ran 2 tests, 1 skipped, 1 failed.
1260 1260 python hash seed: * (glob)
1261 1261 [1]
1262 1262 $ f report.json
1263 1263 report.json: file not found
1264 1264 $ cat output/report.json
1265 1265 testreport ={
1266 1266 "test-failure.t": [\{] (re)
1267 1267 "csys": "\s*\d+\.\d{3,4}", ? (re)
1268 1268 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1269 1269 "diff": "---.+\+\+\+.+", ? (re)
1270 1270 "end": "\s*\d+\.\d{3,4}", ? (re)
1271 1271 "result": "failure", ? (re)
1272 1272 "start": "\s*\d+\.\d{3,4}", ? (re)
1273 1273 "time": "\s*\d+\.\d{3,4}" (re)
1274 1274 }, ? (re)
1275 1275 "test-skip.t": {
1276 1276 "csys": "\s*\d+\.\d{3,4}", ? (re)
1277 1277 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1278 1278 "diff": "", ? (re)
1279 1279 "end": "\s*\d+\.\d{3,4}", ? (re)
1280 1280 "result": "skip", ? (re)
1281 1281 "start": "\s*\d+\.\d{3,4}", ? (re)
1282 1282 "time": "\s*\d+\.\d{3,4}" (re)
1283 1283 }, ? (re)
1284 1284 "test-success.t": [\{] (re)
1285 1285 "csys": "\s*\d+\.\d{3,4}", ? (re)
1286 1286 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1287 1287 "diff": "", ? (re)
1288 1288 "end": "\s*\d+\.\d{3,4}", ? (re)
1289 1289 "result": "success", ? (re)
1290 1290 "start": "\s*\d+\.\d{3,4}", ? (re)
1291 1291 "time": "\s*\d+\.\d{3,4}" (re)
1292 1292 }
1293 1293 } (no-eol)
1294 1294 $ ls -a output
1295 1295 .
1296 1296 ..
1297 1297 .testtimes
1298 1298 report.json
1299 1299 test-failure.t.err
1300 1300
1301 1301 Test that failed test accepted through interactive are properly reported:
1302 1302
1303 1303 $ cp test-failure.t backup
1304 1304 $ echo y | rt --json -i
1305 1305 running 3 tests using 1 parallel processes
1306 1306
1307 1307 --- $TESTTMP/test-failure.t
1308 1308 +++ $TESTTMP/test-failure.t.err
1309 1309 @@ -1,5 +1,5 @@
1310 1310 $ echo babar
1311 1311 - rataxes
1312 1312 + babar
1313 1313 This is a noop statement so that
1314 1314 this test is still more bytes than success.
1315 1315 pad pad pad pad............................................................
1316 1316 Accept this change? [y/N] ..s
1317 1317 Skipped test-skip.t: missing feature: nail clipper
1318 1318 # Ran 2 tests, 1 skipped, 0 failed.
1319 1319
1320 1320 $ cat report.json
1321 1321 testreport ={
1322 1322 "test-failure.t": [\{] (re)
1323 1323 "csys": "\s*\d+\.\d{3,4}", ? (re)
1324 1324 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1325 1325 "diff": "", ? (re)
1326 1326 "end": "\s*\d+\.\d{3,4}", ? (re)
1327 1327 "result": "success", ? (re)
1328 1328 "start": "\s*\d+\.\d{3,4}", ? (re)
1329 1329 "time": "\s*\d+\.\d{3,4}" (re)
1330 1330 }, ? (re)
1331 1331 "test-skip.t": {
1332 1332 "csys": "\s*\d+\.\d{3,4}", ? (re)
1333 1333 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1334 1334 "diff": "", ? (re)
1335 1335 "end": "\s*\d+\.\d{3,4}", ? (re)
1336 1336 "result": "skip", ? (re)
1337 1337 "start": "\s*\d+\.\d{3,4}", ? (re)
1338 1338 "time": "\s*\d+\.\d{3,4}" (re)
1339 1339 }, ? (re)
1340 1340 "test-success.t": [\{] (re)
1341 1341 "csys": "\s*\d+\.\d{3,4}", ? (re)
1342 1342 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1343 1343 "diff": "", ? (re)
1344 1344 "end": "\s*\d+\.\d{3,4}", ? (re)
1345 1345 "result": "success", ? (re)
1346 1346 "start": "\s*\d+\.\d{3,4}", ? (re)
1347 1347 "time": "\s*\d+\.\d{3,4}" (re)
1348 1348 }
1349 1349 } (no-eol)
1350 1350 $ mv backup test-failure.t
1351 1351
1352 1352 backslash on end of line with glob matching is handled properly
1353 1353
1354 1354 $ cat > test-glob-backslash.t << EOF
1355 1355 > $ echo 'foo bar \\'
1356 1356 > foo * \ (glob)
1357 1357 > EOF
1358 1358
1359 1359 $ rt test-glob-backslash.t
1360 1360 running 1 tests using 1 parallel processes
1361 1361 .
1362 1362 # Ran 1 tests, 0 skipped, 0 failed.
1363 1363
1364 1364 $ rm -f test-glob-backslash.t
1365 1365
1366 1366 Test globbing of local IP addresses
1367 1367 $ echo 172.16.18.1
1368 1368 $LOCALIP (glob)
1369 1369 $ echo dead:beef::1
1370 1370 $LOCALIP (glob)
1371 1371
1372 1372 Add support for external test formatter
1373 1373 =======================================
1374 1374
1375 1375 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j1 "$@" test-success.t test-failure.t
1376 1376 running 2 tests using 1 parallel processes
1377 1377
1378 1378 # Ran 2 tests, 0 skipped, 0 failed.
1379 1379 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1380 1380 FAILURE! test-failure.t output changed
1381 1381 SUCCESS! test-success.t
1382 1382 ON_END!
1383 1383
1384 1384 Test reusability for third party tools
1385 1385 ======================================
1386 1386
1387 1387 $ THISTESTDIR="$TESTDIR"
1388 1388 $ export THISTESTDIR
1389 1389 $ THISTESTTMP="$TESTTMP"
1390 1390 $ export THISTESTTMP
1391 1391
1392 1392 #if windows
1393 1393
1394 1394 $ NEWTESTDIR="$THISTESTTMP"\\anothertests
1395 1395
1396 1396 #else
1397 1397
1398 1398 $ NEWTESTDIR="$THISTESTTMP"/anothertests
1399 1399
1400 1400 #endif
1401 1401
1402 1402 $ export NEWTESTDIR
1403 1403
1404 1404 $ echo creating some new test in: $NEWTESTDIR
1405 1405 creating some new test in: $TESTTMP\anothertests (windows !)
1406 1406 creating some new test in: $TESTTMP/anothertests (no-windows !)
1407 1407 $ mkdir "$NEWTESTDIR"
1408 1408 $ cd "$NEWTESTDIR"
1409 1409
1410 1410 test that `run-tests.py` can execute hghave, even if it runs not in
1411 1411 Mercurial source tree.
1412 1412
1413 1413 $ cat > test-hghave.t <<EOF
1414 1414 > #require true
1415 1415 > $ echo foo
1416 1416 > foo
1417 1417 > EOF
1418 1418 $ rt test-hghave.t
1419 1419 running 1 tests using 1 parallel processes
1420 1420 .
1421 1421 # Ran 1 tests, 0 skipped, 0 failed.
1422 1422
1423 1423 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1424 1424 running is placed.
1425 1425
1426 1426
1427 1427 $ cat > test-runtestdir.t <<EOF
1428 1428 > # \$THISTESTDIR, in which test-run-tests.t (this test file) is placed
1429 1429 > # \$THISTESTTMP, in which test-run-tests.t (this test file) is placed
1430 1430 > # \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1431 1431 > # \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1432 1432 >
1433 1433 > $ test "\$TESTDIR" = "\$NEWTESTDIR"
1434 1434 > If this prints a path, that means RUNTESTDIR didn't equal
1435 1435 > THISTESTDIR as it should have.
1436 1436 > $ test "\$RUNTESTDIR" = "\$THISTESTDIR" || echo "\$RUNTESTDIR"
1437 1437 > This should print the start of check-code. If this passes but the
1438 1438 > previous check failed, that means we found a copy of check-code at whatever
1439 1439 > RUNTESTSDIR ended up containing, even though it doesn't match THISTESTDIR.
1440 1440 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1441 1441 > #!USRBINENVPY
1442 1442 > #
1443 1443 > # check-code - a style and portability checker for Mercurial
1444 1444 > EOF
1445 1445 $ rt test-runtestdir.t
1446 1446 running 1 tests using 1 parallel processes
1447 1447 .
1448 1448 # Ran 1 tests, 0 skipped, 0 failed.
1449 1449
1450 1450 #if execbit
1451 1451
1452 1452 test that TESTDIR is referred in PATH
1453 1453
1454 1454 $ cat > custom-command.sh <<EOF
1455 1455 > #!/bin/sh
1456 1456 > echo "hello world"
1457 1457 > EOF
1458 1458 $ chmod +x custom-command.sh
1459 1459 $ cat > test-testdir-path.t <<EOF
1460 1460 > $ custom-command.sh
1461 1461 > hello world
1462 1462 > EOF
1463 1463 $ rt test-testdir-path.t
1464 1464 running 1 tests using 1 parallel processes
1465 1465 .
1466 1466 # Ran 1 tests, 0 skipped, 0 failed.
1467 1467
1468 1468 #endif
1469 1469
1470 1470 test support for --allow-slow-tests
1471 1471 $ cat > test-very-slow-test.t <<EOF
1472 1472 > #require slow
1473 1473 > $ echo pass
1474 1474 > pass
1475 1475 > EOF
1476 1476 $ rt test-very-slow-test.t
1477 1477 running 1 tests using 1 parallel processes
1478 1478 s
1479 1479 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1480 1480 # Ran 0 tests, 1 skipped, 0 failed.
1481 1481 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1482 1482 running 1 tests using 1 parallel processes
1483 1483 .
1484 1484 # Ran 1 tests, 0 skipped, 0 failed.
1485 1485
1486 1486 support for running a test outside the current directory
1487 1487 $ mkdir nonlocal
1488 1488 $ cat > nonlocal/test-is-not-here.t << EOF
1489 1489 > $ echo pass
1490 1490 > pass
1491 1491 > EOF
1492 1492 $ rt nonlocal/test-is-not-here.t
1493 1493 running 1 tests using 1 parallel processes
1494 1494 .
1495 1495 # Ran 1 tests, 0 skipped, 0 failed.
1496 1496
1497 1497 support for automatically discovering test if arg is a folder
1498 1498 $ mkdir tmp && cd tmp
1499 1499
1500 1500 $ cat > test-uno.t << EOF
1501 1501 > $ echo line
1502 1502 > line
1503 1503 > EOF
1504 1504
1505 1505 $ cp test-uno.t test-dos.t
1506 1506 $ cd ..
1507 1507 $ cp -R tmp tmpp
1508 1508 $ cp tmp/test-uno.t test-solo.t
1509 1509
1510 1510 $ rt tmp/ test-solo.t tmpp
1511 1511 running 5 tests using 1 parallel processes
1512 1512 .....
1513 1513 # Ran 5 tests, 0 skipped, 0 failed.
1514 1514 $ rm -rf tmp tmpp
1515 1515
1516 1516 support for running run-tests.py from another directory
1517 1517 $ mkdir tmp && cd tmp
1518 1518
1519 1519 $ cat > useful-file.sh << EOF
1520 1520 > important command
1521 1521 > EOF
1522 1522
1523 1523 $ cat > test-folder.t << EOF
1524 1524 > $ cat \$TESTDIR/useful-file.sh
1525 1525 > important command
1526 1526 > EOF
1527 1527
1528 1528 $ cat > test-folder-fail.t << EOF
1529 1529 > $ cat \$TESTDIR/useful-file.sh
1530 1530 > important commando
1531 1531 > EOF
1532 1532
1533 1533 $ cd ..
1534 1534 $ rt tmp/test-*.t
1535 1535 running 2 tests using 1 parallel processes
1536 1536
1537 1537 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1538 1538 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1539 1539 @@ -1,2 +1,2 @@
1540 1540 $ cat $TESTDIR/useful-file.sh
1541 1541 - important commando
1542 1542 + important command
1543 1543
1544 1544 ERROR: test-folder-fail.t output changed
1545 1545 !.
1546 1546 Failed test-folder-fail.t: output changed
1547 1547 # Ran 2 tests, 0 skipped, 1 failed.
1548 1548 python hash seed: * (glob)
1549 1549 [1]
1550 1550
1551 1551 support for bisecting failed tests automatically
1552 1552 $ hg init bisect
1553 1553 $ cd bisect
1554 1554 $ cat >> test-bisect.t <<EOF
1555 1555 > $ echo pass
1556 1556 > pass
1557 1557 > EOF
1558 1558 $ hg add test-bisect.t
1559 1559 $ hg ci -m 'good'
1560 1560 $ cat >> test-bisect.t <<EOF
1561 1561 > $ echo pass
1562 1562 > fail
1563 1563 > EOF
1564 1564 $ hg ci -m 'bad'
1565 1565 $ rt --known-good-rev=0 test-bisect.t
1566 1566 running 1 tests using 1 parallel processes
1567 1567
1568 1568 --- $TESTTMP/anothertests/bisect/test-bisect.t
1569 1569 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1570 1570 @@ -1,4 +1,4 @@
1571 1571 $ echo pass
1572 1572 pass
1573 1573 $ echo pass
1574 1574 - fail
1575 1575 + pass
1576 1576
1577 1577 ERROR: test-bisect.t output changed
1578 1578 !
1579 1579 Failed test-bisect.t: output changed
1580 1580 test-bisect.t broken by 72cbf122d116 (bad)
1581 1581 # Ran 1 tests, 0 skipped, 1 failed.
1582 1582 python hash seed: * (glob)
1583 1583 [1]
1584 1584
1585 1585 $ cd ..
1586 1586
1587 1587 support bisecting a separate repo
1588 1588
1589 1589 $ hg init bisect-dependent
1590 1590 $ cd bisect-dependent
1591 1591 $ cat > test-bisect-dependent.t <<EOF
1592 1592 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1593 1593 > pass
1594 1594 > EOF
1595 1595 $ hg commit -Am dependent test-bisect-dependent.t
1596 1596
1597 1597 $ rt --known-good-rev=0 test-bisect-dependent.t
1598 1598 running 1 tests using 1 parallel processes
1599 1599
1600 1600 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1601 1601 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1602 1602 @@ -1,2 +1,2 @@
1603 1603 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1604 1604 - pass
1605 1605 + fail
1606 1606
1607 1607 ERROR: test-bisect-dependent.t output changed
1608 1608 !
1609 1609 Failed test-bisect-dependent.t: output changed
1610 1610 Failed to identify failure point for test-bisect-dependent.t
1611 1611 # Ran 1 tests, 0 skipped, 1 failed.
1612 1612 python hash seed: * (glob)
1613 1613 [1]
1614 1614
1615 1615 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1616 1616 usage: run-tests.py [options] [tests]
1617 1617 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1618 1618 [2]
1619 1619
1620 1620 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1621 1621 running 1 tests using 1 parallel processes
1622 1622
1623 1623 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1624 1624 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1625 1625 @@ -1,2 +1,2 @@
1626 1626 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1627 1627 - pass
1628 1628 + fail
1629 1629
1630 1630 ERROR: test-bisect-dependent.t output changed
1631 1631 !
1632 1632 Failed test-bisect-dependent.t: output changed
1633 1633 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1634 1634 # Ran 1 tests, 0 skipped, 1 failed.
1635 1635 python hash seed: * (glob)
1636 1636 [1]
1637 1637
1638 1638 $ cd ..
1639 1639
1640 1640 Test a broken #if statement doesn't break run-tests threading.
1641 1641 ==============================================================
1642 1642 $ mkdir broken
1643 1643 $ cd broken
1644 1644 $ cat > test-broken.t <<EOF
1645 1645 > true
1646 1646 > #if notarealhghavefeature
1647 1647 > $ false
1648 1648 > #endif
1649 1649 > EOF
1650 1650 $ for f in 1 2 3 4 ; do
1651 1651 > cat > test-works-$f.t <<EOF
1652 1652 > This is test case $f
1653 1653 > $ sleep 1
1654 1654 > EOF
1655 1655 > done
1656 1656 $ rt -j 2
1657 1657 running 5 tests using 2 parallel processes
1658 1658 ....
1659 1659 # Ran 5 tests, 0 skipped, 0 failed.
1660 1660 skipped: unknown feature: notarealhghavefeature
1661 1661
1662 1662 $ cd ..
1663 1663 $ rm -rf broken
1664 1664
1665 1665 Test cases in .t files
1666 1666 ======================
1667 1667 $ mkdir cases
1668 1668 $ cd cases
1669 1669 $ cat > test-cases-abc.t <<'EOF'
1670 1670 > #testcases A B C
1671 1671 > $ V=B
1672 1672 > #if A
1673 1673 > $ V=A
1674 1674 > #endif
1675 1675 > #if C
1676 1676 > $ V=C
1677 1677 > #endif
1678 1678 > $ echo $V | sed 's/A/C/'
1679 1679 > C
1680 1680 > #if C
1681 1681 > $ [ $V = C ]
1682 1682 > #endif
1683 1683 > #if A
1684 1684 > $ [ $V = C ]
1685 1685 > [1]
1686 1686 > #endif
1687 1687 > #if no-C
1688 1688 > $ [ $V = C ]
1689 1689 > [1]
1690 1690 > #endif
1691 1691 > $ [ $V = D ]
1692 1692 > [1]
1693 1693 > EOF
1694 1694 $ rt
1695 1695 running 3 tests using 1 parallel processes
1696 1696 .
1697 1697 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1698 1698 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1699 1699 @@ -7,7 +7,7 @@
1700 1700 $ V=C
1701 1701 #endif
1702 1702 $ echo $V | sed 's/A/C/'
1703 1703 - C
1704 1704 + B
1705 1705 #if C
1706 1706 $ [ $V = C ]
1707 1707 #endif
1708 1708
1709 1709 ERROR: test-cases-abc.t#B output changed
1710 1710 !.
1711 1711 Failed test-cases-abc.t#B: output changed
1712 1712 # Ran 3 tests, 0 skipped, 1 failed.
1713 1713 python hash seed: * (glob)
1714 1714 [1]
1715 1715
1716 1716 --restart works
1717 1717
1718 1718 $ rt --restart
1719 1719 running 2 tests using 1 parallel processes
1720 1720
1721 1721 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1722 1722 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1723 1723 @@ -7,7 +7,7 @@
1724 1724 $ V=C
1725 1725 #endif
1726 1726 $ echo $V | sed 's/A/C/'
1727 1727 - C
1728 1728 + B
1729 1729 #if C
1730 1730 $ [ $V = C ]
1731 1731 #endif
1732 1732
1733 1733 ERROR: test-cases-abc.t#B output changed
1734 1734 !.
1735 1735 Failed test-cases-abc.t#B: output changed
1736 1736 # Ran 2 tests, 0 skipped, 1 failed.
1737 1737 python hash seed: * (glob)
1738 1738 [1]
1739 1739
1740 1740 --restart works with outputdir
1741 1741
1742 1742 $ mkdir output
1743 1743 $ mv test-cases-abc.t#B.err output
1744 1744 $ rt --restart --outputdir output
1745 1745 running 2 tests using 1 parallel processes
1746 1746
1747 1747 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1748 1748 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1749 1749 @@ -7,7 +7,7 @@
1750 1750 $ V=C
1751 1751 #endif
1752 1752 $ echo $V | sed 's/A/C/'
1753 1753 - C
1754 1754 + B
1755 1755 #if C
1756 1756 $ [ $V = C ]
1757 1757 #endif
1758 1758
1759 1759 ERROR: test-cases-abc.t#B output changed
1760 1760 !.
1761 1761 Failed test-cases-abc.t#B: output changed
1762 1762 # Ran 2 tests, 0 skipped, 1 failed.
1763 1763 python hash seed: * (glob)
1764 1764 [1]
1765 1765
1766 1766 Test TESTCASE variable
1767 1767
1768 1768 $ cat > test-cases-ab.t <<'EOF'
1769 1769 > $ dostuff() {
1770 1770 > > echo "In case $TESTCASE"
1771 1771 > > }
1772 1772 > #testcases A B
1773 1773 > #if A
1774 1774 > $ dostuff
1775 1775 > In case A
1776 1776 > #endif
1777 1777 > #if B
1778 1778 > $ dostuff
1779 1779 > In case B
1780 1780 > #endif
1781 1781 > EOF
1782 1782 $ rt test-cases-ab.t
1783 1783 running 2 tests using 1 parallel processes
1784 1784 ..
1785 1785 # Ran 2 tests, 0 skipped, 0 failed.
1786 1786
1787 1787 Support running a specific test case
1788 1788
1789 1789 $ rt "test-cases-abc.t#B"
1790 1790 running 1 tests using 1 parallel processes
1791 1791
1792 1792 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1793 1793 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1794 1794 @@ -7,7 +7,7 @@
1795 1795 $ V=C
1796 1796 #endif
1797 1797 $ echo $V | sed 's/A/C/'
1798 1798 - C
1799 1799 + B
1800 1800 #if C
1801 1801 $ [ $V = C ]
1802 1802 #endif
1803 1803
1804 1804 ERROR: test-cases-abc.t#B output changed
1805 1805 !
1806 1806 Failed test-cases-abc.t#B: output changed
1807 1807 # Ran 1 tests, 0 skipped, 1 failed.
1808 1808 python hash seed: * (glob)
1809 1809 [1]
1810 1810
1811 1811 Support running multiple test cases in the same file
1812 1812
1813 1813 $ rt test-cases-abc.t#B test-cases-abc.t#C
1814 1814 running 2 tests using 1 parallel processes
1815 1815
1816 1816 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1817 1817 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1818 1818 @@ -7,7 +7,7 @@
1819 1819 $ V=C
1820 1820 #endif
1821 1821 $ echo $V | sed 's/A/C/'
1822 1822 - C
1823 1823 + B
1824 1824 #if C
1825 1825 $ [ $V = C ]
1826 1826 #endif
1827 1827
1828 1828 ERROR: test-cases-abc.t#B output changed
1829 1829 !.
1830 1830 Failed test-cases-abc.t#B: output changed
1831 1831 # Ran 2 tests, 0 skipped, 1 failed.
1832 1832 python hash seed: * (glob)
1833 1833 [1]
1834 1834
1835 1835 Support ignoring invalid test cases
1836 1836
1837 1837 $ rt test-cases-abc.t#B test-cases-abc.t#D
1838 1838 running 1 tests using 1 parallel processes
1839 1839
1840 1840 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1841 1841 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1842 1842 @@ -7,7 +7,7 @@
1843 1843 $ V=C
1844 1844 #endif
1845 1845 $ echo $V | sed 's/A/C/'
1846 1846 - C
1847 1847 + B
1848 1848 #if C
1849 1849 $ [ $V = C ]
1850 1850 #endif
1851 1851
1852 1852 ERROR: test-cases-abc.t#B output changed
1853 1853 !
1854 1854 Failed test-cases-abc.t#B: output changed
1855 1855 # Ran 1 tests, 0 skipped, 1 failed.
1856 1856 python hash seed: * (glob)
1857 1857 [1]
1858 1858
1859 1859 Support running complex test cases names
1860 1860
1861 1861 $ cat > test-cases-advanced-cases.t <<'EOF'
1862 1862 > #testcases simple case-with-dashes casewith_-.chars
1863 1863 > $ echo $TESTCASE
1864 1864 > simple
1865 1865 > EOF
1866 1866
1867 1867 $ cat test-cases-advanced-cases.t
1868 1868 #testcases simple case-with-dashes casewith_-.chars
1869 1869 $ echo $TESTCASE
1870 1870 simple
1871 1871
1872 1872 $ rt test-cases-advanced-cases.t
1873 1873 running 3 tests using 1 parallel processes
1874 1874
1875 1875 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1876 1876 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1877 1877 @@ -1,3 +1,3 @@
1878 1878 #testcases simple case-with-dashes casewith_-.chars
1879 1879 $ echo $TESTCASE
1880 1880 - simple
1881 1881 + case-with-dashes
1882 1882
1883 1883 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1884 1884 !
1885 1885 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1886 1886 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1887 1887 @@ -1,3 +1,3 @@
1888 1888 #testcases simple case-with-dashes casewith_-.chars
1889 1889 $ echo $TESTCASE
1890 1890 - simple
1891 1891 + casewith_-.chars
1892 1892
1893 1893 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1894 1894 !.
1895 1895 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1896 1896 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1897 1897 # Ran 3 tests, 0 skipped, 2 failed.
1898 1898 python hash seed: * (glob)
1899 1899 [1]
1900 1900
1901 1901 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1902 1902 running 1 tests using 1 parallel processes
1903 1903
1904 1904 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1905 1905 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1906 1906 @@ -1,3 +1,3 @@
1907 1907 #testcases simple case-with-dashes casewith_-.chars
1908 1908 $ echo $TESTCASE
1909 1909 - simple
1910 1910 + case-with-dashes
1911 1911
1912 1912 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1913 1913 !
1914 1914 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1915 1915 # Ran 1 tests, 0 skipped, 1 failed.
1916 1916 python hash seed: * (glob)
1917 1917 [1]
1918 1918
1919 1919 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1920 1920 running 1 tests using 1 parallel processes
1921 1921
1922 1922 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1923 1923 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1924 1924 @@ -1,3 +1,3 @@
1925 1925 #testcases simple case-with-dashes casewith_-.chars
1926 1926 $ echo $TESTCASE
1927 1927 - simple
1928 1928 + casewith_-.chars
1929 1929
1930 1930 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1931 1931 !
1932 1932 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1933 1933 # Ran 1 tests, 0 skipped, 1 failed.
1934 1934 python hash seed: * (glob)
1935 1935 [1]
1936 1936
1937 1937 Test automatic pattern replacement
1938 1938 ==================================
1939 1939
1940 1940 $ cat << EOF >> common-pattern.py
1941 1941 > substitutions = [
1942 1942 > (br'foo-(.*)\\b',
1943 1943 > br'\$XXX=\\1\$'),
1944 1944 > (br'bar\\n',
1945 1945 > br'\$YYY$\\n'),
1946 1946 > ]
1947 1947 > EOF
1948 1948
1949 1949 $ cat << EOF >> test-substitution.t
1950 1950 > $ echo foo-12
1951 1951 > \$XXX=12$
1952 1952 > $ echo foo-42
1953 1953 > \$XXX=42$
1954 1954 > $ echo bar prior
1955 1955 > bar prior
1956 1956 > $ echo lastbar
1957 1957 > last\$YYY$
1958 1958 > $ echo foo-bar foo-baz
1959 1959 > EOF
1960 1960
1961 1961 $ rt test-substitution.t
1962 1962 running 1 tests using 1 parallel processes
1963 1963
1964 1964 --- $TESTTMP/anothertests/cases/test-substitution.t
1965 1965 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1966 1966 @@ -7,3 +7,4 @@
1967 1967 $ echo lastbar
1968 1968 last$YYY$
1969 1969 $ echo foo-bar foo-baz
1970 1970 + $XXX=bar foo-baz$
1971 1971
1972 1972 ERROR: test-substitution.t output changed
1973 1973 !
1974 1974 Failed test-substitution.t: output changed
1975 1975 # Ran 1 tests, 0 skipped, 1 failed.
1976 1976 python hash seed: * (glob)
1977 1977 [1]
1978 1978
1979 1979 --extra-config-opt works
1980 1980
1981 1981 $ cat << EOF >> test-config-opt.t
1982 1982 > $ hg init test-config-opt
1983 1983 > $ hg -R test-config-opt purge
1984 1984 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1985 1985 > HGTESTEXTRAEXTENSIONS: purge
1986 1986 > EOF
1987 1987
1988 1988 $ rt --extra-config-opt extensions.purge= \
1989 1989 > --extra-config-opt not.an.extension=True test-config-opt.t
1990 1990 running 1 tests using 1 parallel processes
1991 1991 .
1992 1992 # Ran 1 tests, 0 skipped, 0 failed.
1993 1993
1994 1994 Test conditional output matching
1995 1995 ================================
1996 1996
1997 1997 $ cat << EOF >> test-conditional-matching.t
1998 1998 > #testcases foo bar
1999 1999 > $ echo richtig
2000 2000 > richtig (true !)
2001 2001 > $ echo falsch
2002 2002 > falsch (false !)
2003 2003 > #if foo
2004 2004 > $ echo arthur
2005 2005 > arthur (bar !)
2006 2006 > #endif
2007 2007 > $ echo celeste
2008 2008 > celeste (foo !)
2009 2009 > $ echo zephir
2010 2010 > zephir (bar !)
2011 2011 > EOF
2012 2012
2013 2013 $ rt test-conditional-matching.t
2014 2014 running 2 tests using 1 parallel processes
2015 2015
2016 2016 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2017 2017 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
2018 2018 @@ -3,11 +3,13 @@
2019 2019 richtig (true !)
2020 2020 $ echo falsch
2021 2021 falsch (false !)
2022 2022 + falsch
2023 2023 #if foo
2024 2024 $ echo arthur
2025 2025 arthur \(bar !\) (re)
2026 2026 #endif
2027 2027 $ echo celeste
2028 2028 celeste \(foo !\) (re)
2029 2029 + celeste
2030 2030 $ echo zephir
2031 2031 zephir \(bar !\) (re)
2032 2032
2033 2033 ERROR: test-conditional-matching.t#bar output changed
2034 2034 !
2035 2035 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2036 2036 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2037 2037 @@ -3,11 +3,14 @@
2038 2038 richtig (true !)
2039 2039 $ echo falsch
2040 2040 falsch (false !)
2041 2041 + falsch
2042 2042 #if foo
2043 2043 $ echo arthur
2044 2044 arthur \(bar !\) (re)
2045 2045 + arthur
2046 2046 #endif
2047 2047 $ echo celeste
2048 2048 celeste \(foo !\) (re)
2049 2049 $ echo zephir
2050 2050 zephir \(bar !\) (re)
2051 2051 + zephir
2052 2052
2053 2053 ERROR: test-conditional-matching.t#foo output changed
2054 2054 !
2055 2055 Failed test-conditional-matching.t#bar: output changed
2056 2056 Failed test-conditional-matching.t#foo: output changed
2057 2057 # Ran 2 tests, 0 skipped, 2 failed.
2058 2058 python hash seed: * (glob)
2059 2059 [1]
2060 2060
2061 2061 Test that a proper "python" has been set up
2062 2062 ===========================================
2063 2063
2064 2064 (with a small check-code work around)
2065 2065 $ printf "#!/usr/bi" > test-py3.tmp
2066 2066 $ printf "n/en" >> test-py3.tmp
2067 2067 $ cat << EOF >> test-py3.tmp
2068 2068 > v python3
2069 2069 > import sys
2070 2070 > print('.'.join(str(x) for x in sys.version_info))
2071 2071 > EOF
2072 2072 $ mv test-py3.tmp test-py3.py
2073 2073 $ chmod +x test-py3.py
2074 2074
2075 2075 (with a small check-code work around)
2076 2076 $ printf "#!/usr/bi" > test-py.tmp
2077 2077 $ printf "n/en" >> test-py.tmp
2078 2078 $ cat << EOF >> test-py.tmp
2079 2079 > v python
2080 2080 > import sys
2081 2081 > print('.'.join(str(x) for x in sys.version_info))
2082 2082 > EOF
2083 2083 $ mv test-py.tmp test-py.py
2084 2084 $ chmod +x test-py.py
2085 2085
2086 2086 $ ./test-py3.py
2087 2087 3.* (glob)
2088 2088 $ ./test-py.py
2089 2089 3.* (glob)
General Comments 0
You need to be logged in to leave comments. Login now