| #!/usr/bin/env python3 |
| # Copyright 2025 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Unit tests for tools.android.build_speed.benchmark.""" |
| |
| import contextlib |
| import json |
| import pathlib |
| import subprocess |
| import sys |
| import tempfile |
| import unittest |
| import unittest.mock |
| |
| import benchmark |
| from pylib import constants |
| |
| |
| class TestBenchmarkScript(unittest.TestCase): |
| """Unit tests for benchmark.py.""" |
| |
| @unittest.mock.patch('benchmark._terminate_build_server_if_needed') |
| @unittest.mock.patch('random.randint', return_value=123) |
| @unittest.mock.patch( |
| 'time.time', |
| side_effect=[1.0, 3.5, 5.0, 10.0, 11.0, 13.0, 15.0, 20.5, 22.0, 25.0]) |
| @unittest.mock.patch('subprocess.run') |
| @unittest.mock.patch('benchmark._emulator', |
| return_value=contextlib.nullcontext( |
| unittest.mock.Mock(serial='emulator-5554'))) |
| def test_run_benchmarks(self, mock_emulator, mock_subprocess_run, |
| mock_time, mock_randint, |
| mock_terminate_build_server): |
| benchmarks_to_run = ['chrome_nosig'] |
| gn_args = ['is_debug=true'] |
| repeat = 1 |
| |
| change_file_path = ( |
| benchmark._SRC_ROOT / |
| benchmark._BENCHMARK_FROM_NAME['chrome_nosig'].change_file) |
| original_content = change_file_path.read_text() |
| |
| try: |
| with tempfile.TemporaryDirectory() as tmpdir: |
| out_dir = pathlib.Path(tmpdir) |
| timings = benchmark.run_benchmarks( |
| benchmarks_to_run, |
| gn_args, |
| out_dir, |
| 'target', |
| repeat, |
| emulator_avd_name='emulator.avd', |
| dry_run=False) |
| self.assertEqual(timings['gn_gen'], [2.5]) |
| self.assertEqual(timings['chrome_nosig_compile'], [5.5]) |
| self.assertEqual(timings['chrome_nosig_install'], [3.0]) |
| finally: |
| # Ensure the file is restored even if the test fails. |
| change_file_path.write_text(original_content) |
| |
| self.assertEqual(change_file_path.read_text(), original_content) |
| |
| @unittest.mock.patch('builtins.print') |
| @unittest.mock.patch('benchmark.run_benchmarks') |
| def test_main_logic(self, mock_run_benchmarks, mock_print): |
| mock_run_benchmarks.return_value = { |
| 'gn_gen': [1.23], |
| 'chrome_nosig_compile': [10.56] |
| } |
| with tempfile.TemporaryDirectory() as tmpdir: |
| constants.SetOutputDirectory(tmpdir) |
| |
| # Test default args and quiet mode. |
| with unittest.mock.patch( |
| 'sys.argv', ['benchmark.py', 'chrome_nosig', '--quiet']): |
| benchmark.main() |
| args, _kwargs = mock_run_benchmarks.call_args |
| expected_gn_args = [ |
| 'target_os="android"', |
| 'use_remoteexec=true', |
| 'android_static_analysis="build_server"', |
| 'incremental_install=true', |
| 'target_cpu="x86"', |
| ] |
| self.assertEqual(args[1], expected_gn_args) |
| # The real output is tested in the next block, this just clears |
| # the mock calls. |
| mock_print.reset_mock() |
| |
| # Test --no-server and output printing. |
| with unittest.mock.patch( |
| 'sys.argv', |
| ['benchmark.py', 'chrome_nosig', '--no-server']): |
| benchmark.main() |
| args, _kwargs = mock_run_benchmarks.call_args |
| expected_gn_args = [ |
| 'target_os="android"', |
| 'use_remoteexec=true', |
| 'android_static_analysis="on"', |
| 'incremental_install=true', |
| 'target_cpu="x86"', |
| ] |
| self.assertEqual(args[1], expected_gn_args) |
| # Get the printed output from the mock. |
| printed_output = '\n'.join(c.args[0] |
| for c in mock_print.call_args_list) |
| expected_output = ( |
| 'Summary\n' |
| 'emulator: None\n' |
| 'gn args: target_os="android" use_remoteexec=true ' |
| 'android_static_analysis="on" ' |
| 'incremental_install=true target_cpu="x86"\n' |
| 'target: chrome_public_apk\n' |
| 'gn_gen: 1.2s\n' |
| 'chrome_nosig_compile: 10.6s') |
| self.assertEqual(printed_output, expected_output) |
| |
| @unittest.mock.patch('builtins.print') |
| @unittest.mock.patch('benchmark.run_benchmarks') |
| def test_main_json_output(self, mock_run_benchmarks, mock_print): |
| mock_run_benchmarks.return_value = { |
| 'gn_gen': [1.23], |
| 'chrome_nosig_compile': [10.56], |
| } |
| with tempfile.TemporaryDirectory() as tmpdir: |
| constants.SetOutputDirectory(tmpdir) |
| with unittest.mock.patch('sys.argv', [ |
| 'benchmark.py', 'chrome_nosig', '--json', '--emulator', |
| 'fake_emu' |
| ]): |
| benchmark.main() |
| |
| # Verify run_benchmarks call |
| args, _kwargs = mock_run_benchmarks.call_args |
| self.assertEqual(args[0], ['chrome_nosig']) |
| self.assertEqual(args[5], 'fake_emu') |
| |
| # Verify JSON output |
| printed_output = mock_print.call_args.args[0] |
| parsed_json = json.loads(printed_output) |
| expected_json = [ |
| { |
| 'name': |
| 'gn_gen', |
| 'timings': [1.23], |
| 'emulator': |
| 'fake_emu', |
| 'gn_args': [ |
| 'target_os="android"', |
| 'use_remoteexec=true', |
| 'android_static_analysis="build_server"', |
| 'incremental_install=true', |
| 'target_cpu="x64"', |
| ], |
| 'target': |
| 'chrome_public_apk', |
| }, |
| { |
| 'name': |
| 'chrome_nosig_compile', |
| 'timings': [10.56], |
| 'emulator': |
| 'fake_emu', |
| 'gn_args': [ |
| 'target_os="android"', |
| 'use_remoteexec=true', |
| 'android_static_analysis="build_server"', |
| 'incremental_install=true', |
| 'target_cpu="x64"', |
| ], |
| 'target': |
| 'chrome_public_apk', |
| }, |
| ] |
| self.assertEqual(parsed_json, expected_json) |
| |
| @unittest.mock.patch('builtins.print') |
| @unittest.mock.patch('benchmark.run_benchmarks') |
| def test_main_dry_run(self, mock_run_benchmarks, mock_print): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| constants.SetOutputDirectory(tmpdir) |
| with unittest.mock.patch( |
| 'sys.argv', ['benchmark.py', 'chrome_nosig', '--dry-run']): |
| benchmark.main() |
| |
| # Verify run_benchmarks was called with dry_run=True |
| _args, kwargs = mock_run_benchmarks.call_args |
| self.assertTrue(kwargs['dry_run']) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |