fuzz: Add fuzzing for core and i2c

Add fuzzing infrastructure and a fuzz target handling mctp-i2c packets.
After running for a few days with honggfuzz this achieves close to
complete line coverage of core.c

Change-Id: I6cd7361e6f600831a319f06fb7c7c0d2186fd7de
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/tests/fuzz/fuzz-coverage.py b/tests/fuzz/fuzz-coverage.py
new file mode 100755
index 0000000..9df4a5e
--- /dev/null
+++ b/tests/fuzz/fuzz-coverage.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+# usage: fuzz-coverage.py [-h] corpus program srcdir builddir outdir
+
+# Runs corpus (directory of testcases) against a program
+# built with coverage, and produces a html report.
+
+# The program should be built with --coverage -fprofile-abs-path
+# -O0 may make the html report more legible?
+
+# Requires lcov and https://github.com/mozilla/grcov
+
+import argparse
+import subprocess
+import sys
+from pathlib import Path
+
+
+def run(args):
+    corpus = Path(args.corpus)
+    outdir = Path(args.outdir)
+
+    for c in Path(args.builddir).glob("**/*.gcda"):
+        print(f"Removed old coverage {c}", file=sys.stderr)
+        c.unlink()
+
+    print("Running corpus", file=sys.stderr)
+    for c in corpus.glob("*"):
+        c = c.open("rb").read()
+        subprocess.run([args.program], input=c)
+
+    print("Running grcov", file=sys.stderr)
+    outdir.mkdir(parents=True, exist_ok=True)
+    coverage_paths = [args.builddir]
+    lcov_file = outdir / "lcov.info"
+
+    subprocess.run(
+        [
+            "grcov",
+            "-b",
+            args.program,
+            "-o",
+            lcov_file,
+            "-t",
+            "lcov",
+            "-s",
+            args.srcdir,
+        ]
+        + coverage_paths,
+        check=True,
+    )
+
+    print("Running genhtml", file=sys.stderr)
+    subprocess.run(
+        [
+            "genhtml",
+            "-o",
+            outdir,
+            "--show-details",
+            "--highlight",
+            "--ignore-errors",
+            "source",
+            "--ignore-errors",
+            "unmapped",
+            "--legend",
+            lcov_file,
+        ],
+        check=True,
+    )
+
+    html = outdir / "index.html"
+    print(f"\n\nOutput is file://{html.absolute()}", file=sys.stderr)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("corpus", type=str, help="Corpus directory")
+    parser.add_argument("program", type=str, help="Target Program")
+    parser.add_argument("srcdir", type=str, help="Source directory")
+    parser.add_argument("builddir", type=str)
+    parser.add_argument("outdir", type=str)
+    args = parser.parse_args()
+
+    run(args)
+
+
+if __name__ == "__main__":
+    main()