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