Home | History | Annotate | Download | only in unittests
      1 #!/usr/bin/env python
      2 # Copyright 2017 the V8 project authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """
      7 Global system tests for V8 test runners and fuzzers.
      8 
      9 This hooks up the framework under tools/testrunner testing high-level scenarios
     10 with different test suite extensions and build configurations.
     11 """
     12 
     13 # TODO(machenbach): Mock out util.GuessOS to make these tests really platform
     14 # independent.
     15 # TODO(machenbach): Move coverage recording to a global test entry point to
     16 # include other unittest suites in the coverage report.
     17 # TODO(machenbach): Coverage data from multiprocessing doesn't work.
     18 # TODO(majeski): Add some tests for the fuzzers.
     19 
     20 import collections
     21 import contextlib
     22 import json
     23 import os
     24 import shutil
     25 import subprocess
     26 import sys
     27 import tempfile
     28 import unittest
     29 
     30 from cStringIO import StringIO
     31 
     32 TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     33 TEST_DATA_ROOT = os.path.join(TOOLS_ROOT, 'unittests', 'testdata')
     34 RUN_TESTS_PY = os.path.join(TOOLS_ROOT, 'run-tests.py')
     35 
     36 Result = collections.namedtuple(
     37     'Result', ['stdout', 'stderr', 'returncode'])
     38 
     39 Result.__str__ = lambda self: (
     40     '\nReturncode: %s\nStdout:\n%s\nStderr:\n%s\n' %
     41     (self.returncode, self.stdout, self.stderr))
     42 
     43 
     44 @contextlib.contextmanager
     45 def temp_dir():
     46   """Wrapper making a temporary directory available."""
     47   path = None
     48   try:
     49     path = tempfile.mkdtemp('v8_test_')
     50     yield path
     51   finally:
     52     if path:
     53       shutil.rmtree(path)
     54 
     55 
     56 @contextlib.contextmanager
     57 def temp_base(baseroot='testroot1'):
     58   """Wrapper that sets up a temporary V8 test root.
     59 
     60   Args:
     61     baseroot: The folder with the test root blueprint. Relevant files will be
     62         copied to the temporary test root, to guarantee a fresh setup with no
     63         dirty state.
     64   """
     65   basedir = os.path.join(TEST_DATA_ROOT, baseroot)
     66   with temp_dir() as tempbase:
     67     builddir = os.path.join(tempbase, 'out', 'Release')
     68     testroot = os.path.join(tempbase, 'test')
     69     os.makedirs(builddir)
     70     shutil.copy(os.path.join(basedir, 'v8_build_config.json'), builddir)
     71     shutil.copy(os.path.join(basedir, 'd8_mocked.py'), builddir)
     72 
     73     for suite in os.listdir(os.path.join(basedir, 'test')):
     74       os.makedirs(os.path.join(testroot, suite))
     75       for entry in os.listdir(os.path.join(basedir, 'test', suite)):
     76         shutil.copy(
     77             os.path.join(basedir, 'test', suite, entry),
     78             os.path.join(testroot, suite))
     79     yield tempbase
     80 
     81 
     82 @contextlib.contextmanager
     83 def capture():
     84   """Wrapper that replaces system stdout/stderr an provides the streams."""
     85   oldout = sys.stdout
     86   olderr = sys.stderr
     87   try:
     88     stdout=StringIO()
     89     stderr=StringIO()
     90     sys.stdout = stdout
     91     sys.stderr = stderr
     92     yield stdout, stderr
     93   finally:
     94     sys.stdout = oldout
     95     sys.stderr = olderr
     96 
     97 
     98 def run_tests(basedir, *args, **kwargs):
     99   """Executes the test runner with captured output."""
    100   with capture() as (stdout, stderr):
    101     sys_args = ['--command-prefix', sys.executable] + list(args)
    102     if kwargs.get('infra_staging', False):
    103       sys_args.append('--infra-staging')
    104     else:
    105       sys_args.append('--no-infra-staging')
    106     code = standard_runner.StandardTestRunner(
    107         basedir=basedir).execute(sys_args)
    108     return Result(stdout.getvalue(), stderr.getvalue(), code)
    109 
    110 
    111 def override_build_config(basedir, **kwargs):
    112   """Override the build config with new values provided as kwargs."""
    113   path = os.path.join(basedir, 'out', 'Release', 'v8_build_config.json')
    114   with open(path) as f:
    115     config = json.load(f)
    116     config.update(kwargs)
    117   with open(path, 'w') as f:
    118     json.dump(config, f)
    119 
    120 
    121 class SystemTest(unittest.TestCase):
    122   @classmethod
    123   def setUpClass(cls):
    124     # Try to set up python coverage and run without it if not available.
    125     cls._cov = None
    126     try:
    127       import coverage
    128       if int(coverage.__version__.split('.')[0]) < 4:
    129         cls._cov = None
    130         print 'Python coverage version >= 4 required.'
    131         raise ImportError()
    132       cls._cov = coverage.Coverage(
    133           source=([os.path.join(TOOLS_ROOT, 'testrunner')]),
    134           omit=['*unittest*', '*__init__.py'],
    135       )
    136       cls._cov.exclude('raise NotImplementedError')
    137       cls._cov.exclude('if __name__ == .__main__.:')
    138       cls._cov.exclude('except TestRunnerError:')
    139       cls._cov.exclude('except KeyboardInterrupt:')
    140       cls._cov.exclude('if options.verbose:')
    141       cls._cov.exclude('if verbose:')
    142       cls._cov.exclude('pass')
    143       cls._cov.exclude('assert False')
    144       cls._cov.start()
    145     except ImportError:
    146       print 'Running without python coverage.'
    147     sys.path.append(TOOLS_ROOT)
    148     global standard_runner
    149     from testrunner import standard_runner
    150     from testrunner.local import command
    151     from testrunner.local import pool
    152     command.setup_testing()
    153     pool.setup_testing()
    154 
    155   @classmethod
    156   def tearDownClass(cls):
    157     if cls._cov:
    158       cls._cov.stop()
    159       print ''
    160       print cls._cov.report(show_missing=True)
    161 
    162   def testPass(self):
    163     """Test running only passing tests in two variants.
    164 
    165     Also test printing durations.
    166     """
    167     with temp_base() as basedir:
    168       result = run_tests(
    169           basedir,
    170           '--mode=Release',
    171           '--progress=verbose',
    172           '--variants=default,stress',
    173           '--time',
    174           'sweet/bananas',
    175           'sweet/raspberries',
    176       )
    177       self.assertIn('Running 2 base tests', result.stdout, result)
    178       self.assertIn('Done running sweet/bananas: pass', result.stdout, result)
    179       # TODO(majeski): Implement for test processors
    180       # self.assertIn('Total time:', result.stderr, result)
    181       # self.assertIn('sweet/bananas', result.stderr, result)
    182       self.assertEqual(0, result.returncode, result)
    183 
    184   def testShardedProc(self):
    185     with temp_base() as basedir:
    186       for shard in [1, 2]:
    187         result = run_tests(
    188             basedir,
    189             '--mode=Release',
    190             '--progress=verbose',
    191             '--variants=default,stress',
    192             '--shard-count=2',
    193             '--shard-run=%d' % shard,
    194             'sweet/bananas',
    195             'sweet/raspberries',
    196             infra_staging=True,
    197         )
    198         # One of the shards gets one variant of each test.
    199         self.assertIn('Running 1 base tests', result.stdout, result)
    200         self.assertIn('2 tests ran', result.stdout, result)
    201         if shard == 1:
    202           self.assertIn('Done running sweet/bananas', result.stdout, result)
    203         else:
    204           self.assertIn('Done running sweet/raspberries', result.stdout, result)
    205         self.assertEqual(0, result.returncode, result)
    206 
    207   @unittest.skip("incompatible with test processors")
    208   def testSharded(self):
    209     """Test running a particular shard."""
    210     with temp_base() as basedir:
    211       for shard in [1, 2]:
    212         result = run_tests(
    213             basedir,
    214             '--mode=Release',
    215             '--progress=verbose',
    216             '--variants=default,stress',
    217             '--shard-count=2',
    218             '--shard-run=%d' % shard,
    219             'sweet/bananas',
    220             'sweet/raspberries',
    221         )
    222         # One of the shards gets one variant of each test.
    223         self.assertIn('Running 2 tests', result.stdout, result)
    224         self.assertIn('Done running sweet/bananas', result.stdout, result)
    225         self.assertIn('Done running sweet/raspberries', result.stdout, result)
    226         self.assertEqual(0, result.returncode, result)
    227 
    228   def testFailProc(self):
    229     self.testFail(infra_staging=True)
    230 
    231   def testFail(self, infra_staging=True):
    232     """Test running only failing tests in two variants."""
    233     with temp_base() as basedir:
    234       result = run_tests(
    235           basedir,
    236           '--mode=Release',
    237           '--progress=verbose',
    238           '--variants=default,stress',
    239           'sweet/strawberries',
    240           infra_staging=infra_staging,
    241       )
    242       if not infra_staging:
    243         self.assertIn('Running 2 tests', result.stdout, result)
    244       else:
    245         self.assertIn('Running 1 base tests', result.stdout, result)
    246         self.assertIn('2 tests ran', result.stdout, result)
    247       self.assertIn('Done running sweet/strawberries: FAIL', result.stdout, result)
    248       self.assertEqual(1, result.returncode, result)
    249 
    250   def check_cleaned_json_output(self, expected_results_name, actual_json):
    251     # Check relevant properties of the json output.
    252     with open(actual_json) as f:
    253       json_output = json.load(f)[0]
    254       pretty_json = json.dumps(json_output, indent=2, sort_keys=True)
    255 
    256     # Replace duration in actual output as it's non-deterministic. Also
    257     # replace the python executable prefix as it has a different absolute
    258     # path dependent on where this runs.
    259     def replace_variable_data(data):
    260       data['duration'] = 1
    261       data['command'] = ' '.join(
    262           ['/usr/bin/python'] + data['command'].split()[1:])
    263     for data in json_output['slowest_tests']:
    264       replace_variable_data(data)
    265     for data in json_output['results']:
    266       replace_variable_data(data)
    267     json_output['duration_mean'] = 1
    268 
    269     with open(os.path.join(TEST_DATA_ROOT, expected_results_name)) as f:
    270       expected_test_results = json.load(f)
    271 
    272     msg = None  # Set to pretty_json for bootstrapping.
    273     self.assertDictEqual(json_output, expected_test_results, msg)
    274 
    275   def testFailWithRerunAndJSONProc(self):
    276     self.testFailWithRerunAndJSON(infra_staging=True)
    277 
    278   def testFailWithRerunAndJSON(self, infra_staging=True):
    279     """Test re-running a failing test and output to json."""
    280     with temp_base() as basedir:
    281       json_path = os.path.join(basedir, 'out.json')
    282       result = run_tests(
    283           basedir,
    284           '--mode=Release',
    285           '--progress=verbose',
    286           '--variants=default',
    287           '--rerun-failures-count=2',
    288           '--random-seed=123',
    289           '--json-test-results', json_path,
    290           'sweet/strawberries',
    291           infra_staging=infra_staging,
    292       )
    293       if not infra_staging:
    294         self.assertIn('Running 1 tests', result.stdout, result)
    295       else:
    296         self.assertIn('Running 1 base tests', result.stdout, result)
    297         self.assertIn('1 tests ran', result.stdout, result)
    298       self.assertIn('Done running sweet/strawberries: FAIL', result.stdout, result)
    299       if not infra_staging:
    300         # We run one test, which fails and gets re-run twice.
    301         self.assertIn('3 tests failed', result.stdout, result)
    302       else:
    303         # With test processors we don't count reruns as separated failures.
    304         # TODO(majeski): fix it?
    305         self.assertIn('1 tests failed', result.stdout, result)
    306       self.assertEqual(0, result.returncode, result)
    307 
    308       # TODO(majeski): Previously we only reported the variant flags in the
    309       # flags field of the test result.
    310       # After recent changes we report all flags, including the file names.
    311       # This is redundant to the command. Needs investigation.
    312       self.maxDiff = None
    313       self.check_cleaned_json_output('expected_test_results1.json', json_path)
    314 
    315   def testFlakeWithRerunAndJSONProc(self):
    316     self.testFlakeWithRerunAndJSON(infra_staging=True)
    317 
    318   def testFlakeWithRerunAndJSON(self, infra_staging=True):
    319     """Test re-running a failing test and output to json."""
    320     with temp_base(baseroot='testroot2') as basedir:
    321       json_path = os.path.join(basedir, 'out.json')
    322       result = run_tests(
    323           basedir,
    324           '--mode=Release',
    325           '--progress=verbose',
    326           '--variants=default',
    327           '--rerun-failures-count=2',
    328           '--random-seed=123',
    329           '--json-test-results', json_path,
    330           'sweet',
    331           infra_staging=infra_staging,
    332       )
    333       if not infra_staging:
    334         self.assertIn('Running 1 tests', result.stdout, result)
    335         self.assertIn(
    336             'Done running sweet/bananaflakes: FAIL', result.stdout, result)
    337         self.assertIn('1 tests failed', result.stdout, result)
    338       else:
    339         self.assertIn('Running 1 base tests', result.stdout, result)
    340         self.assertIn(
    341             'Done running sweet/bananaflakes: pass', result.stdout, result)
    342         self.assertIn('All tests succeeded', result.stdout, result)
    343       self.assertEqual(0, result.returncode, result)
    344       self.maxDiff = None
    345       self.check_cleaned_json_output('expected_test_results2.json', json_path)
    346 
    347   def testAutoDetect(self):
    348     """Fake a build with several auto-detected options.
    349 
    350     Using all those options at once doesn't really make much sense. This is
    351     merely for getting coverage.
    352     """
    353     with temp_base() as basedir:
    354       override_build_config(
    355           basedir, dcheck_always_on=True, is_asan=True, is_cfi=True,
    356           is_msan=True, is_tsan=True, is_ubsan_vptr=True, target_cpu='x86',
    357           v8_enable_i18n_support=False, v8_target_cpu='x86',
    358           v8_use_snapshot=False)
    359       result = run_tests(
    360           basedir,
    361           '--mode=Release',
    362           '--progress=verbose',
    363           '--variants=default',
    364           'sweet/bananas',
    365       )
    366       expect_text = (
    367           '>>> Autodetected:\n'
    368           'asan\n'
    369           'cfi_vptr\n'
    370           'dcheck_always_on\n'
    371           'msan\n'
    372           'no_i18n\n'
    373           'no_snap\n'
    374           'tsan\n'
    375           'ubsan_vptr\n'
    376           '>>> Running tests for ia32.release')
    377       self.assertIn(expect_text, result.stdout, result)
    378       self.assertEqual(0, result.returncode, result)
    379       # TODO(machenbach): Test some more implications of the auto-detected
    380       # options, e.g. that the right env variables are set.
    381 
    382   def testSkipsProc(self):
    383     self.testSkips(infra_staging=True)
    384 
    385   def testSkips(self, infra_staging=True):
    386     """Test skipping tests in status file for a specific variant."""
    387     with temp_base() as basedir:
    388       result = run_tests(
    389           basedir,
    390           '--mode=Release',
    391           '--progress=verbose',
    392           '--variants=nooptimization',
    393           'sweet/strawberries',
    394           infra_staging=infra_staging,
    395       )
    396       if not infra_staging:
    397         self.assertIn('Running 0 tests', result.stdout, result)
    398       else:
    399         self.assertIn('Running 1 base tests', result.stdout, result)
    400         self.assertIn('0 tests ran', result.stdout, result)
    401       self.assertEqual(2, result.returncode, result)
    402 
    403   def testDefaultProc(self):
    404     self.testDefault(infra_staging=True)
    405 
    406   def testDefault(self, infra_staging=True):
    407     """Test using default test suites, though no tests are run since they don't
    408     exist in a test setting.
    409     """
    410     with temp_base() as basedir:
    411       result = run_tests(
    412           basedir,
    413           '--mode=Release',
    414           infra_staging=infra_staging,
    415       )
    416       if not infra_staging:
    417         self.assertIn('Warning: no tests were run!', result.stdout, result)
    418       else:
    419         self.assertIn('Running 0 base tests', result.stdout, result)
    420         self.assertIn('0 tests ran', result.stdout, result)
    421       self.assertEqual(2, result.returncode, result)
    422 
    423   def testNoBuildConfig(self):
    424     """Test failing run when build config is not found."""
    425     with temp_base() as basedir:
    426       result = run_tests(basedir)
    427       self.assertIn('Failed to load build config', result.stdout, result)
    428       self.assertEqual(5, result.returncode, result)
    429 
    430   def testGNOption(self):
    431     """Test using gn option, but no gn build folder is found."""
    432     with temp_base() as basedir:
    433       # TODO(machenbach): This should fail gracefully.
    434       with self.assertRaises(OSError):
    435         run_tests(basedir, '--gn')
    436 
    437   def testInconsistentMode(self):
    438     """Test failing run when attempting to wrongly override the mode."""
    439     with temp_base() as basedir:
    440       override_build_config(basedir, is_debug=True)
    441       result = run_tests(basedir, '--mode=Release')
    442       self.assertIn('execution mode (release) for release is inconsistent '
    443                     'with build config (debug)', result.stdout, result)
    444       self.assertEqual(5, result.returncode, result)
    445 
    446   def testInconsistentArch(self):
    447     """Test failing run when attempting to wrongly override the arch."""
    448     with temp_base() as basedir:
    449       result = run_tests(basedir, '--mode=Release', '--arch=ia32')
    450       self.assertIn(
    451           '--arch value (ia32) inconsistent with build config (x64).',
    452           result.stdout, result)
    453       self.assertEqual(5, result.returncode, result)
    454 
    455   def testWrongVariant(self):
    456     """Test using a bogus variant."""
    457     with temp_base() as basedir:
    458       result = run_tests(basedir, '--mode=Release', '--variants=meh')
    459       self.assertEqual(5, result.returncode, result)
    460 
    461   def testModeFromBuildConfig(self):
    462     """Test auto-detection of mode from build config."""
    463     with temp_base() as basedir:
    464       result = run_tests(basedir, '--outdir=out/Release', 'sweet/bananas')
    465       self.assertIn('Running tests for x64.release', result.stdout, result)
    466       self.assertEqual(0, result.returncode, result)
    467 
    468   @unittest.skip("not available with test processors")
    469   def testReport(self):
    470     """Test the report feature.
    471 
    472     This also exercises various paths in statusfile logic.
    473     """
    474     with temp_base() as basedir:
    475       result = run_tests(
    476           basedir,
    477           '--mode=Release',
    478           '--variants=default',
    479           'sweet',
    480           '--report',
    481       )
    482       self.assertIn(
    483           '3 tests are expected to fail that we should fix',
    484           result.stdout, result)
    485       self.assertEqual(1, result.returncode, result)
    486 
    487   @unittest.skip("not available with test processors")
    488   def testWarnUnusedRules(self):
    489     """Test the unused-rules feature."""
    490     with temp_base() as basedir:
    491       result = run_tests(
    492           basedir,
    493           '--mode=Release',
    494           '--variants=default,nooptimization',
    495           'sweet',
    496           '--warn-unused',
    497       )
    498       self.assertIn( 'Unused rule: carrots', result.stdout, result)
    499       self.assertIn( 'Unused rule: regress/', result.stdout, result)
    500       self.assertEqual(1, result.returncode, result)
    501 
    502   @unittest.skip("not available with test processors")
    503   def testCatNoSources(self):
    504     """Test printing sources, but the suite's tests have none available."""
    505     with temp_base() as basedir:
    506       result = run_tests(
    507           basedir,
    508           '--mode=Release',
    509           '--variants=default',
    510           'sweet/bananas',
    511           '--cat',
    512       )
    513       self.assertIn('begin source: sweet/bananas', result.stdout, result)
    514       self.assertIn('(no source available)', result.stdout, result)
    515       self.assertEqual(0, result.returncode, result)
    516 
    517   def testPredictableProc(self):
    518     self.testPredictable(infra_staging=True)
    519 
    520   def testPredictable(self, infra_staging=True):
    521     """Test running a test in verify-predictable mode.
    522 
    523     The test will fail because of missing allocation output. We verify that and
    524     that the predictable flags are passed and printed after failure.
    525     """
    526     with temp_base() as basedir:
    527       override_build_config(basedir, v8_enable_verify_predictable=True)
    528       result = run_tests(
    529           basedir,
    530           '--mode=Release',
    531           '--progress=verbose',
    532           '--variants=default',
    533           'sweet/bananas',
    534           infra_staging=infra_staging,
    535       )
    536       if not infra_staging:
    537         self.assertIn('Running 1 tests', result.stdout, result)
    538       else:
    539         self.assertIn('Running 1 base tests', result.stdout, result)
    540         self.assertIn('1 tests ran', result.stdout, result)
    541       self.assertIn('Done running sweet/bananas: FAIL', result.stdout, result)
    542       self.assertIn('Test had no allocation output', result.stdout, result)
    543       self.assertIn('--predictable --verify_predictable', result.stdout, result)
    544       self.assertEqual(1, result.returncode, result)
    545 
    546   def testSlowArch(self):
    547     """Test timeout factor manipulation on slow architecture."""
    548     with temp_base() as basedir:
    549       override_build_config(basedir, v8_target_cpu='arm64')
    550       result = run_tests(
    551           basedir,
    552           '--mode=Release',
    553           '--progress=verbose',
    554           '--variants=default',
    555           'sweet/bananas',
    556       )
    557       # TODO(machenbach): We don't have a way for testing if the correct
    558       # timeout was used.
    559       self.assertEqual(0, result.returncode, result)
    560 
    561   def testRandomSeedStressWithDefaultProc(self):
    562     self.testRandomSeedStressWithDefault(infra_staging=True)
    563 
    564   def testRandomSeedStressWithDefault(self, infra_staging=True):
    565     """Test using random-seed-stress feature has the right number of tests."""
    566     with temp_base() as basedir:
    567       result = run_tests(
    568           basedir,
    569           '--mode=Release',
    570           '--progress=verbose',
    571           '--variants=default',
    572           '--random-seed-stress-count=2',
    573           'sweet/bananas',
    574           infra_staging=infra_staging,
    575       )
    576       if infra_staging:
    577         self.assertIn('Running 1 base tests', result.stdout, result)
    578         self.assertIn('2 tests ran', result.stdout, result)
    579       else:
    580         self.assertIn('Running 2 tests', result.stdout, result)
    581       self.assertEqual(0, result.returncode, result)
    582 
    583   def testRandomSeedStressWithSeed(self):
    584     """Test using random-seed-stress feature passing a random seed."""
    585     with temp_base() as basedir:
    586       result = run_tests(
    587           basedir,
    588           '--mode=Release',
    589           '--progress=verbose',
    590           '--variants=default',
    591           '--random-seed-stress-count=2',
    592           '--random-seed=123',
    593           'sweet/strawberries',
    594       )
    595       self.assertIn('Running 1 base tests', result.stdout, result)
    596       self.assertIn('2 tests ran', result.stdout, result)
    597       # We use a failing test so that the command is printed and we can verify
    598       # that the right random seed was passed.
    599       self.assertIn('--random-seed=123', result.stdout, result)
    600       self.assertEqual(1, result.returncode, result)
    601 
    602   def testSpecificVariants(self):
    603     """Test using NO_VARIANTS modifiers in status files skips the desire tests.
    604 
    605     The test runner cmd line configures 4 tests to run (2 tests * 2 variants).
    606     But the status file applies a modifier to each skipping one of the
    607     variants.
    608     """
    609     with temp_base() as basedir:
    610       override_build_config(basedir, v8_use_snapshot=False)
    611       result = run_tests(
    612           basedir,
    613           '--mode=Release',
    614           '--progress=verbose',
    615           '--variants=default,stress',
    616           'sweet/bananas',
    617           'sweet/raspberries',
    618       )
    619       # Both tests are either marked as running in only default or only
    620       # slow variant.
    621       self.assertIn('Running 2 base tests', result.stdout, result)
    622       self.assertIn('2 tests ran', result.stdout, result)
    623       self.assertEqual(0, result.returncode, result)
    624 
    625   def testStatusFilePresubmit(self):
    626     """Test that the fake status file is well-formed."""
    627     with temp_base() as basedir:
    628       from testrunner.local import statusfile
    629       self.assertTrue(statusfile.PresubmitCheck(
    630           os.path.join(basedir, 'test', 'sweet', 'sweet.status')))
    631 
    632   def testDotsProgressProc(self):
    633     self.testDotsProgress(infra_staging=True)
    634 
    635   def testDotsProgress(self, infra_staging=True):
    636     with temp_base() as basedir:
    637       result = run_tests(
    638           basedir,
    639           '--mode=Release',
    640           '--progress=dots',
    641           'sweet/cherries',
    642           'sweet/bananas',
    643           '--no-sorting', '-j1', # make results order deterministic
    644           infra_staging=infra_staging,
    645       )
    646       if not infra_staging:
    647         self.assertIn('Running 2 tests', result.stdout, result)
    648       else:
    649         self.assertIn('Running 2 base tests', result.stdout, result)
    650         self.assertIn('2 tests ran', result.stdout, result)
    651       self.assertIn('F.', result.stdout, result)
    652       self.assertEqual(1, result.returncode, result)
    653 
    654   def testMonoProgressProc(self):
    655     self._testCompactProgress('mono', True)
    656 
    657   def testMonoProgress(self):
    658     self._testCompactProgress('mono', False)
    659 
    660   def testColorProgressProc(self):
    661     self._testCompactProgress('color', True)
    662 
    663   def testColorProgress(self):
    664     self._testCompactProgress('color', False)
    665 
    666   def _testCompactProgress(self, name, infra_staging):
    667     with temp_base() as basedir:
    668       result = run_tests(
    669           basedir,
    670           '--mode=Release',
    671           '--progress=%s' % name,
    672           'sweet/cherries',
    673           'sweet/bananas',
    674           infra_staging=infra_staging,
    675       )
    676       if name == 'color':
    677         expected = ('\033[34m% 100\033[0m|'
    678                     '\033[32m+   1\033[0m|'
    679                     '\033[31m-   1\033[0m]: Done')
    680       else:
    681         expected = '% 100|+   1|-   1]: Done'
    682       self.assertIn(expected, result.stdout)
    683       self.assertIn('sweet/cherries', result.stdout)
    684       self.assertIn('sweet/bananas', result.stdout)
    685       self.assertEqual(1, result.returncode, result)
    686 
    687 if __name__ == '__main__':
    688   unittest.main()
    689