| #!/usr/bin/env vpython3 |
| # Copyright 2019 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import copy |
| import json |
| import os |
| import subprocess |
| import sys |
| import unittest |
| from unittest import mock |
| |
| import merge_results |
| import merge_steps |
| import merge_lib as merger |
| |
| |
| class MergeProfilesTest(unittest.TestCase): |
| |
| # pylint: disable=super-with-arguments |
| def __init__(self, *args, **kwargs): |
| super(MergeProfilesTest, self).__init__(*args, **kwargs) |
| self.maxDiff = None |
| |
| # pylint: enable=super-with-arguments |
| |
| def test_merge_script_api_parameters(self): |
| """Test the step-level merge front-end.""" |
| build_properties = json.dumps({ |
| 'some': { |
| 'complicated': ['nested', { |
| 'json': None, |
| 'object': 'thing', |
| }] |
| } |
| }) |
| task_output_dir = 'some/task/output/dir' |
| profdata_dir = '/some/different/path/to/profdata/default.profdata' |
| profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata') |
| args = [ |
| 'script_name', '--output-json', 'output.json', '--build-properties', |
| build_properties, '--summary-json', 'summary.json', '--task-output-dir', |
| task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', |
| 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', |
| 'base_unittests', '--sparse' |
| ] |
| with mock.patch.object(merger, 'merge_profiles') as mock_merge: |
| mock_merge.return_value = None, None |
| with mock.patch.object(sys, 'argv', args): |
| merge_results.main() |
| self.assertEqual( |
| mock_merge.call_args, |
| mock.call(task_output_dir, |
| profdata_file, |
| '.profraw', |
| 'llvm-profdata', |
| sparse=True, |
| skip_validation=False), None) |
| |
| def test_merge_steps_parameters(self): |
| """Test the build-level merge front-end.""" |
| input_dir = 'some/task/output/dir' |
| output_file = '/some/different/path/to/profdata/merged.profdata' |
| args = [ |
| 'script_name', '--input-dir', input_dir, '--output-file', output_file, |
| '--llvm-profdata', 'llvm-profdata', '--profdata-filename-pattern', '.*' |
| ] |
| with mock.patch.object(merger, 'merge_profiles') as mock_merge: |
| mock_merge.return_value = [], [] |
| with mock.patch.object(sys, 'argv', args): |
| merge_steps.main() |
| self.assertEqual( |
| mock_merge.call_args, |
| mock.call(input_dir, |
| output_file, |
| '.profdata', |
| 'llvm-profdata', |
| '.*', |
| sparse=False, |
| merge_timeout=3600, |
| weights={})) |
| |
| @mock.patch('builtins.open', new_callable=mock.mock_open()) |
| @mock.patch.object(merger, '_validate_and_convert_profraws') |
| def test_merge_profraw(self, mock_validate_and_convert_profraws, |
| mock_file_open): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), |
| ('/b/some/path/0', [], |
| ['output.json', 'default-1.profraw', 'default-2.profraw']), |
| ('/b/some/path/1', [], |
| ['output.json', 'default-1.profraw', 'default-2.profraw']), |
| ] |
| |
| mock_validate_and_convert_profraws.return_value = [ |
| '/b/some/path/0/default-1.profdata', |
| '/b/some/path/1/default-2.profdata', |
| ], [ |
| '/b/some/path/0/default-2.profraw', |
| '/b/some/path/1/default-1.profraw', |
| ], [ |
| '/b/some/path/1/default-1.profraw', |
| ] |
| |
| with mock.patch.object(os, 'walk') as mock_walk: |
| with mock.patch.object(os, 'remove'): |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'run') as mock_exec_cmd: |
| merger.merge_profiles('/b/some/path', |
| 'output/dir/default.profdata', |
| '.profraw', |
| 'llvm-profdata', |
| show_profdata=False) |
| self.assertEqual( |
| mock.call([ |
| 'llvm-profdata', |
| 'merge', |
| '-o', |
| 'output/dir/default.profdata', |
| '-f', |
| 'output/dir/input-profdata-files.txt', |
| ], |
| capture_output=True, |
| check=True, |
| text=True, |
| timeout=3600), mock_exec_cmd.call_args) |
| context = mock_file_open() |
| self.assertEqual(context.__enter__().write.call_count, 2) |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/0/default-1.profdata\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/1/default-2.profdata\n') |
| |
| self.assertTrue(mock_validate_and_convert_profraws.called) |
| |
| @mock.patch('builtins.open', new_callable=mock.mock_open()) |
| @mock.patch.object(merger, '_validate_and_convert_profraws') |
| def test_profraw_skip_validation(self, mock_validate_and_convert_profraws, |
| mock_file_open): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), |
| ('/b/some/path/0', [], |
| ['output.json', 'default-1.profraw', 'default-2.profraw']), |
| ('/b/some/path/1', [], |
| ['output.json', 'default-1.profraw', 'default-2.profraw']), |
| ] |
| |
| with mock.patch.object(os, 'walk') as mock_walk: |
| with mock.patch.object(os, 'remove'): |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'run') as mock_exec_cmd: |
| merger.merge_profiles('/b/some/path', |
| 'output/dir/default.profdata', |
| '.profraw', |
| 'llvm-profdata', |
| skip_validation=True, |
| show_profdata=False) |
| self.assertEqual( |
| mock.call([ |
| 'llvm-profdata', |
| 'merge', |
| '-o', |
| 'output/dir/default.profdata', |
| '-f', |
| 'output/dir/input-profdata-files.txt', |
| ], |
| capture_output=True, |
| check=True, |
| text=True, |
| timeout=3600), mock_exec_cmd.call_args) |
| context = mock_file_open() |
| self.assertEqual(context.__enter__().write.call_count, 4) |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/0/default-1.profraw\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/0/default-2.profraw\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/1/default-1.profraw\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/1/default-2.profraw\n') |
| |
| # Skip validation should've passed all profraw files directly, and |
| # this validate call should not have been invoked. |
| self.assertFalse(mock_validate_and_convert_profraws.called) |
| |
| def test_merge_profraw_skip_if_there_is_no_file(self): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), |
| ] |
| |
| with mock.patch.object(os, 'walk') as mock_walk: |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: |
| merger.merge_profiles('/b/some/path', |
| 'output/dir/default.profdata', |
| '.profraw', |
| 'llvm-profdata', |
| show_profdata=False) |
| self.assertFalse(mock_exec_cmd.called) |
| |
| @mock.patch('builtins.open', new_callable=mock.mock_open()) |
| @mock.patch.object(merger, '_validate_and_convert_profraws') |
| def test_merge_profdata(self, mock_validate_and_convert_profraws, |
| mock_file_open): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']), |
| ('/b/some/path/base_unittests', [], ['output.json', |
| 'default.profdata']), |
| ('/b/some/path/url_unittests', [], ['output.json', 'default.profdata']), |
| ] |
| with mock.patch.object(os, 'walk') as mock_walk: |
| with mock.patch.object(os, 'remove'): |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'run') as mock_exec_cmd: |
| merger.merge_profiles('/b/some/path', |
| 'output/dir/default.profdata', |
| '.profdata', |
| 'llvm-profdata', |
| show_profdata=False) |
| self.assertEqual( |
| mock.call([ |
| 'llvm-profdata', |
| 'merge', |
| '-o', |
| 'output/dir/default.profdata', |
| '-f', |
| 'output/dir/input-profdata-files.txt', |
| ], |
| capture_output=True, |
| check=True, |
| text=True, |
| timeout=3600), mock_exec_cmd.call_args) |
| context = mock_file_open() |
| self.assertEqual(context.__enter__().write.call_count, 2) |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/base_unittests/default.profdata\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/url_unittests/default.profdata\n') |
| |
| # The mock method should only apply when merging .profraw files. |
| self.assertFalse(mock_validate_and_convert_profraws.called) |
| |
| @mock.patch('builtins.open', new_callable=mock.mock_open()) |
| @mock.patch.object(merger, '_validate_and_convert_profraws') |
| def test_merge_profdata_pattern(self, mock_validate_and_convert_profraws, |
| mock_file_open): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']), |
| ('/b/some/path/base_unittests', [], |
| ['output.json', 'base_unittests.profdata']), |
| ( |
| '/b/some/path/url_unittests', |
| [], |
| ['output.json', 'url_unittests.profdata'], |
| ), |
| ( |
| '/b/some/path/ios_chrome_smoke_eg2tests', |
| [], |
| ['output.json', 'ios_chrome_smoke_eg2tests.profdata'], |
| ), |
| ] |
| with mock.patch.object(os, 'walk') as mock_walk: |
| with mock.patch.object(os, 'remove'): |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'run') as mock_exec_cmd: |
| input_profdata_filename_pattern = r'.+_unittests\.profdata' |
| merger.merge_profiles('/b/some/path', |
| 'output/dir/default.profdata', |
| '.profdata', |
| 'llvm-profdata', |
| input_profdata_filename_pattern, |
| show_profdata=False) |
| self.assertEqual( |
| mock.call([ |
| 'llvm-profdata', |
| 'merge', |
| '-o', |
| 'output/dir/default.profdata', |
| '-f', |
| 'output/dir/input-profdata-files.txt', |
| ], |
| capture_output=True, |
| check=True, |
| text=True, |
| timeout=3600), mock_exec_cmd.call_args) |
| context = mock_file_open() |
| self.assertEqual(context.__enter__().write.call_count, 2) |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/base_unittests/base_unittests.profdata\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/url_unittests/url_unittests.profdata\n') |
| |
| # The mock method should only apply when merging .profraw files. |
| self.assertFalse(mock_validate_and_convert_profraws.called) |
| |
| @mock.patch('builtins.open', new_callable=mock.mock_open()) |
| @mock.patch.object(merger, '_validate_and_convert_profraws') |
| def test_merge_profiles_with_weights(self, mock_validate_and_convert_profraws, |
| mock_file_open): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['speedometer_benchmark', 'motionmark_benchmark'], []), |
| ('/b/some/path/speedometer_benchmark', [], ['foo.profdata']), |
| ('/b/some/path/motionmark_benchmark', [], ['foo.profdata']), |
| ] |
| with mock.patch.object(os, 'walk') as mock_walk: |
| with mock.patch.object(os, 'remove'): |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'run') as mock_exec_cmd: |
| merger.merge_profiles( |
| '/b/some/path', |
| 'output/dir/default.profdata', |
| '.profdata', |
| 'llvm-profdata', |
| '.*', |
| show_profdata=False, |
| weights={'speedometer_benchmark/foo.profdata': '3'}) |
| self.assertEqual( |
| mock.call([ |
| 'llvm-profdata', |
| 'merge', |
| '-o', |
| 'output/dir/default.profdata', |
| '-f', |
| 'output/dir/input-profdata-files.txt', |
| ], |
| capture_output=True, |
| check=True, |
| text=True, |
| timeout=3600), mock_exec_cmd.call_args) |
| context = mock_file_open() |
| self.assertEqual(context.__enter__().write.call_count, 2) |
| context.__enter__().write.assert_any_call( |
| '3,/b/some/path/speedometer_benchmark/foo.profdata\n') |
| context.__enter__().write.assert_any_call( |
| '/b/some/path/motionmark_benchmark/foo.profdata\n') |
| |
| # The mock method should only apply when merging .profraw files. |
| self.assertFalse(mock_validate_and_convert_profraws.called) |
| |
| @mock.patch('merge_lib._JAVA_PATH', 'java') |
| def test_merge_java_exec_files(self): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), |
| ('/b/some/path/0', [], |
| ['output.json', 'default-1.exec', 'default-2.exec']), |
| ('/b/some/path/1', [], |
| ['output.json', 'default-3.exec', 'default-4.exec']), |
| ] |
| |
| with mock.patch.object(os, 'walk') as mock_walk: |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: |
| merger.merge_java_exec_files('/b/some/path', 'output/path', |
| 'path/to/jacococli.jar') |
| self.assertEqual( |
| mock.call([ |
| 'java', |
| '-jar', |
| 'path/to/jacococli.jar', |
| 'merge', |
| '/b/some/path/0/default-1.exec', |
| '/b/some/path/0/default-2.exec', |
| '/b/some/path/1/default-3.exec', |
| '/b/some/path/1/default-4.exec', |
| '--destfile', |
| 'output/path', |
| ], |
| stderr=-2), mock_exec_cmd.call_args) |
| |
| def test_merge_java_exec_files_if_there_is_no_file(self): |
| mock_input_dir_walk = [ |
| ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), |
| ] |
| |
| with mock.patch.object(os, 'walk') as mock_walk: |
| mock_walk.return_value = mock_input_dir_walk |
| with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: |
| merger.merge_java_exec_files('/b/some/path', 'output/path', |
| 'path/to/jacococli.jar') |
| self.assertFalse(mock_exec_cmd.called) |
| |
| def test_calls_merge_js_results_script(self): |
| task_output_dir = 'some/task/output/dir' |
| profdata_dir = '/some/different/path/to/profdata/default.profdata' |
| |
| args = [ |
| 'script_name', '--output-json', 'output.json', '--task-output-dir', |
| task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', |
| 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', |
| 'v8_unittests', '--sparse', '--javascript-coverage-dir', |
| 'output/dir/devtools_code_coverage', '--chromium-src-dir', |
| 'chromium/src', '--build-dir', 'output/dir' |
| ] |
| with mock.patch.object(merger, 'merge_profiles') as mock_merge: |
| mock_merge.return_value = None, None |
| with mock.patch.object(sys, 'argv', args): |
| with mock.patch.object(subprocess, 'call') as mock_exec_cmd: |
| with mock.patch.object(os.path, 'join') as mock_os_path_join: |
| mock_merge_js_results_path = 'path/to/js/merge_js_results.py' |
| mock_os_path_join.return_value = mock_merge_js_results_path |
| python_exec = sys.executable |
| merge_results.main() |
| |
| mock_exec_cmd.assert_called_with([ |
| python_exec, mock_merge_js_results_path, '--task-output-dir', |
| task_output_dir, '--javascript-coverage-dir', |
| 'output/dir/devtools_code_coverage', '--chromium-src-dir', |
| 'chromium/src', '--build-dir', 'output/dir' |
| ]) |
| |
| def test_argparse_sparse(self): |
| """Ensure that sparse flag defaults to true, and is set to correct value""" |
| # Basic required args |
| build_properties = json.dumps({ |
| 'some': { |
| 'complicated': ['nested', { |
| 'json': None, |
| 'object': 'thing', |
| }] |
| } |
| }) |
| task_output_dir = 'some/task/output/dir' |
| profdata_dir = '/some/different/path/to/profdata/default.profdata' |
| profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata') |
| args = [ |
| 'script_name', '--output-json', 'output.json', '--build-properties', |
| build_properties, '--summary-json', 'summary.json', '--task-output-dir', |
| task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', |
| 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', |
| 'base_unittests' |
| ] |
| |
| test_scenarios = [ |
| { |
| # Base set of args should set --sparse to false by default |
| 'args': None, |
| 'expected_outcome': False, |
| }, |
| { |
| # Sparse should parse True when only --sparse is specified |
| 'args': ['--sparse'], |
| 'expected_outcome': True, |
| } |
| ] |
| |
| for scenario in test_scenarios: |
| args = copy.deepcopy(args) |
| additional_args = scenario['args'] |
| if additional_args: |
| args.extend(additional_args) |
| expected_outcome = scenario['expected_outcome'] |
| |
| with mock.patch.object(merger, 'merge_profiles') as mock_merge: |
| mock_merge.return_value = None, None |
| with mock.patch.object(sys, 'argv', args): |
| merge_results.main() |
| self.assertEqual( |
| mock_merge.call_args, |
| mock.call(task_output_dir, |
| profdata_file, |
| '.profraw', |
| 'llvm-profdata', |
| sparse=expected_outcome, |
| skip_validation=False), None) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |