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