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