blob: e7171716114c3219e06189213a085c2eb7647799 [file]
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
import unittest
from unittest import mock
import run_all_fuzzers
@unittest.skipUnless(sys.platform == 'linux', 'Only linux is supported')
class RunAllFuzzersTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
gn_args = (
'use_clang_coverage=true '
'dcheck_always_on=true '
'ffmpeg_branding="ChromeOS" '
'is_component_build=false '
'is_debug=false '
'proprietary_codecs=true '
'use_remoteexec=true '
'use_libfuzzer=true '
'use_clang_modules=false'
)
cls.testfuzzer1 = 'xml_parser_fuzzer'
cls.testfuzzer2 = 'query_parser_fuzzer'
cls.chromium_src_dir = os.path.join(
os.path.abspath(os.path.dirname(__file__)), "..", ".."
)
fuzzer_binaries_dir = "out/run_all_fuzzers_test"
pathlib.Path(fuzzer_binaries_dir).mkdir(parents=True, exist_ok=True)
cls.fuzzer_binaries_dir = fuzzer_binaries_dir
gn_gen_cmd = ['gn', 'gen', cls.fuzzer_binaries_dir, '--args=%s' % gn_args]
try:
subprocess.check_output(gn_gen_cmd, cwd=cls.chromium_src_dir)
except subprocess.CalledProcessError as e:
print("GN command failed. Error:")
print(e.output)
build_cmd = [
'autoninja',
'-C',
cls.fuzzer_binaries_dir,
cls.testfuzzer1,
cls.testfuzzer2,
]
with open("test.log", "wb") as f:
try:
process = subprocess.Popen(
build_cmd, cwd=cls.chromium_src_dir, stdout=subprocess.PIPE
)
for c in iter(lambda: process.stdout.read(1), b''):
f.write(c)
except subprocess.CalledProcessError as e:
print("Build command failed. Error:")
print(e.output)
@classmethod
def tearDownClass(cls):
# ignore_errors allows us to delete the directory even though the directory
# is non-empty. This is what we want, since we created these temporarily,
# only for the purpose of tests.
shutil.rmtree(cls.fuzzer_binaries_dir, ignore_errors=True)
def setUp(self):
self.fuzzer_corpora_dir = tempfile.mkdtemp()
self.profdata_outdir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.fuzzer_corpora_dir, ignore_errors=True)
shutil.rmtree(self.profdata_outdir, ignore_errors=True)
def test_wrong_arguments(self):
cmd = [
'python3',
'tools/code_coverage/run_all_fuzzers.py',
'--fuzzer-binaries-dir',
self.fuzzer_binaries_dir,
'--fuzzer-corpora-dir',
self.fuzzer_corpora_dir,
]
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_call(cmd, cwd=self.chromium_src_dir)
assert "returned non-zero exit status 2" in str(e.exception)
cmd = [
'python3',
'tools/code_coverage/run_all_fuzzers.py',
'--fuzzer-binaries-dir',
self.fuzzer_binaries_dir,
'--profdata-outdir',
self.profdata_outdir,
]
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_call(cmd, cwd=self.chromium_src_dir)
assert "returned non-zero exit status 2" in str(e.exception)
cmd = [
'python3',
'tools/code_coverage/run_all_fuzzers.py',
'--fuzzer-corpora-dir',
self.fuzzer_corpora_dir,
'--profdata-outdir',
self.profdata_outdir,
]
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_call(cmd, cwd=self.chromium_src_dir)
assert "returned non-zero exit status 2" in str(e.exception)
def test_libfuzzer_fuzzers_succeed(self):
os.makedirs(os.path.join(self.fuzzer_corpora_dir, self.testfuzzer1))
corporadir1 = os.path.join(self.fuzzer_corpora_dir, self.testfuzzer1)
os.makedirs(os.path.join(self.fuzzer_corpora_dir, self.testfuzzer2))
corporadir2 = os.path.join(self.fuzzer_corpora_dir, self.testfuzzer2)
for letter in ["a", "b", "c", "d"]:
f1 = open(os.path.join(corporadir1, letter), "x")
f2 = open(os.path.join(corporadir2, letter), "x")
f1.write(letter)
f2.write(letter)
f1.close()
f2.close()
cmd = [
'python3',
'tools/code_coverage/run_all_fuzzers.py',
'--fuzzer-binaries-dir',
self.fuzzer_binaries_dir,
'--fuzzer-corpora-dir',
self.fuzzer_corpora_dir,
'--profdata-outdir',
self.profdata_outdir,
'--fuzzer',
'libfuzzer',
]
subprocess.check_call(cmd, cwd=self.chromium_src_dir)
expected_profdata = sorted(
[self.testfuzzer1 + ".profdata", self.testfuzzer2 + ".profdata"]
)
actual_profdata = sorted(os.listdir(self.profdata_outdir))
assert expected_profdata == actual_profdata, (
"Expected " + str(expected_profdata) + " but got " + str(actual_profdata)
)
def test_blackbox_fuzzers_succeed(self):
# Create a dummy chrome binary in a temp dir.
with tempfile.TemporaryDirectory() as bin_dir:
chrome_bin = os.path.join(bin_dir, "chrome")
with open(chrome_bin, "w") as f:
f.write("#!/bin/sh\n")
f.write("exit 0\n")
os.chmod(chrome_bin, 0o755)
# Create dummy testcases in the corpora_dir directory.
with tempfile.TemporaryDirectory() as corpora_dir:
with open(os.path.join(corpora_dir, "test.html"), "w") as f:
f.write("<html></html>")
with tempfile.TemporaryDirectory() as out_dir:
cmd = [
sys.executable,
'tools/code_coverage/run_all_fuzzers.py',
'--fuzzer-binaries-dir',
bin_dir,
'--fuzzer-corpora-dir',
corpora_dir,
'--profdata-outdir',
out_dir,
'--fuzzer',
'blackbox',
'--target',
'chrome',
]
# Verify the script runs without crashing when blackbox is specified.
subprocess.check_call(cmd, cwd=self.chromium_src_dir)
class TargetDetailsWindowsTest(unittest.TestCase):
def setUp(self):
self.bin_dir = tempfile.mkdtemp()
self.corpora_dir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.bin_dir, ignore_errors=True)
shutil.rmtree(self.corpora_dir, ignore_errors=True)
def test_get_all_target_details_windows_exe_resolution(self):
pathlib.Path(os.path.join(self.bin_dir, 'target1.exe')).touch()
pathlib.Path(os.path.join(self.bin_dir, 'stamp_fuzzer')).touch()
pathlib.Path(os.path.join(self.bin_dir, 'chrome.exe')).touch()
os.makedirs(os.path.join(self.corpora_dir, 'target1'))
os.makedirs(os.path.join(self.corpora_dir, 'stamp_fuzzer'))
args = argparse.Namespace(
fuzzer_binaries_dir=self.bin_dir,
fuzzer_corpora_dir=self.corpora_dir,
fuzzer='libfuzzer',
testcase_timeout=60,
)
with mock.patch('sys.platform', 'win32'):
details = run_all_fuzzers._get_all_target_details(args)
target_map = {d['name']: d for d in details}
self.assertIn('target1', target_map)
self.assertNotIn('chrome', target_map)
self.assertNotIn('stamp_fuzzer', target_map)
self.assertEqual(
target_map['target1']['cmd_runner'].cmd[0],
os.path.join(self.bin_dir, 'target1.exe'),
)
def test_resolve_target_binary_non_windows_requires_executable_bit(self):
exec_bin = os.path.join(self.bin_dir, 'exec_fuzzer')
non_exec_bin = os.path.join(self.bin_dir, 'non_exec_fuzzer')
pathlib.Path(exec_bin).touch()
pathlib.Path(non_exec_bin).touch()
os.chmod(exec_bin, 0o755)
os.chmod(non_exec_bin, 0o644)
with mock.patch('sys.platform', 'linux'):
self.assertEqual(
run_all_fuzzers._resolve_target_binary(self.bin_dir, 'exec_fuzzer'),
exec_bin,
)
self.assertIsNone(
run_all_fuzzers._resolve_target_binary(self.bin_dir, 'non_exec_fuzzer')
)
def test_run_and_log_oserror_returns_false(self):
err = OSError(8, '%1 is not a valid Win32 application')
with mock.patch('subprocess.run', side_effect=err):
self.assertFalse(
run_all_fuzzers._run_and_log(['fuzzer.exe'], {}, 1, 'annotation')
)
class BlackboxCoverageTest(unittest.TestCase):
def test_run_testcases_continuous_merges_all_profraws(self):
def write_profiles(env, **_):
out_dir = os.path.dirname(env['LLVM_PROFILE_FILE'])
for name, data in [
('t_1.profraw', 'x'),
('child_pool-0.profraw', 'x'),
('child_pool-1.profraw', ''),
]:
pathlib.Path(out_dir, name).write_text(data)
return True
env = {}
runner = mock.Mock(**{'run_testcases.side_effect': write_profiles})
with mock.patch.object(run_all_fuzzers, '_accumulated_profdata_merge') as m:
run_all_fuzzers._run_testcases(
'chrome', runner, env, ['t.html'], 'out.profdata', 1, True
)
self.assertTrue(env['LLVM_PROFILE_FILE'].endswith('_%p%c.profraw'))
merged = sorted(os.path.basename(f) for f in m.call_args[0][0])
self.assertEqual(merged, ['child_pool-0.profraw', 't_1.profraw'])
def test_blackbox_target_details(self):
with tempfile.TemporaryDirectory() as bin_dir:
chrome = os.path.join(bin_dir, 'chrome')
pathlib.Path(chrome).touch(mode=0o755)
args = argparse.Namespace(
fuzzer_binaries_dir=bin_dir,
fuzzer_corpora_dir='corpus',
fuzzer='blackbox',
target='chrome',
target_args='--foo "--bar=a b"',
testcase_timeout=20,
)
with mock.patch.dict(os.environ, {'DISPLAY': ':99'}):
(details,) = run_all_fuzzers._get_blackbox_target_details(args)
self.assertEqual(details['cmd_runner'].cmd, [chrome, '--foo', '--bar=a b'])
self.assertTrue(details['continuous_profiling'])
if __name__ == '__main__':
unittest.main()