summaryrefslogtreecommitdiff
path: root/lab/goals.py
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-10-31 07:42:56 -0400
committerNed Batchelder <ned@nedbatchelder.com>2021-10-31 16:24:16 -0400
commitb0c00780b87b89f68d2e4a9fde386e10ffb01103 (patch)
treebfe6180c391723ed5d06c287ac6b2a49fa1db20d /lab/goals.py
parente4f0f9ee71a1ade66b51ec53d0061f462e3838cb (diff)
downloadpython-coveragepy-git-b0c00780b87b89f68d2e4a9fde386e10ffb01103.tar.gz
a proof-of-concept for coverage-goals
Diffstat (limited to 'lab/goals.py')
-rw-r--r--lab/goals.py91
1 files changed, 91 insertions, 0 deletions
diff --git a/lab/goals.py b/lab/goals.py
new file mode 100644
index 00000000..2c98f043
--- /dev/null
+++ b/lab/goals.py
@@ -0,0 +1,91 @@
+"""\
+Check coverage goals.
+
+Use `coverage json` to get a coverage.json file, then run this tool
+to check goals for subsets of files.
+
+Patterns can use '**/foo*.py' to find files anywhere in the project,
+and '!**/something.py' to exclude files matching a pattern.
+
+--file will check each file individually for the required coverage.
+--group checks the entire group collectively.
+
+"""
+
+import argparse
+import json
+import sys
+
+from wcmatch import fnmatch as wcfnmatch # python -m pip install wcmatch
+
+from coverage.results import Numbers # Note: an internal class!
+
+
+def get_data():
+ with open("coverage.json") as j:
+ return json.load(j)
+
+def select_files(files, pat):
+ flags = wcfnmatch.NEGATE | wcfnmatch.NEGATEALL
+ selected = [f for f in files if wcfnmatch.fnmatch(f, pat, flags=flags)]
+ return selected
+
+def total_for_files(data, files):
+ total = Numbers(precision=3)
+ for f in files:
+ sel_summ = data["files"][f]["summary"]
+ total += Numbers(
+ n_statements=sel_summ["num_statements"],
+ n_excluded=sel_summ["excluded_lines"],
+ n_missing=sel_summ["missing_lines"],
+ n_branches=sel_summ["num_branches"],
+ n_partial_branches=sel_summ["num_partial_branches"],
+ n_missing_branches=sel_summ["missing_branches"],
+ )
+
+ return total
+
+def main(argv):
+ parser = argparse.ArgumentParser(__doc__)
+ parser.add_argument("--file", "-f", action="store_true", help="Check each file individually")
+ parser.add_argument("--group", "-g", action="store_true", help="Check a group of files")
+ parser.add_argument("--verbose", "-v", action="store_true", help="Be chatty about what's happening")
+ parser.add_argument("goal", type=float, help="Coverage goal")
+ parser.add_argument("pattern", type=str, nargs="+", help="Patterns to check")
+ args = parser.parse_args(argv)
+
+ if args.file and args.group:
+ print("Can't use --file and --group together")
+ return 1
+ if not (args.file or args.group):
+ print("Need either --file or --group")
+ return 1
+
+ data = get_data()
+ all_files = list(data["files"].keys())
+ selected = select_files(all_files, args.pattern)
+
+ ok = True
+ if args.group:
+ total = total_for_files(data, selected)
+ pat_nice = ",".join(args.pattern)
+ result = f"Coverage for {pat_nice} is {total.pc_covered_str}"
+ if total.pc_covered < args.goal:
+ print(f"{result}, below {args.goal}")
+ ok = False
+ elif args.verbose:
+ print(result)
+ else:
+ for fname in selected:
+ total = total_for_files(data, [fname])
+ result = f"Coverage for {fname} is {total.pc_covered_str}"
+ if total.pc_covered < args.goal:
+ print(f"{result}, below {args.goal}")
+ ok = False
+ elif args.verbose:
+ print(result)
+
+ return 0 if ok else 2
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))