1# Copyright 2021 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Tests for coverage.py""" 15import os 16import json 17import unittest 18from unittest import mock 19 20import coverage 21 22# pylint: disable=protected-access 23 24TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 25 'test_data') 26 27PROJECT_NAME = 'curl' 28REPO_PATH = '/src/curl' 29FUZZ_TARGET = 'curl_fuzzer' 30PROJECT_COV_JSON_FILENAME = 'example_curl_cov.json' 31FUZZ_TARGET_COV_JSON_FILENAME = 'example_curl_fuzzer_cov.json' 32INVALID_TARGET = 'not-a-fuzz-target' 33 34with open(os.path.join(TEST_DATA_PATH, 35 PROJECT_COV_JSON_FILENAME),) as cov_file_handle: 36 PROJECT_COV_INFO = json.loads(cov_file_handle.read()) 37 38 39class GetFuzzerStatsDirUrlTest(unittest.TestCase): 40 """Tests _get_fuzzer_stats_dir_url.""" 41 42 @mock.patch('coverage.get_json_from_url', 43 return_value={ 44 'fuzzer_stats_dir': 45 'gs://oss-fuzz-coverage/systemd/fuzzer_stats/20210303' 46 }) 47 def test_get_valid_project(self, mocked_get_json_from_url): 48 """Tests that a project's coverage report can be downloaded and parsed. 49 50 NOTE: This test relies on the PROJECT_NAME repo's coverage report. 51 The "example" project was not used because it has no coverage reports. 52 """ 53 result = coverage._get_fuzzer_stats_dir_url(PROJECT_NAME) 54 (url,), _ = mocked_get_json_from_url.call_args 55 self.assertEqual( 56 'https://storage.googleapis.com/oss-fuzz-coverage/' 57 'latest_report_info/curl.json', url) 58 59 expected_result = ( 60 'https://storage.googleapis.com/oss-fuzz-coverage/systemd/fuzzer_stats/' 61 '20210303') 62 self.assertEqual(result, expected_result) 63 64 def test_get_invalid_project(self): 65 """Tests that passing a bad project returns None.""" 66 self.assertIsNone(coverage._get_fuzzer_stats_dir_url('not-a-proj')) 67 68 69class GetTargetCoverageReportTest(unittest.TestCase): 70 """Tests get_target_coverage_report.""" 71 72 def setUp(self): 73 with mock.patch('coverage._get_latest_cov_report_info', 74 return_value=PROJECT_COV_INFO): 75 self.coverage_getter = coverage.OssFuzzCoverageGetter( 76 PROJECT_NAME, REPO_PATH) 77 78 @mock.patch('coverage.get_json_from_url', return_value={}) 79 def test_valid_target(self, mocked_get_json_from_url): 80 """Tests that a target's coverage report can be downloaded and parsed.""" 81 self.coverage_getter.get_target_coverage_report(FUZZ_TARGET) 82 (url,), _ = mocked_get_json_from_url.call_args 83 self.assertEqual( 84 'https://storage.googleapis.com/oss-fuzz-coverage/' 85 'curl/fuzzer_stats/20200226/curl_fuzzer.json', url) 86 87 def test_invalid_target(self): 88 """Tests that passing an invalid target coverage report returns None.""" 89 self.assertIsNone( 90 self.coverage_getter.get_target_coverage_report(INVALID_TARGET)) 91 92 @mock.patch('coverage._get_latest_cov_report_info', return_value=None) 93 def test_invalid_project_json(self, _): 94 """Tests an invalid project JSON results in None being returned.""" 95 coverage_getter = coverage.OssFuzzCoverageGetter(PROJECT_NAME, REPO_PATH) 96 self.assertIsNone(coverage_getter.get_target_coverage_report(FUZZ_TARGET)) 97 98 99class GetFilesCoveredByTargetTest(unittest.TestCase): 100 """Tests get_files_covered_by_target.""" 101 102 def setUp(self): 103 with mock.patch('coverage._get_latest_cov_report_info', 104 return_value=PROJECT_COV_INFO): 105 self.coverage_getter = coverage.OssFuzzCoverageGetter( 106 PROJECT_NAME, REPO_PATH) 107 108 def test_valid_target(self): 109 """Tests that covered files can be retrieved from a coverage report.""" 110 with open(os.path.join(TEST_DATA_PATH, 111 FUZZ_TARGET_COV_JSON_FILENAME),) as file_handle: 112 fuzzer_cov_info = json.loads(file_handle.read()) 113 114 with mock.patch('coverage.OssFuzzCoverageGetter.get_target_coverage_report', 115 return_value=fuzzer_cov_info): 116 file_list = self.coverage_getter.get_files_covered_by_target(FUZZ_TARGET) 117 118 curl_files_list_path = os.path.join(TEST_DATA_PATH, 119 'example_curl_file_list.json') 120 with open(curl_files_list_path) as file_handle: 121 expected_file_list = json.loads(file_handle.read()) 122 self.assertCountEqual(file_list, expected_file_list) 123 124 def test_invalid_target(self): 125 """Tests passing invalid fuzz target returns None.""" 126 self.assertIsNone( 127 self.coverage_getter.get_files_covered_by_target(INVALID_TARGET)) 128 129 130class IsFileCoveredTest(unittest.TestCase): 131 """Tests for is_file_covered.""" 132 133 def test_is_file_covered_covered(self): 134 """Tests that is_file_covered returns True for a covered file.""" 135 file_coverage = { 136 'filename': '/src/systemd/src/basic/locale-util.c', 137 'summary': { 138 'regions': { 139 'count': 204, 140 'covered': 200, 141 'notcovered': 200, 142 'percent': 98.03 143 } 144 } 145 } 146 self.assertTrue(coverage.is_file_covered(file_coverage)) 147 148 def test_is_file_covered_not_covered(self): 149 """Tests that is_file_covered returns False for a not covered file.""" 150 file_coverage = { 151 'filename': '/src/systemd/src/basic/locale-util.c', 152 'summary': { 153 'regions': { 154 'count': 204, 155 'covered': 0, 156 'notcovered': 0, 157 'percent': 0 158 } 159 } 160 } 161 self.assertFalse(coverage.is_file_covered(file_coverage)) 162 163 164class GetLatestCovReportInfo(unittest.TestCase): 165 """Tests that _get_latest_cov_report_info works as intended.""" 166 167 PROJECT = 'project' 168 LATEST_REPORT_INFO_URL = ('https://storage.googleapis.com/oss-fuzz-coverage/' 169 'latest_report_info/project.json') 170 171 @mock.patch('logging.error') 172 @mock.patch('coverage.get_json_from_url', return_value={'coverage': 1}) 173 def test_get_latest_cov_report_info(self, mocked_get_json_from_url, 174 mocked_error): 175 """Tests that _get_latest_cov_report_info works as intended.""" 176 result = coverage._get_latest_cov_report_info(self.PROJECT) 177 self.assertEqual(result, {'coverage': 1}) 178 mocked_error.assert_not_called() 179 mocked_get_json_from_url.assert_called_with(self.LATEST_REPORT_INFO_URL) 180 181 @mock.patch('logging.error') 182 @mock.patch('coverage.get_json_from_url', return_value=None) 183 def test_get_latest_cov_report_info_fail(self, _, mocked_error): 184 """Tests that _get_latest_cov_report_info works as intended when we can't 185 get latest report info.""" 186 result = coverage._get_latest_cov_report_info('project') 187 self.assertIsNone(result) 188 mocked_error.assert_called_with( 189 'Could not get the coverage report json from url: %s.', 190 self.LATEST_REPORT_INFO_URL) 191 192 193if __name__ == '__main__': 194 unittest.main() 195