diff options
Diffstat (limited to 'infra/cifuzz/get_coverage_test.py')
-rw-r--r-- | infra/cifuzz/get_coverage_test.py | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/infra/cifuzz/get_coverage_test.py b/infra/cifuzz/get_coverage_test.py new file mode 100644 index 000000000..fcfc9bd25 --- /dev/null +++ b/infra/cifuzz/get_coverage_test.py @@ -0,0 +1,239 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for get_coverage.py""" +import os +import json +import unittest +from unittest import mock + +from pyfakefs import fake_filesystem_unittest +import pytest + +import get_coverage + +# pylint: disable=protected-access + +TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_data') + +PROJECT_NAME = 'curl' +REPO_PATH = '/src/curl' +FUZZ_TARGET = 'curl_fuzzer' +PROJECT_COV_JSON_FILENAME = 'example_curl_cov.json' +FUZZ_TARGET_COV_JSON_FILENAME = 'example_curl_fuzzer_cov.json' +INVALID_TARGET = 'not-a-fuzz-target' + +with open(os.path.join(TEST_DATA_PATH, + PROJECT_COV_JSON_FILENAME),) as cov_file_handle: + PROJECT_COV_INFO = json.loads(cov_file_handle.read()) + + +class GetOssFuzzFuzzerStatsDirUrlTest(unittest.TestCase): + """Tests _get_oss_fuzz_fuzzer_stats_dir_url.""" + + @mock.patch('http_utils.get_json_from_url', + return_value={ + 'fuzzer_stats_dir': + 'gs://oss-fuzz-coverage/systemd/fuzzer_stats/20210303' + }) + def test_get_valid_project(self, mock_get_json_from_url): + """Tests that a project's coverage report can be downloaded and parsed. + + NOTE: This test relies on the PROJECT_NAME repo's coverage report. + The "example" project was not used because it has no coverage reports. + """ + result = get_coverage._get_oss_fuzz_fuzzer_stats_dir_url(PROJECT_NAME) + (url,), _ = mock_get_json_from_url.call_args + self.assertEqual( + 'https://storage.googleapis.com/oss-fuzz-coverage/' + 'latest_report_info/curl.json', url) + + expected_result = ( + 'https://storage.googleapis.com/oss-fuzz-coverage/systemd/fuzzer_stats/' + '20210303') + self.assertEqual(result, expected_result) + + def test_get_invalid_project(self): + """Tests that passing a bad project returns None.""" + self.assertIsNone( + get_coverage._get_oss_fuzz_fuzzer_stats_dir_url('not-a-proj')) + + +class OSSFuzzCoverageGetTargetCoverageTest(unittest.TestCase): + """Tests OSSFuzzCoverage.get_target_coverage.""" + + def setUp(self): + with mock.patch('get_coverage._get_oss_fuzz_latest_cov_report_info', + return_value=PROJECT_COV_INFO): + self.oss_fuzz_coverage = get_coverage.OSSFuzzCoverage( + REPO_PATH, PROJECT_NAME) + + @mock.patch('http_utils.get_json_from_url', return_value={}) + def test_valid_target(self, mock_get_json_from_url): + """Tests that a target's coverage report can be downloaded and parsed.""" + self.oss_fuzz_coverage.get_target_coverage(FUZZ_TARGET) + (url,), _ = mock_get_json_from_url.call_args + self.assertEqual( + 'https://storage.googleapis.com/oss-fuzz-coverage/' + 'curl/fuzzer_stats/20200226/curl_fuzzer.json', url) + + def test_invalid_target(self): + """Tests that passing an invalid target coverage report returns None.""" + self.assertIsNone( + self.oss_fuzz_coverage.get_target_coverage(INVALID_TARGET)) + + @mock.patch('get_coverage._get_oss_fuzz_latest_cov_report_info', + return_value=None) + def test_invalid_project_json(self, _): # pylint: disable=no-self-use + """Tests an invalid project JSON results in None being returned.""" + with pytest.raises(get_coverage.CoverageError): + get_coverage.OSSFuzzCoverage(REPO_PATH, PROJECT_NAME) + + +def _get_expected_curl_covered_file_list(): + """Returns the expected covered file list for + FUZZ_TARGET_COV_JSON_FILENAME.""" + curl_files_list_path = os.path.join(TEST_DATA_PATH, + 'example_curl_file_list.json') + with open(curl_files_list_path) as file_handle: + return json.loads(file_handle.read()) + + +def _get_example_curl_coverage(): + """Returns the contents of the fuzzer stats JSON file for + FUZZ_TARGET_COV_JSON_FILENAME.""" + with open(os.path.join(TEST_DATA_PATH, + FUZZ_TARGET_COV_JSON_FILENAME)) as file_handle: + return json.loads(file_handle.read()) + + +class OSSFuzzCoverageGetFilesCoveredByTargetTest(unittest.TestCase): + """Tests OSSFuzzCoverage.get_files_covered_by_target.""" + + def setUp(self): + with mock.patch('get_coverage._get_oss_fuzz_latest_cov_report_info', + return_value=PROJECT_COV_INFO): + self.oss_fuzz_coverage = get_coverage.OSSFuzzCoverage( + REPO_PATH, PROJECT_NAME) + + def test_valid_target(self): + """Tests that covered files can be retrieved from a coverage report.""" + fuzzer_cov_data = _get_example_curl_coverage() + with mock.patch('get_coverage.OSSFuzzCoverage.get_target_coverage', + return_value=fuzzer_cov_data): + file_list = self.oss_fuzz_coverage.get_files_covered_by_target( + FUZZ_TARGET) + + expected_file_list = _get_expected_curl_covered_file_list() + self.assertCountEqual(file_list, expected_file_list) + + def test_invalid_target(self): + """Tests passing invalid fuzz target returns None.""" + self.assertIsNone( + self.oss_fuzz_coverage.get_files_covered_by_target(INVALID_TARGET)) + + +class FilesystemCoverageGetFilesCoveredByTargetTest( + fake_filesystem_unittest.TestCase): + """Tests FilesystemCoverage.get_files_covered_by_target.""" + + def setUp(self): + _fuzzer_cov_data = _get_example_curl_coverage() + self._expected_file_list = _get_expected_curl_covered_file_list() + self.coverage_path = '/coverage' + self.filesystem_coverage = get_coverage.FilesystemCoverage( + REPO_PATH, self.coverage_path) + self.setUpPyfakefs() + self.fs.create_file(os.path.join(self.coverage_path, 'fuzzer_stats', + FUZZ_TARGET + '.json'), + contents=json.dumps(_fuzzer_cov_data)) + + def test_valid_target(self): + """Tests that covered files can be retrieved from a coverage report.""" + file_list = self.filesystem_coverage.get_files_covered_by_target( + FUZZ_TARGET) + self.assertCountEqual(file_list, self._expected_file_list) + + def test_invalid_target(self): + """Tests passing invalid fuzz target returns None.""" + self.assertIsNone( + self.filesystem_coverage.get_files_covered_by_target(INVALID_TARGET)) + + +class IsFileCoveredTest(unittest.TestCase): + """Tests for is_file_covered.""" + + def test_is_file_covered_covered(self): + """Tests that is_file_covered returns True for a covered file.""" + file_coverage = { + 'filename': '/src/systemd/src/basic/locale-util.c', + 'summary': { + 'regions': { + 'count': 204, + 'covered': 200, + 'notcovered': 200, + 'percent': 98.03 + } + } + } + self.assertTrue(get_coverage.is_file_covered(file_coverage)) + + def test_is_file_covered_not_covered(self): + """Tests that is_file_covered returns False for a not covered file.""" + file_coverage = { + 'filename': '/src/systemd/src/basic/locale-util.c', + 'summary': { + 'regions': { + 'count': 204, + 'covered': 0, + 'notcovered': 0, + 'percent': 0 + } + } + } + self.assertFalse(get_coverage.is_file_covered(file_coverage)) + + +class GetOssFuzzLatestCovReportInfo(unittest.TestCase): + """Tests that _get_oss_fuzz_latest_cov_report_info works as + intended.""" + + PROJECT = 'project' + LATEST_REPORT_INFO_URL = ('https://storage.googleapis.com/oss-fuzz-coverage/' + 'latest_report_info/project.json') + + @mock.patch('logging.error') + @mock.patch('http_utils.get_json_from_url', return_value={'coverage': 1}) + def test_get_oss_fuzz_latest_cov_report_info(self, mock_get_json_from_url, + mock_error): + """Tests that _get_oss_fuzz_latest_cov_report_info works as intended.""" + result = get_coverage._get_oss_fuzz_latest_cov_report_info(self.PROJECT) + self.assertEqual(result, {'coverage': 1}) + mock_error.assert_not_called() + mock_get_json_from_url.assert_called_with(self.LATEST_REPORT_INFO_URL) + + @mock.patch('logging.error') + @mock.patch('http_utils.get_json_from_url', return_value=None) + def test_get_oss_fuzz_latest_cov_report_info_fail(self, _, mock_error): + """Tests that _get_oss_fuzz_latest_cov_report_info works as intended when we + can't get latest report info.""" + result = get_coverage._get_oss_fuzz_latest_cov_report_info('project') + self.assertIsNone(result) + mock_error.assert_called_with( + 'Could not get the coverage report json from url: %s.', + self.LATEST_REPORT_INFO_URL) + + +if __name__ == '__main__': + unittest.main() |