blob: 1f98b13ee373159fa50d059cd310bb1482d30465 [file] [log] [blame]
Dirk Pranke4d164bb2021-03-24 06:52:401#!/usr/bin/env python
Avi Drissmandfd880852022-09-15 20:11:092# Copyright 2018 The Chromium Authors
Max Moroza19fd492018-10-22 17:07:113# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Tests for code coverage tools."""
6
7import os
8import re
9import shutil
10import subprocess
Yuke Liao84017e92019-03-14 18:33:2511import sys
Max Moroza19fd492018-10-22 17:07:1112import unittest
Max Moroza19fd492018-10-22 17:07:1113import coverage_utils
14
15
16def _RecursiveDirectoryListing(dirpath):
17 """Returns a list of relative paths to all files in a given directory."""
18 result = []
19 for root, _, files in os.walk(dirpath):
20 for f in files:
21 result.append(os.path.relpath(os.path.join(root, f), dirpath))
22 return result
23
24
25def _ReadFile(filepath):
26 """Returns contents of a given file."""
27 with open(filepath) as f:
28 return f.read()
29
30
31class CoverageTest(unittest.TestCase):
32
33 def setUp(self):
34 self.maxDiff = 1000
35 self.COVERAGE_TOOLS_DIR = os.path.abspath(os.path.dirname(__file__))
36 self.COVERAGE_SCRIPT = os.path.join(self.COVERAGE_TOOLS_DIR, 'coverage.py')
37 self.COVERAGE_UTILS = os.path.join(self.COVERAGE_TOOLS_DIR,
38 'coverage_utils.py')
39
40 self.CHROMIUM_SRC_DIR = os.path.dirname(
41 os.path.dirname(self.COVERAGE_TOOLS_DIR))
42 self.BUILD_DIR = os.path.join(self.CHROMIUM_SRC_DIR, 'out',
43 'code_coverage_tools_test')
44
45 self.REPORT_DIR_1 = os.path.join(self.BUILD_DIR, 'report1')
46 self.REPORT_DIR_1_NO_COMPONENTS = os.path.join(self.BUILD_DIR,
47 'report1_no_components')
48 self.REPORT_DIR_2 = os.path.join(self.BUILD_DIR, 'report2')
49 self.REPORT_DIR_3 = os.path.join(self.BUILD_DIR, 'report3')
Akekawit Jitprasertf9cb6622021-08-24 17:48:0250 self.REPORT_DIR_4 = os.path.join(self.BUILD_DIR, 'report4')
Max Moroza19fd492018-10-22 17:07:1151
52 self.LLVM_COV = os.path.join(self.CHROMIUM_SRC_DIR, 'third_party',
53 'llvm-build', 'Release+Asserts', 'bin',
54 'llvm-cov')
55
Julia Hansbrough5e3ae9a22023-06-20 19:17:0356 self.PYTHON = 'python3'
Max Moroza19fd492018-10-22 17:07:1157 self.PLATFORM = coverage_utils.GetHostPlatform()
58 if self.PLATFORM == 'win32':
59 self.LLVM_COV += '.exe'
60 self.PYTHON += '.exe'
61
62 # Even though 'is_component_build=false' is recommended, we intentionally
63 # use 'is_component_build=true' to test handling of shared libraries.
Julia Hansbrough5e3ae9a22023-06-20 19:17:0364 self.GN_ARGS = ('use_clang_coverage=true '
65 'dcheck_always_on=true '
66 'ffmpeg_branding=\"ChromeOS\" '
67 'is_component_build=true '
68 'is_debug=false '
69 'proprietary_codecs=true '
70 'use_libfuzzer=true')
Max Moroza19fd492018-10-22 17:07:1171
72 shutil.rmtree(self.BUILD_DIR, ignore_errors=True)
73
74 gn_gen_cmd = ['gn', 'gen', self.BUILD_DIR, '--args=%s' % self.GN_ARGS]
75 self.run_cmd(gn_gen_cmd)
76
77 build_cmd = [
78 'autoninja', '-C', self.BUILD_DIR, 'crypto_unittests',
79 'libpng_read_fuzzer'
80 ]
81 self.run_cmd(build_cmd)
82
83 def tearDown(self):
84 shutil.rmtree(self.BUILD_DIR, ignore_errors=True)
85
86 def run_cmd(self, cmd):
87 return subprocess.check_output(cmd, cwd=self.CHROMIUM_SRC_DIR)
88
89 def verify_component_view(self, filepath):
90 """Asserts that a given component view looks correct."""
91 # There must be several Blink and Internals components.
92 with open(filepath) as f:
93 data = f.read()
94
95 counts = data.count('Blink') + data.count('Internals')
96 self.assertGreater(counts, 5)
97
98 def verify_directory_view(self, filepath):
99 """Asserts that a given directory view looks correct."""
100 # Directory view page does a redirect to another page, extract its URL.
101 with open(filepath) as f:
102 data = f.read()
103
104 url = re.search(r'.*refresh.*url=([a-zA-Z0-9_\-\/.]+).*', data).group(1)
105 directory_view_path = os.path.join(os.path.dirname(filepath), url)
106
107 # There must be at least 'crypto' and 'third_party' directories.
108 with open(directory_view_path) as f:
109 data = f.read()
110
111 self.assertTrue('crypto' in data and 'third_party' in data)
112
113 def verify_file_view(self, filepath):
114 """Asserts that a given file view looks correct."""
115 # There must be hundreds of '.*crypto.*' files and 10+ of '.*libpng.*'.
116 with open(filepath) as f:
117 data = f.read()
118
119 self.assertGreater(data.count('crypto'), 100)
120 self.assertGreater(data.count('libpng'), 10)
121
Akekawit Jitprasertf9cb6622021-08-24 17:48:02122 def verify_lcov_file(self, filepath):
123 """Asserts that a given lcov file looks correct."""
124 with open(filepath) as f:
125 data = f.read()
126
127 self.assertGreater(data.count('SF:'), 100)
128 self.assertGreater(data.count('crypto'), 100)
129 self.assertGreater(data.count('libpng'), 10)
130
Max Moroza19fd492018-10-22 17:07:11131 def test_different_workflows_and_cross_check_the_results(self):
132 """Test a few different workflows and assert that the results are the same
133
134 and look legit.
135 """
136 # Testcase 1. End-to-end report generation using coverage.py script. This is
137 # the workflow of a regular user.
138 cmd = [
139 self.COVERAGE_SCRIPT,
140 'crypto_unittests',
141 'libpng_read_fuzzer',
142 '-v',
143 '-b',
144 self.BUILD_DIR,
145 '-o',
146 self.REPORT_DIR_1,
147 '-c'
148 '%s/crypto_unittests' % self.BUILD_DIR,
149 '-c',
150 '%s/libpng_read_fuzzer -runs=0 third_party/libpng/' % self.BUILD_DIR,
151 ]
152 self.run_cmd(cmd)
153
154 output_dir = os.path.join(self.REPORT_DIR_1, self.PLATFORM)
155 self.verify_component_view(
156 os.path.join(output_dir, 'component_view_index.html'))
157 self.verify_directory_view(
158 os.path.join(output_dir, 'directory_view_index.html'))
159 self.verify_file_view(os.path.join(output_dir, 'file_view_index.html'))
160
161 # Also try generating a report without components view. Useful for cross
162 # checking with the report produced in the testcase #3.
163 cmd = [
164 self.COVERAGE_SCRIPT,
165 'crypto_unittests',
166 'libpng_read_fuzzer',
167 '-v',
168 '-b',
169 self.BUILD_DIR,
170 '-o',
171 self.REPORT_DIR_1_NO_COMPONENTS,
172 '-c'
173 '%s/crypto_unittests' % self.BUILD_DIR,
174 '-c',
175 '%s/libpng_read_fuzzer -runs=0 third_party/libpng/' % self.BUILD_DIR,
176 '--no-component-view',
177 ]
178 self.run_cmd(cmd)
179
180 output_dir = os.path.join(self.REPORT_DIR_1_NO_COMPONENTS, self.PLATFORM)
181 self.verify_directory_view(
182 os.path.join(output_dir, 'directory_view_index.html'))
183 self.verify_file_view(os.path.join(output_dir, 'file_view_index.html'))
184 self.assertFalse(
185 os.path.exists(os.path.join(output_dir, 'component_view_index.html')))
186
187 # Testcase #2. Run the script for post processing in Chromium tree. This is
188 # the workflow of the code coverage bots.
189 instr_profile_path = os.path.join(self.REPORT_DIR_1, self.PLATFORM,
190 'coverage.profdata')
191
192 cmd = [
193 self.COVERAGE_SCRIPT,
194 'crypto_unittests',
195 'libpng_read_fuzzer',
196 '-v',
197 '-b',
198 self.BUILD_DIR,
199 '-p',
200 instr_profile_path,
201 '-o',
202 self.REPORT_DIR_2,
203 ]
204 self.run_cmd(cmd)
205
206 # Verify that the output dirs are the same except of the expected diff.
207 report_1_listing = set(_RecursiveDirectoryListing(self.REPORT_DIR_1))
208 report_2_listing = set(_RecursiveDirectoryListing(self.REPORT_DIR_2))
209 logs_subdir = os.path.join(self.PLATFORM, 'logs')
210 self.assertEqual(
211 set([
212 os.path.join(self.PLATFORM, 'coverage.profdata'),
213 os.path.join(logs_subdir, 'crypto_unittests_output.log'),
214 os.path.join(logs_subdir, 'libpng_read_fuzzer_output.log'),
215 ]), report_1_listing - report_2_listing)
216
217 output_dir = os.path.join(self.REPORT_DIR_2, self.PLATFORM)
218 self.verify_component_view(
219 os.path.join(output_dir, 'component_view_index.html'))
220 self.verify_directory_view(
221 os.path.join(output_dir, 'directory_view_index.html'))
222 self.verify_file_view(os.path.join(output_dir, 'file_view_index.html'))
223
224 # Verify that the file view pages are binary equal.
225 report_1_file_view_data = _ReadFile(
226 os.path.join(self.REPORT_DIR_1, self.PLATFORM, 'file_view_index.html'))
227 report_2_file_view_data = _ReadFile(
228 os.path.join(self.REPORT_DIR_2, self.PLATFORM, 'file_view_index.html'))
229 self.assertEqual(report_1_file_view_data, report_2_file_view_data)
230
231 # Testcase #3, run coverage_utils.py on manually produced report and summary
232 # file. This is the workflow of OSS-Fuzz code coverage job.
233 objects = [
234 '-object=%s' % os.path.join(self.BUILD_DIR, 'crypto_unittests'),
235 '-object=%s' % os.path.join(self.BUILD_DIR, 'libpng_read_fuzzer'),
236 ]
237
238 cmd = [
239 self.PYTHON,
240 self.COVERAGE_UTILS,
241 '-v',
242 'shared_libs',
243 '-build-dir=%s' % self.BUILD_DIR,
244 ] + objects
245
246 shared_libraries = self.run_cmd(cmd)
247 objects.extend(shared_libraries.split())
248
249 instr_profile_path = os.path.join(self.REPORT_DIR_1_NO_COMPONENTS,
250 self.PLATFORM, 'coverage.profdata')
251 cmd = [
252 self.LLVM_COV,
253 'show',
254 '-format=html',
255 '-output-dir=%s' % self.REPORT_DIR_3,
256 '-instr-profile=%s' % instr_profile_path,
257 ] + objects
258 if self.PLATFORM in ['linux', 'mac']:
259 cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
260 self.run_cmd(cmd)
261
262 cmd = [
263 self.LLVM_COV,
264 'export',
265 '-summary-only',
266 '-instr-profile=%s' % instr_profile_path,
267 ] + objects
268 summary_output = self.run_cmd(cmd)
269
270 summary_path = os.path.join(self.REPORT_DIR_3, 'summary.json')
Akekawit Jitprasertf9cb6622021-08-24 17:48:02271 with open(summary_path, 'wb') as f:
Max Moroza19fd492018-10-22 17:07:11272 f.write(summary_output)
273
274 cmd = [
275 self.PYTHON,
276 self.COVERAGE_UTILS,
277 '-v',
278 'post_process',
279 '-src-root-dir=%s' % self.CHROMIUM_SRC_DIR,
280 '-summary-file=%s' % summary_path,
281 '-output-dir=%s' % self.REPORT_DIR_3,
282 ]
283 self.run_cmd(cmd)
284
285 output_dir = os.path.join(self.REPORT_DIR_3, self.PLATFORM)
286 self.verify_directory_view(
287 os.path.join(output_dir, 'directory_view_index.html'))
288 self.verify_file_view(os.path.join(output_dir, 'file_view_index.html'))
289 self.assertFalse(
290 os.path.exists(os.path.join(output_dir, 'component_view_index.html')))
291
292 # Verify that the file view pages are binary equal.
293 report_1_file_view_data_no_component = _ReadFile(
294 os.path.join(self.REPORT_DIR_1_NO_COMPONENTS, self.PLATFORM,
295 'file_view_index.html'))
296 report_3_file_view_data = _ReadFile(
297 os.path.join(self.REPORT_DIR_3, self.PLATFORM, 'file_view_index.html'))
298 self.assertEqual(report_1_file_view_data_no_component,
299 report_3_file_view_data)
300
Akekawit Jitprasertf9cb6622021-08-24 17:48:02301 # Testcase 4. Export coverage data in lcov format using coverage.py script.
302 cmd = [
303 self.COVERAGE_SCRIPT,
304 'crypto_unittests',
305 'libpng_read_fuzzer',
306 '--format',
307 'lcov',
308 '-v',
309 '-b',
310 self.BUILD_DIR,
311 '-o',
312 self.REPORT_DIR_4,
313 '-c'
314 '%s/crypto_unittests' % self.BUILD_DIR,
315 '-c',
316 '%s/libpng_read_fuzzer -runs=0 third_party/libpng/' % self.BUILD_DIR,
317 ]
318 self.run_cmd(cmd)
319
320 output_dir = os.path.join(self.REPORT_DIR_4, self.PLATFORM)
321 self.verify_lcov_file(os.path.join(output_dir, 'coverage.lcov'))
322
Max Moroza19fd492018-10-22 17:07:11323
324if __name__ == '__main__':
325 unittest.main()