| # Copyright 2015 The Chromium Authors | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | from __future__ import print_function | 
 | import json | 
 | import logging | 
 | import os | 
 | import shutil | 
 | import subprocess | 
 | import sys | 
 | import tempfile | 
 | import unittest | 
 |  | 
 | from telemetry import decorators | 
 | from telemetry.testing import options_for_unittests | 
 |  | 
 | RUNNER_SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), | 
 |                                   '..', '..', 'testing', 'scripts') | 
 | sys.path.append(RUNNER_SCRIPTS_DIR) | 
 | import run_performance_tests  # pylint: disable=wrong-import-position,import-error | 
 |  | 
 |  | 
 | class ScriptsSmokeTest(unittest.TestCase): | 
 |  | 
 |   perf_dir = os.path.dirname(__file__) | 
 |  | 
 |   def setUp(self): | 
 |     self.options = options_for_unittests.GetCopy() | 
 |  | 
 |   def RunPerfScript(self, args, env=None): | 
 |     # TODO(crbug.com/985712): Switch all clients to pass a list of args rather | 
 |     # than a string which we may not be parsing correctly. | 
 |     if not isinstance(args, list): | 
 |       args = args.split(' ') | 
 |     proc = subprocess.Popen([sys.executable] + args, stdout=subprocess.PIPE, | 
 |                             stderr=subprocess.STDOUT, cwd=self.perf_dir, | 
 |                             env=env) | 
 |     stdout = proc.communicate()[0] | 
 |     return_code = proc.returncode | 
 |     return return_code, stdout.decode('utf-8') | 
 |  | 
 |   def testRunBenchmarkHelp(self): | 
 |     return_code, stdout = self.RunPerfScript('run_benchmark --help') | 
 |     self.assertEqual(return_code, 0, stdout) | 
 |     self.assertIn('usage: run_benchmark', stdout) | 
 |  | 
 |   @decorators.Disabled('chromeos')  # crbug.com/754913 | 
 |   def testRunBenchmarkListBenchmarks(self): | 
 |     cmdline = [ | 
 |         'run_benchmark', 'list', '--browser', self.options.browser_type, | 
 |         '--chrome-root', | 
 |         os.path.abspath(self.options.chrome_root) | 
 |     ] | 
 |     if self.options.browser_type == 'exact': | 
 |       # If we're running with an exact browser and it was not specified with | 
 |       # an absolute path, then there's no guarantee that we can actually find it | 
 |       # now, so make the test a no-op. | 
 |       if not os.path.isabs(self.options.browser_executable): | 
 |         return | 
 |       cmdline.extend(['--browser-executable', self.options.browser_executable]) | 
 |     return_code, stdout = self.RunPerfScript(cmdline) | 
 |     if sys.version_info.major == 3: | 
 |       self.assertRegex(stdout, r'Available benchmarks .*? are:') | 
 |     else: | 
 |       # TODO: (crbug/1342770) clean up after python migration is done. | 
 |       self.assertRegexpMatches(stdout, r'Available benchmarks .*? are:')  # pylint: disable=deprecated-method | 
 |     self.assertEqual(return_code, 0) | 
 |  | 
 |   def testRunBenchmarkRunListsOutBenchmarks(self): | 
 |     return_code, stdout = self.RunPerfScript('run_benchmark run') | 
 |     self.assertIn('Pass --browser to list benchmarks', stdout) | 
 |     self.assertNotEqual(return_code, 0) | 
 |  | 
 |   def testRunBenchmarkRunNonExistingBenchmark(self): | 
 |     return_code, stdout = self.RunPerfScript('run_benchmark foo') | 
 |     self.assertIn('no such benchmark: foo', stdout) | 
 |     self.assertNotEqual(return_code, 0) | 
 |  | 
 |   def testRunRecordWprHelp(self): | 
 |     return_code, stdout = self.RunPerfScript('record_wpr') | 
 |     self.assertEqual(return_code, 0, stdout) | 
 |     self.assertIn('optional arguments:', stdout) | 
 |  | 
 |   @decorators.Disabled('chromeos')  # crbug.com/814068 | 
 |   def testRunRecordWprList(self): | 
 |     return_code, stdout = self.RunPerfScript('record_wpr --list-benchmarks') | 
 |     # TODO(nednguyen): Remove this once we figure out why importing | 
 |     # small_profile_extender fails on Android dbg. | 
 |     # crbug.com/561668 | 
 |     if 'ImportError: cannot import name small_profile_extender' in stdout: | 
 |       self.skipTest('small_profile_extender is missing') | 
 |     self.assertEqual(return_code, 0, stdout) | 
 |  | 
 |   @decorators.Disabled('chromeos')  # crbug.com/754913 | 
 |   def testRunPerformanceTestsTelemetry_end2end(self): | 
 |     tempdir = tempfile.mkdtemp() | 
 |     benchmarks = ['dummy_benchmark.stable_benchmark_1', | 
 |                   'dummy_benchmark.noisy_benchmark_1'] | 
 |     cmdline = ('../../testing/scripts/run_performance_tests.py ' | 
 |                '../../tools/perf/run_benchmark ' | 
 |                '--benchmarks=%s ' | 
 |                '--browser=%s ' | 
 |                '--chrome-root=%s ' | 
 |                '--isolated-script-test-also-run-disabled-tests ' | 
 |                '--isolated-script-test-output=%s' % | 
 |                (','.join(benchmarks), self.options.browser_type, | 
 |                 os.path.abspath(self.options.chrome_root), | 
 |                 os.path.join(tempdir, 'output.json'))) | 
 |     if self.options.browser_type == 'exact': | 
 |       # If the path to the browser executable is not absolute, there is no | 
 |       # guarantee that we can actually find it at this point, so no-op the | 
 |       # test. | 
 |       if not os.path.isabs(self.options.browser_executable): | 
 |         return | 
 |       cmdline += ' --browser-executable=%s' % self.options.browser_executable | 
 |     return_code, stdout = self.RunPerfScript(cmdline) | 
 |     self.assertEqual(return_code, 0, stdout) | 
 |     try: | 
 |       with open(os.path.join(tempdir, 'output.json')) as f: | 
 |         test_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             test_results, 'json_test_results should be populated: ' + stdout) | 
 |         benchmarks_run = [str(b) for b in test_results['tests'].keys()] | 
 |         self.assertEqual(sorted(benchmarks_run), sorted(benchmarks)) | 
 |         story_runs = test_results['num_failures_by_type']['PASS'] | 
 |         self.assertEqual( | 
 |             story_runs, 2, | 
 |             'Total runs should be 2 since each benchmark has one story.') | 
 |       for benchmark in benchmarks: | 
 |         with open(os.path.join(tempdir, benchmark, 'test_results.json')) as f: | 
 |           test_results = json.load(f) | 
 |           self.assertIsNotNone( | 
 |               test_results, 'json_test_results should be populated: ' + stdout) | 
 |         with open(os.path.join(tempdir, benchmark, 'perf_results.json')) as f: | 
 |           perf_results = json.load(f) | 
 |           self.assertIsNotNone( | 
 |               perf_results, 'json perf results should be populated: ' + stdout) | 
 |     except IOError as e: | 
 |       self.fail('json_test_results should be populated: ' + stdout + str(e)) | 
 |     except AssertionError as e: | 
 |       self.fail('Caught assertion error: ' + str(e) + 'With stdout: ' + stdout) | 
 |     finally: | 
 |       shutil.rmtree(tempdir) | 
 |  | 
 |   @decorators.Enabled('linux')  # Testing platform-independent code. | 
 |   def testRunPerformanceTestsTelemetry_NoTestResults(self): | 
 |     """Test that test results output gets returned for complete failures.""" | 
 |     tempdir = tempfile.mkdtemp() | 
 |     benchmarks = ['benchmark1', 'benchmark2'] | 
 |     return_code, stdout = self.RunPerfScript( | 
 |         '../../testing/scripts/run_performance_tests.py ' | 
 |         '../../tools/perf/testdata/fail_and_do_nothing ' | 
 |         '--benchmarks=%s ' | 
 |         '--browser=%s ' | 
 |         '--isolated-script-test-output=%s' % ( | 
 |             ','.join(benchmarks), | 
 |             self.options.browser_type, | 
 |             os.path.join(tempdir, 'output.json') | 
 |         )) | 
 |     self.assertNotEqual(return_code, 0) | 
 |     try: | 
 |       with open(os.path.join(tempdir, 'output.json')) as f: | 
 |         test_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             test_results, 'json_test_results should be populated: ' + stdout) | 
 |         self.assertTrue( | 
 |             test_results['interrupted'], | 
 |             'if the benchmark does not populate test results, then we should ' | 
 |             'populate it with a failure.') | 
 |       for benchmark in benchmarks: | 
 |         with open(os.path.join(tempdir, benchmark, 'test_results.json')) as f: | 
 |           test_results = json.load(f) | 
 |           self.assertIsNotNone( | 
 |               test_results, 'json_test_results should be populated: ' + stdout) | 
 |           self.assertTrue( | 
 |               test_results['interrupted'], | 
 |               'if the benchmark does not populate test results, then we should ' | 
 |               'populate it with a failure.') | 
 |     except IOError as e: | 
 |       self.fail('json_test_results should be populated: ' + stdout + str(e)) | 
 |     finally: | 
 |       shutil.rmtree(tempdir) | 
 |  | 
 |   # Android: crbug.com/932301 | 
 |   # ChromeOS: crbug.com/754913 | 
 |   # Windows: crbug.com/1024767 | 
 |   # Linux: crbug.com/1024767 | 
 |   # all: Disabled everywhere because the smoke test shard map | 
 |   # needed to be changed to fix crbug.com/1024767. | 
 |   @decorators.Disabled('all') | 
 |   def testRunPerformanceTestsTelemetrySharded_end2end(self): | 
 |     tempdir = tempfile.mkdtemp() | 
 |     env = os.environ.copy() | 
 |     env['GTEST_SHARD_INDEX'] = '0' | 
 |     env['GTEST_TOTAL_SHARDS'] = '2' | 
 |     return_code, stdout = self.RunPerfScript( | 
 |         '../../testing/scripts/run_performance_tests.py ' | 
 |         '../../tools/perf/run_benchmark ' | 
 |         '--test-shard-map-filename=smoke_test_benchmark_shard_map.json ' | 
 |         '--browser=%s ' | 
 |         '--run-ref-build ' | 
 |         '--isolated-script-test-filter=dummy_benchmark.noisy_benchmark_1/' | 
 |         'dummy_page.html::dummy_benchmark.stable_benchmark_1/dummy_page.html ' | 
 |         '--isolated-script-test-repeat=2 ' | 
 |         '--isolated-script-test-also-run-disabled-tests ' | 
 |         '--isolated-script-test-output=%s' % ( | 
 |             self.options.browser_type, | 
 |             os.path.join(tempdir, 'output.json') | 
 |         ), env=env) | 
 |     test_results = None | 
 |     try: | 
 |       self.assertEqual(return_code, 0) | 
 |       expected_benchmark_folders = ( | 
 |           'dummy_benchmark.stable_benchmark_1', | 
 |           'dummy_benchmark.stable_benchmark_1.reference', | 
 |           'dummy_gtest') | 
 |       with open(os.path.join(tempdir, 'output.json')) as f: | 
 |         test_results = json.load(f) | 
 |       self.assertIsNotNone( | 
 |           test_results, 'json_test_results should be populated.') | 
 |       test_runs = test_results['num_failures_by_type']['PASS'] | 
 |       # 1 gtest runs (since --isolated-script-test-repeat doesn't work for gtest | 
 |       # yet) plus 2 dummy_benchmark runs = 3 runs. | 
 |       self.assertEqual( | 
 |           test_runs, 3, '--isolated-script-test-repeat=2 should work.') | 
 |       for folder in expected_benchmark_folders: | 
 |         with open(os.path.join(tempdir, folder, 'test_results.json')) as f: | 
 |           test_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             test_results, 'json test results should be populated.') | 
 |         test_repeats = test_results['num_failures_by_type']['PASS'] | 
 |         if 'dummy_gtest' not in folder:  # Repeats don't work for gtest yet. | 
 |           self.assertEqual( | 
 |               test_repeats, 2, '--isolated-script-test-repeat=2 should work.') | 
 |         with open(os.path.join(tempdir, folder, 'perf_results.json')) as f: | 
 |           perf_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             perf_results, 'json perf results should be populated.') | 
 |     except Exception as exc: | 
 |       logging.error( | 
 |           'Failed with error: %s\nOutput from run_performance_tests.py:\n\n%s', | 
 |           exc, stdout) | 
 |       if test_results is not None: | 
 |         logging.error( | 
 |             'Got test_results: %s\n', json.dumps(test_results, indent=2)) | 
 |       raise | 
 |     finally: | 
 |       shutil.rmtree(tempdir) | 
 |  | 
 |   def RunGtest(self, generate_trace): | 
 |     tempdir = tempfile.mkdtemp() | 
 |     benchmark = 'dummy_gtest' | 
 |     return_code, stdout = self.RunPerfScript( | 
 |         '../../testing/scripts/run_performance_tests.py ' + | 
 |         ('../../tools/perf/run_gtest_benchmark.py ' if generate_trace else '') + | 
 |         os.path.join('..', '..', 'tools', 'perf', 'testdata', | 
 |                      'dummy_gtest') + | 
 |         (' --use-gtest-benchmark-script --output-format=histograms' | 
 |              if generate_trace else '') + | 
 |         ' --non-telemetry=true ' | 
 |         '--this-arg=passthrough ' | 
 |         '--argument-to-check-that-arguments-work ' | 
 |         '--gtest-benchmark-name dummy_gtest ' | 
 |         '--isolated-script-test-output=%s' % ( | 
 |             os.path.join(tempdir, 'output.json') | 
 |         )) | 
 |     try: | 
 |       self.assertEqual(return_code, 0, stdout) | 
 |     except AssertionError: | 
 |       try: | 
 |         with open(os.path.join(tempdir, benchmark, 'benchmark_log.txt')) as fh: | 
 |           print(fh.read()) | 
 |       # pylint: disable=bare-except | 
 |       except: | 
 |         # pylint: enable=bare-except | 
 |         pass | 
 |       raise | 
 |     try: | 
 |       with open(os.path.join(tempdir, 'output.json')) as f: | 
 |         test_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             test_results, 'json_test_results should be populated: ' + stdout) | 
 |       with open(os.path.join(tempdir, benchmark, 'test_results.json')) as f: | 
 |         test_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             test_results, 'json_test_results should be populated: ' + stdout) | 
 |       with open(os.path.join(tempdir, benchmark, 'perf_results.json')) as f: | 
 |         perf_results = json.load(f) | 
 |         self.assertIsNotNone( | 
 |             perf_results, 'json perf results should be populated: ' + stdout) | 
 |     except IOError as e: | 
 |       self.fail('json_test_results should be populated: ' + stdout + str(e)) | 
 |     finally: | 
 |       shutil.rmtree(tempdir) | 
 |  | 
 |   # Windows: ".exe" is auto-added which breaks Windows. | 
 |   # ChromeOS: crbug.com/754913. | 
 |   @decorators.Disabled('win', 'chromeos') | 
 |   def testRunPerformanceTestsGtest_end2end(self): | 
 |     self.RunGtest(generate_trace=False) | 
 |  | 
 |   # Windows: ".exe" is auto-added which breaks Windows. | 
 |   # ChromeOS: crbug.com/754913. | 
 |   @decorators.Disabled('win', 'chromeos') | 
 |   def testRunPerformanceTestsGtestTrace_end2end(self): | 
 |     self.RunGtest(generate_trace=True) | 
 |  | 
 |   def testRunPerformanceTestsShardedArgsParser(self): | 
 |     options = run_performance_tests.parse_arguments([ | 
 |         '../../tools/perf/run_benchmark', '-v', '--browser=release_x64', | 
 |         '--upload-results', '--run-ref-build', | 
 |         '--test-shard-map-filename=win-10-perf_map.json', | 
 |         '--assert-gpu-compositing', | 
 |         r'--isolated-script-test-output=c:\a\b\c\output.json', | 
 |         r'--isolated-script-test-perf-output=c:\a\b\c\perftest-output.json', | 
 |         '--passthrough-arg=--a=b', | 
 |     ]) | 
 |     self.assertIn('--assert-gpu-compositing', options.passthrough_args) | 
 |     self.assertIn('--browser=release_x64', options.passthrough_args) | 
 |     self.assertIn('-v', options.passthrough_args) | 
 |     self.assertIn('--a=b', options.passthrough_args) | 
 |     self.assertEqual(options.executable, '../../tools/perf/run_benchmark') | 
 |     self.assertEqual(options.isolated_script_test_output, | 
 |                      r'c:\a\b\c\output.json') | 
 |  | 
 |   def testRunPerformanceTestsTelemetryCommandGenerator_ReferenceBrowserComeLast(self): | 
 |     """This tests for crbug.com/928928.""" | 
 |     options = run_performance_tests.parse_arguments([ | 
 |         '../../tools/perf/run_benchmark', '--browser=release_x64', | 
 |         '--run-ref-build', | 
 |         '--test-shard-map-filename=win-10-perf_map.json', | 
 |         r'--isolated-script-test-output=c:\a\b\c\output.json', | 
 |     ]) | 
 |     self.assertIn('--browser=release_x64', options.passthrough_args) | 
 |     command = run_performance_tests.TelemetryCommandGenerator( | 
 |         'fake_benchmark_name', options, is_reference=True).generate( | 
 |             'fake_output_dir') | 
 |     original_browser_arg_index = command.index('--browser=release_x64') | 
 |     reference_browser_arg_index = command.index('--browser=reference') | 
 |     self.assertTrue(reference_browser_arg_index > original_browser_arg_index) | 
 |  | 
 |   def testRunPerformanceTestsTelemetryCommandGenerator_StorySelectionConfig_Unabridged(self): | 
 |     options = run_performance_tests.parse_arguments([ | 
 |         '../../tools/perf/run_benchmark', '--browser=release_x64', | 
 |         '--run-ref-build', | 
 |         r'--isolated-script-test-output=c:\a\b\c\output.json', | 
 |     ]) | 
 |     story_selection_config = { | 
 |         'abridged': False, | 
 |         'begin': 1, | 
 |         'end': 5, | 
 |     } | 
 |     command = run_performance_tests.TelemetryCommandGenerator( | 
 |         'fake_benchmark_name', options, story_selection_config).generate( | 
 |             'fake_output_dir') | 
 |     self.assertNotIn('--run-abridged-story-set', command) | 
 |     self.assertIn('--story-shard-begin-index=1', command) | 
 |     self.assertIn('--story-shard-end-index=5', command) | 
 |  | 
 |   def testRunPerformanceTestsTelemetryCommandGenerator_StorySelectionConfig_Abridged(self): | 
 |     options = run_performance_tests.parse_arguments([ | 
 |         '../../tools/perf/run_benchmark', '--browser=release_x64', | 
 |         '--run-ref-build', | 
 |         r'--isolated-script-test-output=c:\a\b\c\output.json', | 
 |     ]) | 
 |     story_selection_config = { | 
 |         'abridged': True, | 
 |     } | 
 |     command = run_performance_tests.TelemetryCommandGenerator( | 
 |         'fake_benchmark_name', options, story_selection_config).generate( | 
 |             'fake_output_dir') | 
 |     self.assertIn('--run-abridged-story-set', command) | 
 |  | 
 |   def testRunPerformanceTestsGtestArgsParser(self): | 
 |     options = run_performance_tests.parse_arguments([ | 
 |         'media_perftests', | 
 |         '--non-telemetry=true', | 
 |         '--single-process-tests', | 
 |         '--test-launcher-retry-limit=0', | 
 |         '--isolated-script-test-filter=*::-*_unoptimized::*_unaligned::' | 
 |         '*unoptimized_aligned', | 
 |         '--gtest-benchmark-name', | 
 |         'media_perftests', | 
 |         '--isolated-script-test-output=/x/y/z/output.json', | 
 |     ]) | 
 |     self.assertIn('--single-process-tests', options.passthrough_args) | 
 |     self.assertIn('--test-launcher-retry-limit=0', options.passthrough_args) | 
 |     self.assertEqual(options.executable, 'media_perftests') | 
 |     self.assertEqual(options.isolated_script_test_output, r'/x/y/z/output.json') | 
 |  | 
 |   def testRunPerformanceTestsExecuteGtest_OSError(self): | 
 |     class FakeCommandGenerator(object): | 
 |       def __init__(self): | 
 |         self.executable_name = 'binary_that_doesnt_exist' | 
 |         self._ignore_shard_env_vars = False | 
 |  | 
 |       def generate(self, unused_path): | 
 |         return [self.executable_name] | 
 |  | 
 |     tempdir = tempfile.mkdtemp() | 
 |     try: | 
 |       fake_command_generator = FakeCommandGenerator() | 
 |       output_paths = run_performance_tests.OutputFilePaths( | 
 |           tempdir, 'fake_gtest') | 
 |       output_paths.SetUp() | 
 |       return_code = run_performance_tests.execute_gtest_perf_test( | 
 |           fake_command_generator, output_paths, is_unittest=True) | 
 |       self.assertEqual(return_code, 1) | 
 |       with open(output_paths.test_results) as fh: | 
 |         json_test_results = json.load(fh) | 
 |       self.assertGreater(json_test_results['num_failures_by_type']['FAIL'], 0) | 
 |     finally: | 
 |       shutil.rmtree(tempdir) |