diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2021-10-31 07:42:56 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2021-10-31 16:24:16 -0400 |
commit | b0c00780b87b89f68d2e4a9fde386e10ffb01103 (patch) | |
tree | bfe6180c391723ed5d06c287ac6b2a49fa1db20d /lab/goals.py | |
parent | e4f0f9ee71a1ade66b51ec53d0061f462e3838cb (diff) | |
download | python-coveragepy-git-b0c00780b87b89f68d2e4a9fde386e10ffb01103.tar.gz |
a proof-of-concept for coverage-goals
Diffstat (limited to 'lab/goals.py')
-rw-r--r-- | lab/goals.py | 91 |
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:])) |