qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: "Alex Bennée" <alex.bennee@linaro.org>
To: qemu-devel@nongnu.org
Cc: "Thomas Huth" <thuth@redhat.com>,
	"Alex Bennée" <alex.bennee@linaro.org>,
	"Warner Losh" <imp@bsdimp.com>, "Ryo ONODERA" <ryoon@netbsd.org>,
	"Kevin Wolf" <kwolf@redhat.com>,
	"Beraldo Leal" <bleal@redhat.com>,
	"Wainer dos Santos Moschetta" <wainersm@redhat.com>,
	"Hanna Reitz" <hreitz@redhat.com>,
	qemu-block@nongnu.org,
	"Philippe Mathieu-Daudé" <philmd@linaro.org>,
	"Kyle Evans" <kevans@freebsd.org>,
	"Reinoud Zandijk" <reinoud@netbsd.org>,
	"Kautuk Consul" <kconsul@linux.vnet.ibm.com>
Subject: [PATCH 01/11] scripts/coverage: initial coverage comparison script
Date: Thu, 30 Mar 2023 11:11:31 +0100	[thread overview]
Message-ID: <20230330101141.30199-2-alex.bennee@linaro.org> (raw)
In-Reply-To: <20230330101141.30199-1-alex.bennee@linaro.org>

This is a very rough and ready first pass at comparing gcovr's json
output between two different runs. At the moment it will give you a
file level diff between two runs but hopefully it wont be too hard to
extend to give better insight.

After generating the coverage results you run with something like:

  ./scripts/coverage/compare_gcov_json.py \
    -a ./builds/gcov.config1/coverage.json \
    -b ./builds/gcov.config2/coverage.json

My hope is we can use this to remove some redundancy from testing as
well as evaluate if new tests are actually providing additional
coverage or just burning our precious CI time.

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Cc: Kautuk Consul <kconsul@linux.vnet.ibm.com>

---
v2
  - add MAINTAINERS entry
---
 MAINTAINERS                           |   5 ++
 scripts/coverage/compare_gcov_json.py | 118 ++++++++++++++++++++++++++
 2 files changed, 123 insertions(+)
 create mode 100755 scripts/coverage/compare_gcov_json.py

diff --git a/MAINTAINERS b/MAINTAINERS
index ef45b5e71e..9e1a60ea24 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3908,3 +3908,8 @@ Performance Tools and Tests
 M: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
 S: Maintained
 F: scripts/performance/
