summaryrefslogtreecommitdiff
path: root/lab/goals.py
blob: 4bda0f0f52a776f2e2af06c0ab923498bc6dcf17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt

"""\
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 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.get("num_branches", 0),
            n_partial_branches=sel_summ.get("num_partial_branches", 0),
            n_missing_branches=sel_summ.get("missing_branches", 0),
        )

    return total

def main(argv):
    parser = argparse.ArgumentParser(description=__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)

    print("** Note: this is a proof-of-concept. Support is not promised. **")
    print("Read more: https://nedbatchelder.com/blog/202111/coverage_goals.html")
    print("Feedback is appreciated: https://github.com/nedbat/coveragepy/issues/691")

    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

    with open("coverage.json") as j:
        data = json.load(j)
    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:]))