comparison tests/run-tests.py @ 21521:855f092c96e5

run-tests: move diff generation into TestResult TestResult is the thing that captures all our test results. It's logical for diff viewing to be handled there and not inside Test. While writing this patch, it was discovered that Test.fail() was printing redundant test result output. This has been removed. Arguments controlling diffs have been removed from Test.__init__.
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 22 Apr 2014 13:16:34 -0700
parents fa6ba4372678
children eeaec308ad5f
comparison
equal deleted inserted replaced
21520:fa6ba4372678 21521:855f092c96e5
280 for existing destination support. 280 for existing destination support.
281 """ 281 """
282 shutil.copy(src, dst) 282 shutil.copy(src, dst)
283 os.remove(src) 283 os.remove(src)
284 284
285 def showdiff(expected, output, ref, err): 285 def getdiff(expected, output, ref, err):
286 print
287 servefail = False 286 servefail = False
287 lines = []
288 for line in difflib.unified_diff(expected, output, ref, err): 288 for line in difflib.unified_diff(expected, output, ref, err):
289 sys.stdout.write(line) 289 lines.append(line)
290 if not servefail and line.startswith( 290 if not servefail and line.startswith(
291 '+ abort: child process failed to start'): 291 '+ abort: child process failed to start'):
292 servefail = True 292 servefail = True
293 return {'servefail': servefail} 293
294 294 return servefail, lines
295 295
296 verbose = False 296 verbose = False
297 def vlog(*msg): 297 def vlog(*msg):
298 if verbose is not False: 298 if verbose is not False:
299 iolock.acquire() 299 iolock.acquire()
337 337
338 # Status code reserved for skipped tests (used by hghave). 338 # Status code reserved for skipped tests (used by hghave).
339 SKIPPED_STATUS = 80 339 SKIPPED_STATUS = 80
340 340
341 def __init__(self, path, tmpdir, keeptmpdir=False, 341 def __init__(self, path, tmpdir, keeptmpdir=False,
342 debug=False, nodiff=False, diffviewer=None, 342 debug=False,
343 interactive=False, timeout=defaults['timeout'], 343 interactive=False, timeout=defaults['timeout'],
344 startport=defaults['port'], extraconfigopts=None, 344 startport=defaults['port'], extraconfigopts=None,
345 py3kwarnings=False, shell=None): 345 py3kwarnings=False, shell=None):
346 """Create a test from parameters. 346 """Create a test from parameters.
347 347
353 after execution. It defaults to removal (False). 353 after execution. It defaults to removal (False).
354 354
355 debug mode will make the test execute verbosely, with unfiltered 355 debug mode will make the test execute verbosely, with unfiltered
356 output. 356 output.
357 357
358 nodiff will suppress the printing of a diff when output changes.
359
360 diffviewer is the program that should be used to display diffs. Only
361 used when output changes.
362
363 interactive controls whether the test will run interactively. 358 interactive controls whether the test will run interactively.
364 359
365 timeout controls the maximum run time of the test. It is ignored when 360 timeout controls the maximum run time of the test. It is ignored when
366 debug is True. 361 debug is True.
367 362
385 self.errpath = os.path.join(self._testdir, '%s.err' % self.name) 380 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
386 381
387 self._threadtmp = tmpdir 382 self._threadtmp = tmpdir
388 self._keeptmpdir = keeptmpdir 383 self._keeptmpdir = keeptmpdir
389 self._debug = debug 384 self._debug = debug
390 self._nodiff = nodiff
391 self._diffviewer = diffviewer
392 self._interactive = interactive 385 self._interactive = interactive
393 self._timeout = timeout 386 self._timeout = timeout
394 self._startport = startport 387 self._startport = startport
395 self._extraconfigopts = extraconfigopts or [] 388 self._extraconfigopts = extraconfigopts or []
396 self._py3kwarnings = py3kwarnings 389 self._py3kwarnings = py3kwarnings
406 399
407 # If we're not in --debug mode and reference output file exists, 400 # If we're not in --debug mode and reference output file exists,
408 # check test output against it. 401 # check test output against it.
409 if debug: 402 if debug:
410 self._refout = None # to match "out is None" 403 self._refout = None # to match "out is None"
411 elif os.path.exists(self._refpath): 404 elif os.path.exists(self.refpath):
412 f = open(self._refpath, 'r') 405 f = open(self.refpath, 'r')
413 self._refout = f.read().splitlines(True) 406 self._refout = f.read().splitlines(True)
414 f.close() 407 f.close()
415 else: 408 else:
416 self._refout = [] 409 self._refout = []
417 410
441 # Remove any previous output files. 434 # Remove any previous output files.
442 if os.path.exists(self.errpath): 435 if os.path.exists(self.errpath):
443 os.remove(self.errpath) 436 os.remove(self.errpath)
444 437
445 def run(self, result): 438 def run(self, result):
439 self._result = result
446 result.startTest(self) 440 result.startTest(self)
447 try: 441 try:
448 try: 442 try:
449 self.setUp() 443 self.setUp()
450 except (KeyboardInterrupt, SystemExit): 444 except (KeyboardInterrupt, SystemExit):
531 self._skipped = True 525 self._skipped = True
532 raise SkipTest(missing[-1]) 526 raise SkipTest(missing[-1])
533 elif ret == 'timeout': 527 elif ret == 'timeout':
534 self.fail('timed out', ret) 528 self.fail('timed out', ret)
535 elif out != self._refout: 529 elif out != self._refout:
536 info = {} 530 # The result object handles diff calculation for us.
537 if not self._nodiff: 531 self._result.addOutputMismatch(self, out, self._refout)
538 iolock.acquire() 532
539 if self._diffviewer:
540 os.system("%s %s %s" % (self._diffviewer, self._refpath,
541 self.errpath))
542 else:
543 info = showdiff(self._refout, out, self._refpath,
544 self.errpath)
545 iolock.release()
546 msg = ''
547 if info.get('servefail'):
548 msg += 'serve failed and '
549 if ret: 533 if ret:
550 msg += 'output changed and ' + describe(ret) 534 msg = 'output changed and ' + describe(ret)
551 else: 535 else:
552 msg += 'output changed' 536 msg = 'output changed'
553 537
554 if (ret != 0 or out != self._refout) and not self._skipped \ 538 if (ret != 0 or out != self._refout) and not self._skipped \
555 and not self._debug: 539 and not self._debug:
556 f = open(self.errpath, 'wb') 540 f = open(self.errpath, 'wb')
557 for line in out: 541 for line in out:
659 hgrc.write('[%s]\n%s\n' % (section, key)) 643 hgrc.write('[%s]\n%s\n' % (section, key))
660 hgrc.close() 644 hgrc.close()
661 645
662 def fail(self, msg, ret): 646 def fail(self, msg, ret):
663 warned = ret is False 647 warned = ret is False
664 if not self._nodiff:
665 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
666 msg))
667 if (not ret and self._interactive and 648 if (not ret and self._interactive and
668 os.path.exists(self.errpath)): 649 os.path.exists(self.errpath)):
669 iolock.acquire() 650 iolock.acquire()
670 print 'Accept this change? [n] ', 651 print 'Accept this change? [n] ',
671 answer = sys.stdin.readline().strip() 652 answer = sys.stdin.readline().strip()
687 668
688 class PythonTest(Test): 669 class PythonTest(Test):
689 """A Python-based test.""" 670 """A Python-based test."""
690 671
691 @property 672 @property
692 def _refpath(self): 673 def refpath(self):
693 return os.path.join(self._testdir, '%s.out' % self.name) 674 return os.path.join(self._testdir, '%s.out' % self.name)
694 675
695 def _run(self, replacements, env): 676 def _run(self, replacements, env):
696 py3kswitch = self._py3kwarnings and ' -3' or '' 677 py3kswitch = self._py3kwarnings and ' -3' or ''
697 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path) 678 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
715 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub 696 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
716 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update( 697 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
717 {'\\': '\\\\', '\r': r'\r'}) 698 {'\\': '\\\\', '\r': r'\r'})
718 699
719 @property 700 @property
720 def _refpath(self): 701 def refpath(self):
721 return os.path.join(self._testdir, self.name) 702 return os.path.join(self._testdir, self.name)
722 703
723 def _run(self, replacements, env): 704 def _run(self, replacements, env):
724 f = open(self.path) 705 f = open(self.path)
725 lines = f.readlines() 706 lines = f.readlines()
1138 self.stream.writeln('warned %s' % reason) 1119 self.stream.writeln('warned %s' % reason)
1139 else: 1120 else:
1140 self.stream.write('~') 1121 self.stream.write('~')
1141 self.stream.flush() 1122 self.stream.flush()
1142 1123
1124 def addOutputMismatch(self, test, got, expected):
1125 """Record a mismatch in test output for a particular test."""
1126
1127 if self._options.nodiff:
1128 return
1129
1130 if self._options.view:
1131 os.system("%s %s %s" % (self._view, test.refpath, test.errpath))
1132 else:
1133 failed, lines = getdiff(expected, got,
1134 test.refpath, test.errpath)
1135 if failed:
1136 self.addFailure(test, 'diff generation failed')
1137 else:
1138 self.stream.write('\n')
1139 for line in lines:
1140 self.stream.write(line)
1141 self.stream.flush()
1142
1143 def startTest(self, test): 1143 def startTest(self, test):
1144 super(TestResult, self).startTest(test) 1144 super(TestResult, self).startTest(test)
1145 1145
1146 self._started[test.name] = time.time() 1146 self._started[test.name] = time.time()
1147 1147
1524 tmpdir = os.path.join(self.hgtmp, 'child%d' % count) 1524 tmpdir = os.path.join(self.hgtmp, 'child%d' % count)
1525 1525
1526 return testcls(refpath, tmpdir, 1526 return testcls(refpath, tmpdir,
1527 keeptmpdir=self.options.keep_tmpdir, 1527 keeptmpdir=self.options.keep_tmpdir,
1528 debug=self.options.debug, 1528 debug=self.options.debug,
1529 nodiff = self.options.nodiff,
1530 diffviewer=self.options.view,
1531 interactive=self.options.interactive, 1529 interactive=self.options.interactive,
1532 timeout=self.options.timeout, 1530 timeout=self.options.timeout,
1533 startport=self.options.port + count * 3, 1531 startport=self.options.port + count * 3,
1534 extraconfigopts=self.options.extra_config_opt, 1532 extraconfigopts=self.options.extra_config_opt,
1535 py3kwarnings=self.options.py3k_warnings, 1533 py3kwarnings=self.options.py3k_warnings,