+
+Code Coverage Tools
+M: Alex Bennée <alex.bennee@linaro.org>
+S: Odd Fixes
+F: scripts/coverage/
diff --git a/scripts/coverage/compare_gcov_json.py b/scripts/coverage/compare_gcov_json.py
new file mode 100755
index 0000000000..451e2f8c00
--- /dev/null
+++ b/scripts/coverage/compare_gcov_json.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+#
+# Compare output of two gcovr JSON reports and report differences. To
+# generate the required output first:
+#   - create two build dirs with --enable-gcov
+#   - run set of tests in each
+#   - run make coverage-html in each
+#   - run gcovr --json --exclude-unreachable-branches --print-summary -o coverage.json --root ../../ . *.p
+#
+# Author: Alex Bennée <alex.bennee@linaro.org>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+def create_parser():
+    parser = argparse.ArgumentParser(
+        prog='compare_gcov_json',
+        description='analyse the differences in coverage between two runs')
+
+    parser.add_argument('-a', type=Path, default=None,
+                        help=('First file to check'))
+
+    parser.add_argument('-b', type=Path, default=None,
+                        help=('Second file to check'))
+
+    parser.add_argument('--verbose', action='store_true', default=False,
+                        help=('A minimal verbosity level that prints the '
+                              'overall result of the check/wait'))
+    return parser
+
+
+# See https://gcovr.com/en/stable/output/json.html#json-format-reference
+def load_json(json_file_path: Path, verbose = False) -> dict[str, set[int]]:
+
+    with open(json_file_path) as f:
+        data = json.load(f)
+
+    root_dir = json_file_path.absolute().parent
+    covered_lines = dict()
+
+    for filecov in data["files"]:
+        file_path = Path(filecov["file"])
+
+        # account for generated files - map into src tree
+        resolved_path = Path(file_path).absolute()
+        if resolved_path.is_relative_to(root_dir):
+            file_path = resolved_path.relative_to(root_dir)
+            # print(f"remapped {resolved_path} to {file_path}")
+
+        lines = filecov["lines"]
+
+        executed_lines = set(
+            linecov["line_number"]
+            for linecov in filecov["lines"]
+            if linecov["count"] != 0 and not linecov["gcovr/noncode"]
+        )
+
+        # if this file has any coverage add it to the system
+        if len(executed_lines) > 0:
+            if verbose:
+                print(f"file {file_path} {len(executed_lines)}/{len(lines)}")
+            covered_lines[str(file_path)] = executed_lines
+
+    return covered_lines
+
+def find_missing_files(first, second):
+    """
+    Return a list of files not covered in the second set
+    """
+    missing_files = []
+    for f in sorted(first):
+        file_a = first[f]
+        try:
+            file_b = second[f]
+        except KeyError:
+            missing_files.append(f)
+
+    return missing_files
+
+def main():
+    """
+    Script entry point
+    """
+    parser = create_parser()
+    args = parser.parse_args()
+
+    if not args.a or not args.b:
+        print("We need two files to compare")
+        sys.exit(1)
+
+    first_coverage = load_json(args.a, args.verbose)
+    second_coverage = load_json(args.b, args.verbose)
+
+    first_missing = find_missing_files(first_coverage,
+                                       second_coverage)
+
+    second_missing = find_missing_files(second_coverage,
+                                        first_coverage)
+
+    a_name = args.a.parent.name
+    b_name = args.b.parent.name
+
+    print(f"{b_name} missing coverage in {len(first_missing)} files")
+    for f in first_missing:
+        print(f"  {f}")
+
+    print(f"{a_name} missing coverage in {len(second_missing)} files")
+    for f in second_missing:
+        print(f"  {f}")
+
+
+if __name__ == '__main__':
+    main()
-- 
2.39.2



  reply	other threads:[~2023-03-30 10:13 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-30 10:11 [PATCH 00/11] more misc fixes for 8.0 (tests, gdbstub, meta, docs) Alex Bennée
2023-03-30 10:11 ` Alex Bennée [this message]
2023-03-30 12:37   ` [PATCH 01/11] scripts/coverage: initial coverage comparison script Thomas Huth
2023-03-30 10:11 ` [PATCH 02/11] gdbstub: Only build libgdb_user.fa / libgdb_softmmu.fa if necessary Alex Bennée
2023-03-30 10:11 ` [PATCH 03/11] MAINTAINERS: add a section for policy documents Alex Bennée
2023-03-30 11:24   ` Thomas Huth
2023-03-30 15:31   ` Markus Armbruster
2023-03-30 15:34   ` Warner Losh
2023-03-30 16:29   ` Kashyap Chamarthy
2023-04-03  7:56   ` Philippe Mathieu-Daudé
2023-03-30 10:11 ` [PATCH 04/11] qemu-options: finesse the recommendations around -blockdev Alex Bennée
2023-03-30 11:24   ` Thomas Huth
2023-04-01  8:00   ` Michael Tokarev
2023-04-03  6:22   ` Markus Armbruster
2023-04-03 13:16     ` Alex Bennée
2023-04-03 14:55       ` Markus Armbruster
2023-04-03 16:31         ` Thomas Huth
2023-04-03 18:17           ` Markus Armbruster
2023-03-30 10:11 ` [PATCH 05/11] metadata: add .git-blame-ignore-revs Alex Bennée
2023-03-30 11:25   ` Thomas Huth
2023-03-30 10:11 ` [PATCH 06/11] Use hexagon toolchain version 16.0.0 Alex Bennée
2023-03-30 10:11 ` [PATCH 07/11] tests/qemu-iotests: explicitly invoke 'check' via 'python' Alex Bennée
2023-03-30 11:27   ` Thomas Huth
2023-03-30 10:11 ` [PATCH 08/11] tests/vm: use the default system python for NetBSD Alex Bennée
2023-03-30 11:27   ` Thomas Huth
2023-03-30 10:11 ` [PATCH 09/11] tests/requirements.txt: bump up avocado-framework version to 101.0 Alex Bennée
2023-03-30 11:43   ` Thomas Huth
2023-03-30 12:12     ` Alex Bennée
2023-03-30 12:21       ` Thomas Huth
2023-03-31  7:50         ` Thomas Huth
2023-03-30 10:11 ` [PATCH 10/11] gitlab: fix typo Alex Bennée
2023-03-30 10:39   ` Philippe Mathieu-Daudé
2023-03-30 11:35   ` Thomas Huth
2023-03-30 10:11 ` [PATCH 11/11] tests/gitlab: use kaniko to build images Alex Bennée
2023-03-30 10:17   ` Daniel P. Berrangé
2023-03-30 10:49     ` Daniel P. Berrangé
2023-03-30 18:14       ` Alex Bennée
2023-03-30 12:35   ` Thomas Huth

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230330101141.30199-2-alex.bennee@linaro.org \
    --to=alex.bennee@linaro.org \
    --cc=bleal@redhat.com \
    --cc=hreitz@redhat.com \
    --cc=imp@bsdimp.com \
    --cc=kconsul@linux.vnet.ibm.com \
    --cc=kevans@freebsd.org \
    --cc=kwolf@redhat.com \
    --cc=philmd@linaro.org \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=reinoud@netbsd.org \
    --cc=ryoon@netbsd.org \
    --cc=thuth@redhat.com \
    --cc=wainersm@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).