| #!/usr/bin/env vpython3 |
| # Copyright 2026 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Tests for measure_fuzzilli_coverage.py.""" |
| |
| import os |
| import subprocess |
| import unittest |
| from unittest import mock |
| import measure_fuzzilli_coverage |
| from pathlib import Path |
| |
| COVERAGE_SCRIPT_PATH = (f"{Path(__file__).resolve().parent.parents[1]}" |
| "/tools/code_coverage/coverage.py") |
| |
| TEST_FUZZILLI_DIR = '/fake/fuzzilli' |
| TEST_BUILD_OUT_DIR = '/fake/out/Fuzzilli' |
| TEST_REPORT_OUT_DIR = '/fake/out/report' |
| TEST_MINUTES = 2 |
| TEST_SECONDS = 120 |
| TEST_PROFILE = "fakeProfile" |
| TEST_PROFRAW_DIR = '/tmp/fake_profraw_dir' |
| |
| |
| class MeasureFuzzilliCoverageTest(unittest.TestCase): |
| |
| @mock.patch.dict(os.environ, {}, clear=True) |
| @mock.patch('subprocess.Popen') |
| @mock.patch('subprocess.run') |
| def test_RunFuzzilli(self, mock_run, mock_popen): |
| mock_process = mock.Mock() |
| mock_process.wait.side_effect = subprocess.TimeoutExpired( |
| cmd='FuzzilliCli', timeout=TEST_SECONDS) |
| mock_popen.return_value = mock_process |
| mock_pg_handler = mock.Mock() |
| |
| success = measure_fuzzilli_coverage._RunFuzzilli(mock_pg_handler, |
| TEST_FUZZILLI_DIR, |
| TEST_BUILD_OUT_DIR, |
| TEST_MINUTES, TEST_PROFILE, |
| TEST_PROFRAW_DIR) |
| |
| self.assertTrue(success) |
| self.assertEqual(os.environ.get("LLVM_PROFILE_FILE"), |
| os.path.join(TEST_PROFRAW_DIR, "fuzzilli.%4m%c.profraw")) |
| |
| mock_run.assert_called_with(['swift', 'build', '-c', 'release'], |
| check=True, |
| cwd=TEST_FUZZILLI_DIR) |
| |
| expected_run_command_prefix = [ |
| os.path.join(TEST_FUZZILLI_DIR, '.build/release/FuzzilliCli'), |
| '--storagePath=/tmp/fuzzilli_storage', '--overwrite', '--engine=hybrid', |
| '--profile=fakeProfile' |
| ] |
| |
| self.assertTrue(mock_popen.called) |
| actual_run_command = mock_popen.call_args[0][0] |
| self.assertEqual(actual_run_command[:-1], expected_run_command_prefix) |
| self.assertTrue(actual_run_command[-1].endswith('.sh')) |
| |
| self.assertEqual(mock_popen.call_args[1], { |
| 'cwd': TEST_FUZZILLI_DIR, |
| 'start_new_session': True |
| }) |
| |
| mock_process.wait.assert_called_with(timeout=TEST_SECONDS) |
| mock_pg_handler.SetProcessGroup.assert_called_with(mock_process) |
| mock_pg_handler.KillProcessGroup.assert_called_once() |
| |
| @mock.patch.dict(os.environ, {}, clear=True) |
| @mock.patch('subprocess.Popen') |
| @mock.patch('subprocess.run') |
| def test_RunFuzzilliBuildFails(self, mock_run, mock_popen): |
| mock_run.side_effect = subprocess.CalledProcessError(returncode=1, |
| cmd='swift build') |
| mock_pg_handler = mock.Mock() |
| |
| success = measure_fuzzilli_coverage._RunFuzzilli(mock_pg_handler, |
| TEST_FUZZILLI_DIR, |
| TEST_BUILD_OUT_DIR, |
| TEST_MINUTES, TEST_PROFILE, |
| TEST_PROFRAW_DIR) |
| |
| self.assertFalse(success) |
| mock_run.assert_called_with(['swift', 'build', '-c', 'release'], |
| check=True, |
| cwd=TEST_FUZZILLI_DIR) |
| mock_popen.assert_not_called() |
| mock_pg_handler.SetProcessGroup.assert_not_called() |
| mock_pg_handler.KillProcessGroup.assert_not_called() |
| |
| @mock.patch.dict(os.environ, {}, clear=True) |
| @mock.patch('subprocess.Popen') |
| @mock.patch('subprocess.run') |
| def test_RunFuzzilliEarlyExit(self, mock_run, mock_popen): |
| mock_process = mock.Mock() |
| # As if crash before timeout reached |
| mock_process.returncode = 1 |
| mock_popen.return_value = mock_process |
| mock_pg_handler = mock.Mock() |
| |
| success = measure_fuzzilli_coverage._RunFuzzilli(mock_pg_handler, |
| TEST_FUZZILLI_DIR, |
| TEST_BUILD_OUT_DIR, |
| TEST_MINUTES, TEST_PROFILE, |
| TEST_PROFRAW_DIR) |
| |
| self.assertFalse(success) |
| mock_process.wait.assert_called_with(timeout=TEST_SECONDS) |
| mock_pg_handler.SetProcessGroup.assert_called_with(mock_process) |
| mock_pg_handler.KillProcessGroup.assert_not_called() |
| |
| def _assert_generate_coverage_report(self, |
| mock_run, |
| mock_glob, |
| filters=None, |
| ignore_regex=None): |
| if not filters: |
| filters = [] |
| |
| mock_glob.return_value = [os.path.join(TEST_PROFRAW_DIR, '1.profraw')] |
| |
| measure_fuzzilli_coverage._GenerateCoverageReport( |
| TEST_BUILD_OUT_DIR, |
| TEST_REPORT_OUT_DIR, |
| ignore_filename_regex=ignore_regex, |
| filters=filters, |
| profraw_dir=TEST_PROFRAW_DIR) |
| |
| expected_gen_profile_command = [ |
| 'llvm-profdata', 'merge', '-o', |
| os.path.join(TEST_PROFRAW_DIR, 'coverage.profdata'), |
| os.path.join(TEST_PROFRAW_DIR, '1.profraw') |
| ] |
| |
| expected_coverage_command = [ |
| COVERAGE_SCRIPT_PATH, 'js_in_process_fuzzer', '-b', TEST_BUILD_OUT_DIR, |
| '-o', TEST_REPORT_OUT_DIR, '-p', |
| os.path.join(TEST_PROFRAW_DIR, |
| 'coverage.profdata'), '--no-component-view' |
| ] |
| |
| for f in filters: |
| expected_coverage_command.extend(['-f', f]) |
| if ignore_regex: |
| expected_coverage_command.extend(['-i', ignore_regex]) |
| |
| mock_run.assert_has_calls([ |
| mock.call(expected_gen_profile_command, check=True), |
| mock.call(expected_coverage_command, check=True) |
| ]) |
| |
| @mock.patch('glob.glob') |
| @mock.patch('subprocess.run') |
| def test_GenerateCoverageReport(self, mock_run, mock_glob): |
| self._assert_generate_coverage_report(mock_run, mock_glob) |
| |
| @mock.patch('glob.glob') |
| @mock.patch('subprocess.run') |
| def test_GenerateCoverageReportWithFilters(self, mock_run, mock_glob): |
| self._assert_generate_coverage_report(mock_run, |
| mock_glob, |
| filters=['/fake/directory']) |
| |
| @mock.patch('glob.glob') |
| @mock.patch('subprocess.run') |
| def test_GenerateCoverageReportWithIgnoreRegex(self, mock_run, mock_glob): |
| self._assert_generate_coverage_report(mock_run, |
| mock_glob, |
| ignore_regex='.*out.*') |
| |
| @mock.patch('glob.glob') |
| @mock.patch('subprocess.run') |
| def test_GenerateCoverageReportWithFiltersAndIgnoreRegex( |
| self, mock_run, mock_glob): |
| self._assert_generate_coverage_report(mock_run, |
| mock_glob, |
| filters=['/fake/directory'], |
| ignore_regex='.*out.*') |
| |
| @mock.patch('glob.glob') |
| @mock.patch('subprocess.run') |
| def test_GenerateCoverageReportFails(self, mock_run, mock_glob): |
| mock_glob.return_value = [os.path.join(TEST_PROFRAW_DIR, '1.profraw')] |
| mock_run.side_effect = subprocess.CalledProcessError(returncode=1, |
| cmd='llvm-profdata') |
| |
| success = measure_fuzzilli_coverage._GenerateCoverageReport( |
| TEST_BUILD_OUT_DIR, |
| TEST_REPORT_OUT_DIR, |
| ignore_filename_regex=None, |
| filters=None, |
| profraw_dir=TEST_PROFRAW_DIR) |
| |
| self.assertFalse(success) |
| |
| @mock.patch('glob.glob') |
| @mock.patch('subprocess.run') |
| def test_GenerateCoverageReportFailsFileNotFoundError(self, mock_run, |
| mock_glob): |
| mock_glob.return_value = [os.path.join(TEST_PROFRAW_DIR, '1.profraw')] |
| mock_run.side_effect = FileNotFoundError() |
| |
| success = measure_fuzzilli_coverage._GenerateCoverageReport( |
| TEST_BUILD_OUT_DIR, |
| TEST_REPORT_OUT_DIR, |
| ignore_filename_regex=None, |
| filters=None, |
| profraw_dir=TEST_PROFRAW_DIR) |
| |
| self.assertFalse(success) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |