
preserving the resulting profdata files. Being able to run all fuzzers, in separate processes, is crucial for efficiently generating a coverage dashboard & tracking individual fuzzer jobs that fail for whatever reason. This patch is part of a series, and the overall goal of the series is to provide a set of convenience scripts to make it simpler to generate fuzzing coverage reports. Change-Id: I50b1a204ec521014e687a6f6ad908cc22ec3b7cf Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4633629 Reviewed-by: Prakhar Asthana <pasthana@google.com> Commit-Queue: Julia Hansbrough <flowerhack@google.com> Cr-Commit-Position: refs/heads/main@{#1161048}
129 lines
4.9 KiB
Python
129 lines
4.9 KiB
Python
# 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 os
|
|
import pathlib
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
import sys
|
|
|
|
|
|
class RunAllFuzzersTest(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if sys.platform != 'linux':
|
|
return
|
|
gn_args = ('use_clang_coverage=true '
|
|
'dcheck_always_on=true '
|
|
'ffmpeg_branding=\"ChromeOS\" '
|
|
'is_component_build=true '
|
|
'is_debug=false '
|
|
'proprietary_codecs=true '
|
|
'use_reclient=true '
|
|
'use_libfuzzer=true')
|
|
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
|
|
cls.fuzzer_corpora_dir = tempfile.mkdtemp()
|
|
cls.profdata_outdir = tempfile.mkdtemp()
|
|
os.mkdir(os.path.join(cls.fuzzer_corpora_dir, cls.testfuzzer1))
|
|
corporadir1 = os.path.join(cls.fuzzer_corpora_dir, cls.testfuzzer1)
|
|
os.mkdir(os.path.join(cls.fuzzer_corpora_dir, cls.testfuzzer2))
|
|
corporadir2 = os.path.join(cls.fuzzer_corpora_dir, cls.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()
|
|
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):
|
|
if sys.platform != 'linux':
|
|
return
|
|
# 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)
|
|
shutil.rmtree(cls.fuzzer_corpora_dir, ignore_errors=True)
|
|
shutil.rmtree(cls.profdata_outdir, ignore_errors=True)
|
|
|
|
def test_wrong_arguments(self):
|
|
if sys.platform != 'linux':
|
|
return
|
|
cmd = [
|
|
'python3', 'tools/code_coverage/run_all_fuzzers.py',
|
|
'--fuzzer-binaries-dir', self.__class__.fuzzer_binaries_dir,
|
|
'--fuzzer-corpora-dir', self.__class__.fuzzer_corpora_dir
|
|
]
|
|
with self.assertRaises(subprocess.CalledProcessError) as e:
|
|
subprocess.check_call(cmd, cwd=self.__class__.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.__class__.fuzzer_binaries_dir,
|
|
'--profdata-outdir', self.__class__.profdata_outdir
|
|
]
|
|
with self.assertRaises(subprocess.CalledProcessError) as e:
|
|
subprocess.check_call(cmd, cwd=self.__class__.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.__class__.fuzzer_corpora_dir,
|
|
'--profdata-outdir', self.__class__.profdata_outdir
|
|
]
|
|
with self.assertRaises(subprocess.CalledProcessError) as e:
|
|
subprocess.check_call(cmd, cwd=self.__class__.chromium_src_dir)
|
|
assert ("returned non-zero exit status 2" in str(e.exception))
|
|
|
|
def test_all_fuzzers_succeed(self):
|
|
if sys.platform != 'linux':
|
|
return
|
|
cmd = [
|
|
'python3', 'tools/code_coverage/run_all_fuzzers.py',
|
|
'--fuzzer-binaries-dir', self.__class__.fuzzer_binaries_dir,
|
|
'--fuzzer-corpora-dir', self.__class__.fuzzer_corpora_dir,
|
|
'--profdata-outdir', self.__class__.profdata_outdir
|
|
]
|
|
subprocess.check_call(cmd, cwd=self.__class__.chromium_src_dir)
|
|
|
|
expected_profdata = sorted([
|
|
self.__class__.testfuzzer1 + ".profraw",
|
|
self.__class__.testfuzzer2 + ".profraw"
|
|
])
|
|
actual_profdata = sorted(os.listdir(self.__class__.profdata_outdir))
|
|
assert (
|
|
expected_profdata == actual_profdata
|
|
), "Expected " + str(expected_profdata) + " but got " + str(actual_profdata)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|