blob: b7b084c18e0eadd780100e6fd6185b1d11f86add [file] [log] [blame]
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -08001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium OS 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
6"""Unit tests for cros_fuzz."""
7
8from __future__ import print_function
9
10import os
Mike Frysinger03b983f2020-02-21 02:31:49 -050011import sys
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080012
13from chromite.lib import cros_logging as logging
14from chromite.lib import cros_test_lib
15from chromite.scripts import cros_fuzz
16
Mike Frysinger03b983f2020-02-21 02:31:49 -050017
18assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
19
20
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -080021DEFAULT_MAX_TOTAL_TIME_OPTION = cros_fuzz.GetLibFuzzerOption(
22 cros_fuzz.MAX_TOTAL_TIME_OPTION_NAME,
23 cros_fuzz.MAX_TOTAL_TIME_DEFAULT_VALUE)
24
25FUZZ_TARGET = 'fuzzer'
26FUZZER_COVERAGE_PATH = (
27 '/build/amd64-generic/tmp/fuzz/coverage-report/%s' % FUZZ_TARGET)
28
29BOARD = 'amd64-generic'
30
31
32class SysrootPathTest(cros_test_lib.TestCase):
33 """Tests the SysrootPath class."""
34
35 def setUp(self):
36 self.path_to_sysroot = _SetPathToSysroot()
37 self.sysroot_relative_path = '/dir'
38 self.basename = os.path.basename(self.sysroot_relative_path)
39 # Chroot relative path of a path that is in the sysroot.
40 self.path_in_sysroot = os.path.join(self.path_to_sysroot, self.basename)
41
42 def testSysroot(self):
43 """Tests that SysrootPath.sysroot returns expected result."""
44 sysroot_path = cros_fuzz.SysrootPath(self.sysroot_relative_path)
45 self.assertEqual(self.sysroot_relative_path, sysroot_path.sysroot)
46
47 def testChroot(self):
48 """Tests that SysrootPath.chroot returns expected result."""
49 sysroot_path = cros_fuzz.SysrootPath(self.sysroot_relative_path)
50 expected = os.path.join(self.path_to_sysroot, self.basename)
51 self.assertEqual(expected, sysroot_path.chroot)
52
53 def testIsSysrootPath(self):
54 """Tests that the IsSysrootPath can tell what is in the sysroot."""
55 self.assertTrue(cros_fuzz.SysrootPath.IsPathInSysroot(self.path_to_sysroot))
56 self.assertTrue(cros_fuzz.SysrootPath.IsPathInSysroot(self.path_in_sysroot))
57 path_not_in_sysroot_1 = os.path.join(
58 os.path.dirname(self.path_to_sysroot), self.basename)
59 self.assertFalse(
60 cros_fuzz.SysrootPath.IsPathInSysroot(path_not_in_sysroot_1))
61 path_not_in_sysroot_2 = os.path.join('/dir/build/amd64-generic')
62 self.assertFalse(
63 cros_fuzz.SysrootPath.IsPathInSysroot(path_not_in_sysroot_2))
64
65 def testFromChrootPathInSysroot(self):
66 """Tests that FromChrootPathInSysroot converts paths properly."""
67 # Test that it raises an assertion error when the path is not in the
68 # sysroot.
69 path_not_in_sysroot_1 = os.path.join(
70 os.path.dirname(self.path_to_sysroot), 'dir')
71 with self.assertRaises(AssertionError):
72 cros_fuzz.SysrootPath.FromChrootPathInSysroot(path_not_in_sysroot_1)
73
74 sysroot_path = cros_fuzz.SysrootPath.FromChrootPathInSysroot(
75 self.path_in_sysroot)
76 self.assertEqual(self.sysroot_relative_path, sysroot_path)
77
78
79class GetPathForCopyTest(cros_test_lib.TestCase):
80 """Tests GetPathForCopy."""
81
82 def testGetPathForCopy(self):
83 """Test that GetPathForCopy gives us the correct sysroot directory."""
84 _SetPathToSysroot()
85 directory = '/path/to/directory'
86 parent = 'parent'
87 child = os.path.basename(directory)
88 path_to_sysroot = cros_fuzz.SysrootPath.path_to_sysroot
89 storage_directory = cros_fuzz.SCRIPT_STORAGE_DIRECTORY
90
91 sysroot_path = cros_fuzz.GetPathForCopy(parent, child)
92
93 expected_chroot_path = os.path.join(path_to_sysroot, 'tmp',
94 storage_directory, parent, child)
95 self.assertEqual(expected_chroot_path, sysroot_path.chroot)
96
97 expected_sysroot_path = os.path.join('/', 'tmp', storage_directory, parent,
98 child)
99 self.assertEqual(expected_sysroot_path, sysroot_path.sysroot)
100
101
102class GetLibFuzzerOptionTest(cros_test_lib.TestCase):
103 """Tests GetLibFuzzerOption."""
104
105 def testGetLibFuzzerOption(self):
106 """Tests that GetLibFuzzerOption returns a correct libFuzzer option."""
107 expected = '-max_total_time=60'
108 self.assertEqual(expected, cros_fuzz.GetLibFuzzerOption(
109 'max_total_time', 60))
110
111
112class LimitFuzzingTest(cros_test_lib.TestCase):
113 """Tests LimitFuzzing."""
114
115 def setUp(self):
116 self.fuzz_command = ['./fuzzer', '-rss_limit_mb=4096']
117 self.corpus = None
118
119 def _Helper(self, expected_command=None):
120 """Calls LimitFuzzing and asserts fuzz_command equals |expected_command|.
121
122 If |expected| is None, then it is set to self.fuzz_command before calling
123 LimitFuzzing.
124 """
125 if expected_command is None:
126 expected_command = self.fuzz_command[:]
127 cros_fuzz.LimitFuzzing(self.fuzz_command, self.corpus)
128 self.assertEqual(expected_command, self.fuzz_command)
129
130 def testCommandHasMaxTotalTime(self):
131 """Tests that no limit is added when user specifies -max_total_time."""
132 self.fuzz_command.append('-max_total_time=60')
133 self._Helper()
134
135 def testCommandHasRuns(self):
136 """Tests that no limit is added when user specifies -runs"""
137 self.fuzz_command.append('-runs=1')
138 self._Helper()
139
140 def testCommandHasCorpus(self):
141 """Tests that a limit is added when user specifies a corpus."""
142 self.corpus = 'corpus'
143 expected = self.fuzz_command + ['-runs=0']
144 self._Helper(expected)
145
146 def testNoLimitOrCorpus(self):
147 """Tests that a limit is added when user specifies no corpus or limit."""
148 expected = self.fuzz_command + [DEFAULT_MAX_TOTAL_TIME_OPTION]
149 self._Helper(expected)
150
151
152class RunSysrootCommandMockTestCase(cros_test_lib.MockTestCase):
153 """Class for TestCases that call RunSysrootCommand."""
154
155 def setUp(self):
156 _SetPathToSysroot()
157 self.expected_command = None
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800158 self.expected_extra_env = None
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800159 self.PatchObject(
160 cros_fuzz,
161 'RunSysrootCommand',
162 side_effect=self.MockedRunSysrootCommand)
163
164
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800165 def MockedRunSysrootCommand(self, command, extra_env=None,
166 **kwargs): # pylint: disable=unused-argument
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800167 """The mocked version of RunSysrootCommand.
168
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800169 Asserts |command| and |extra_env| are what is expected.
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800170 """
171 self.assertEqual(self.expected_command, command)
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800172 self.assertEqual(self.expected_extra_env, extra_env)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800173
174
175class RunFuzzerTest(RunSysrootCommandMockTestCase):
176 """Tests RunFuzzer."""
177
178 def setUp(self):
179 self.corpus_path = None
180 self.fuzz_args = ''
181 self.testcase_path = None
182 self.expected_command = [
183 cros_fuzz.GetFuzzerSysrootPath(FUZZ_TARGET).sysroot,
184 ]
Jonathan Metzmanb2c33732018-11-08 11:33:35 -0800185 self.expected_extra_env = {
186 'ASAN_OPTIONS': 'log_path=stderr',
187 'MSAN_OPTIONS': 'log_path=stderr',
188 'UBSAN_OPTIONS': 'log_path=stderr',
189 }
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800190
191 def _Helper(self):
192 """Calls RunFuzzer."""
193 cros_fuzz.RunFuzzer(FUZZ_TARGET, self.corpus_path, self.fuzz_args,
194 self.testcase_path)
195
196 def testNoOptional(self):
197 """Tests correct command and env used when not specifying optional."""
198 self.expected_command.append(DEFAULT_MAX_TOTAL_TIME_OPTION)
199 self._Helper()
200
201 def testFuzzArgs(self):
202 """Tests that the correct command is used when fuzz_args is specified."""
203 fuzz_args = [DEFAULT_MAX_TOTAL_TIME_OPTION, '-fake_arg=fake_value']
204 self.expected_command.extend(fuzz_args)
205 self.fuzz_args = ' '.join(fuzz_args)
206 self._Helper()
207
208 def testTestCase(self):
209 """Tests a testcase is used when specified."""
210 self.testcase_path = '/path/to/testcase'
211 self.expected_command.append(self.testcase_path)
212 self._Helper()
213
214
215class MergeProfrawTest(RunSysrootCommandMockTestCase):
216 """Tests MergeProfraw."""
217
218 def testMergeProfraw(self):
219 """Tests that MergeProfraw works as expected."""
220 # Parent class will assert that these commands are used.
221 profdata_path = cros_fuzz.GetProfdataPath(FUZZ_TARGET)
222 self.expected_command = [
223 'llvm-profdata',
224 'merge',
225 '-sparse',
226 cros_fuzz.DEFAULT_PROFRAW_PATH,
227 '-o',
228 profdata_path.sysroot,
229 ]
230 cros_fuzz.MergeProfraw(FUZZ_TARGET)
231
232
233class GenerateCoverageReportTest(cros_test_lib.RunCommandTestCase):
234 """Tests GenerateCoverageReport."""
235
236 def setUp(self):
237 _SetPathToSysroot()
238 self.fuzzer_path = cros_fuzz.GetFuzzerSysrootPath(FUZZ_TARGET).chroot
239 self.profdata_path = cros_fuzz.GetProfdataPath(FUZZ_TARGET)
240
241 def testWithSharedLibraries(self):
242 """Tests that right command is used when specifying shared libraries."""
243 shared_libraries = ['shared_lib.so']
244 cros_fuzz.GenerateCoverageReport(FUZZ_TARGET, shared_libraries)
245 instr_profile_option = '-instr-profile=%s' % self.profdata_path.chroot
246 output_dir_option = '-output-dir=%s' % FUZZER_COVERAGE_PATH
247 expected_command = [
248 'llvm-cov',
249 'show',
250 '-object',
251 self.fuzzer_path,
252 '-object',
253 'shared_lib.so',
254 '-format=html',
255 instr_profile_option,
256 output_dir_option,
257 ]
258 self.assertCommandCalled(
Mike Frysinger0282d222019-12-17 17:15:48 -0500259 expected_command, stderr=True, debug_level=logging.DEBUG)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800260
261 def testNoSharedLibraries(self):
262 """Tests the right coverage command is used without shared libraries."""
263 shared_libraries = []
264 cros_fuzz.GenerateCoverageReport(FUZZ_TARGET, shared_libraries)
265 instr_profile_option = '-instr-profile=%s' % self.profdata_path.chroot
266 output_dir_option = '-output-dir=%s' % FUZZER_COVERAGE_PATH
267 expected_command = [
268 'llvm-cov', 'show', '-object', self.fuzzer_path, '-format=html',
269 instr_profile_option, output_dir_option
270 ]
271 self.assertCommandCalled(
Mike Frysinger0282d222019-12-17 17:15:48 -0500272 expected_command, stderr=True, debug_level=logging.DEBUG)
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800273
274
275class RunSysrootCommandTest(cros_test_lib.RunCommandTestCase):
276 """Tests RunSysrootCommand."""
277
278 def testRunSysrootCommand(self):
279 """Tests RunSysrootCommand creates a proper command to run in sysroot."""
280 command = ['./fuzz', '-rss_limit_mb=4096']
281 cros_fuzz.RunSysrootCommand(command)
282 sysroot_path = _SetPathToSysroot()
283 expected_command = ['sudo', '--', 'chroot', sysroot_path]
284 expected_command.extend(command)
285 self.assertCommandCalled(expected_command, debug_level=logging.DEBUG)
286
287
288class GetBuildExtraEnvTest(cros_test_lib.TestCase):
289 """Tests GetBuildExtraEnv."""
290
291 TEST_ENV_VAR = 'TEST_VAR'
292
293 def testUseAndFeaturesNotClobbered(self):
294 """Tests that values of certain environment variables are appended to."""
295 vars_and_values = {'FEATURES': 'foo', 'USE': 'bar'}
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400296 for var, value in vars_and_values.items():
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800297 os.environ[var] = value
298 extra_env = cros_fuzz.GetBuildExtraEnv(cros_fuzz.BuildType.COVERAGE)
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400299 for var, value in vars_and_values.items():
Jonathan Metzmand5ee1c62018-11-05 10:33:08 -0800300 self.assertIn(value, extra_env[var])
301
302 def testCoverageBuild(self):
303 """Tests that a proper environment is returned for a coverage build."""
304 extra_env = cros_fuzz.GetBuildExtraEnv(cros_fuzz.BuildType.COVERAGE)
305 for expected_flag in ['fuzzer', 'coverage', 'asan']:
306 self.assertIn(expected_flag, extra_env['USE'])
307 self.assertIn('noclean', extra_env['FEATURES'])
308
309
310def _SetPathToSysroot():
311 """Calls SysrootPath.SetPathToSysroot and returns result."""
312 return cros_fuzz.SysrootPath.SetPathToSysroot(BOARD)