1*060c71edSMatt Johnston#!/usr/bin/env python3 2*060c71edSMatt Johnston# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 3*060c71edSMatt Johnston 4*060c71edSMatt Johnston# usage: fuzz-coverage.py [-h] corpus program srcdir builddir outdir 5*060c71edSMatt Johnston 6*060c71edSMatt Johnston# Runs corpus (directory of testcases) against a program 7*060c71edSMatt Johnston# built with coverage, and produces a html report. 8*060c71edSMatt Johnston 9*060c71edSMatt Johnston# The program should be built with --coverage -fprofile-abs-path 10*060c71edSMatt Johnston# -O0 may make the html report more legible? 11*060c71edSMatt Johnston 12*060c71edSMatt Johnston# Requires lcov and https://github.com/mozilla/grcov 13*060c71edSMatt Johnston 14*060c71edSMatt Johnstonimport argparse 15*060c71edSMatt Johnstonimport subprocess 16*060c71edSMatt Johnstonimport sys 17*060c71edSMatt Johnstonfrom pathlib import Path 18*060c71edSMatt Johnston 19*060c71edSMatt Johnston 20*060c71edSMatt Johnstondef run(args): 21*060c71edSMatt Johnston corpus = Path(args.corpus) 22*060c71edSMatt Johnston outdir = Path(args.outdir) 23*060c71edSMatt Johnston 24*060c71edSMatt Johnston for c in Path(args.builddir).glob("**/*.gcda"): 25*060c71edSMatt Johnston print(f"Removed old coverage {c}", file=sys.stderr) 26*060c71edSMatt Johnston c.unlink() 27*060c71edSMatt Johnston 28*060c71edSMatt Johnston print("Running corpus", file=sys.stderr) 29*060c71edSMatt Johnston for c in corpus.glob("*"): 30*060c71edSMatt Johnston c = c.open("rb").read() 31*060c71edSMatt Johnston subprocess.run([args.program], input=c) 32*060c71edSMatt Johnston 33*060c71edSMatt Johnston print("Running grcov", file=sys.stderr) 34*060c71edSMatt Johnston outdir.mkdir(parents=True, exist_ok=True) 35*060c71edSMatt Johnston coverage_paths = [args.builddir] 36*060c71edSMatt Johnston lcov_file = outdir / "lcov.info" 37*060c71edSMatt Johnston 38*060c71edSMatt Johnston subprocess.run( 39*060c71edSMatt Johnston [ 40*060c71edSMatt Johnston "grcov", 41*060c71edSMatt Johnston "-b", 42*060c71edSMatt Johnston args.program, 43*060c71edSMatt Johnston "-o", 44*060c71edSMatt Johnston lcov_file, 45*060c71edSMatt Johnston "-t", 46*060c71edSMatt Johnston "lcov", 47*060c71edSMatt Johnston "-s", 48*060c71edSMatt Johnston args.srcdir, 49*060c71edSMatt Johnston ] 50*060c71edSMatt Johnston + coverage_paths, 51*060c71edSMatt Johnston check=True, 52*060c71edSMatt Johnston ) 53*060c71edSMatt Johnston 54*060c71edSMatt Johnston print("Running genhtml", file=sys.stderr) 55*060c71edSMatt Johnston subprocess.run( 56*060c71edSMatt Johnston [ 57*060c71edSMatt Johnston "genhtml", 58*060c71edSMatt Johnston "-o", 59*060c71edSMatt Johnston outdir, 60*060c71edSMatt Johnston "--show-details", 61*060c71edSMatt Johnston "--highlight", 62*060c71edSMatt Johnston "--ignore-errors", 63*060c71edSMatt Johnston "source", 64*060c71edSMatt Johnston "--ignore-errors", 65*060c71edSMatt Johnston "unmapped", 66*060c71edSMatt Johnston "--legend", 67*060c71edSMatt Johnston lcov_file, 68*060c71edSMatt Johnston ], 69*060c71edSMatt Johnston check=True, 70*060c71edSMatt Johnston ) 71*060c71edSMatt Johnston 72*060c71edSMatt Johnston html = outdir / "index.html" 73*060c71edSMatt Johnston print(f"\n\nOutput is file://{html.absolute()}", file=sys.stderr) 74*060c71edSMatt Johnston 75*060c71edSMatt Johnston 76*060c71edSMatt Johnstondef main(): 77*060c71edSMatt Johnston parser = argparse.ArgumentParser() 78*060c71edSMatt Johnston parser.add_argument("corpus", type=str, help="Corpus directory") 79*060c71edSMatt Johnston parser.add_argument("program", type=str, help="Target Program") 80*060c71edSMatt Johnston parser.add_argument("srcdir", type=str, help="Source directory") 81*060c71edSMatt Johnston parser.add_argument("builddir", type=str) 82*060c71edSMatt Johnston parser.add_argument("outdir", type=str) 83*060c71edSMatt Johnston args = parser.parse_args() 84*060c71edSMatt Johnston 85*060c71edSMatt Johnston run(args) 86*060c71edSMatt Johnston 87*060c71edSMatt Johnston 88*060c71edSMatt Johnstonif __name__ == "__main__": 89*060c71edSMatt Johnston main() 90