Passed
Pull Request — master (#5209)
by Matěj
02:19
created

test_profile_stability.Difference.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
from __future__ import print_function
2
3
import argparse
4
import fnmatch
5
import os.path
6
import sys
7
8
import ssg.yaml
9
10
11
class Difference(object):
12
    def __init__(self):
13
        self.added = []
14
        self.removed = []
15
16
    @property
17
    def empty(self):
18
        return not (self.added or self.removed)
19
20
21
def describe_changeset(intro, changeset):
22
    if not changeset:
23
        return ""
24
25
    msg = intro
26
    for rid in changeset:
27
        msg += " - {rid}\n".format(rid=rid)
28
    return msg
29
30
31
def describe_change(difference, name):
32
    msg = ""
33
34
    msg += describe_changeset(
35
        "Following selections were added to the {name} profile:\n".format(name=name),
36
        difference.added,
37
    )
38
    msg += describe_changeset(
39
        "Following selections were removed from the {name} profile:\n".format(name=name),
40
        difference.removed,
41
    )
42
    return msg.rstrip()
43
44
45
def compare_sets(reference, sample):
46
    reference = set(reference)
47
    sample = set(sample)
48
49
    result = Difference()
50
    result.added = list(sample.difference(reference))
51
    result.removed = list(reference.difference(sample))
52
    return result
53
54
55
def report_comparison(name, result):
56
    msg = ""
57
    if not result.empty:
58
        msg = describe_change(result, name)
59
    print(msg, file=sys.stderr)
60
61
62
def get_references(ref_root):
63
    found = []
64
    for root, dirs, files in os.walk(ref_root):
65
        for basename in files:
66
            if fnmatch.fnmatch(basename, "*.profile"):
67
                filename = os.path.join(root, basename)
68
                found.append(filename)
69
    return found
70
71
72
def corresponding_product_built(build_dir, reference_fname):
73
    ref_path_components = reference_fname.split(os.path.sep)
74
    product_id = ref_path_components[-2]
75
    return os.path.isdir(os.path.join(build_dir, product_id))
76
77
78
def get_matching_compiled_profile_filename(build_dir, reference_fname):
79
    ref_path_components = reference_fname.split(os.path.sep)
80
    product_id = ref_path_components[-2]
81
    profile_fname = ref_path_components[-1]
82
    matching_filename = os.path.join(build_dir, product_id, "profiles", profile_fname)
83
    if os.path.isfile(matching_filename):
84
        return matching_filename
85
86
87
def get_selections_key_from_yaml(yaml_fname):
88
    return ssg.yaml.open_raw(yaml_fname)["selections"]
89
90
91
def get_profile_name_from_reference_filename(fname):
92
    path_components = fname.split(os.path.sep)
93
    product_id = path_components[-2]
94
    profile_id = os.path.splitext(path_components[-1])[0]
95
    name = "{product_id}'s {profile_id}".format(
96
        product_id=product_id, profile_id=profile_id)
97
    return name
98
99
100
def get_reference_vs_built_difference(reference_fname, built_fname):
101
    ref_selections = get_selections_key_from_yaml(reference_fname)
102
    built_selections = get_selections_key_from_yaml(built_fname)
103
    difference = compare_sets(ref_selections, built_selections)
104
    return difference
105
106
107
def main():
108
    parser = argparse.ArgumentParser()
109
    parser.add_argument("build_root")
110
    parser.add_argument("test_data_root")
111
    args = parser.parse_args()
112
113
    reference_files = get_references(args.test_data_root)
114
    if not reference_files:
115
        raise RuntimeError("Unable to find any reference profiles in {test_root}"
116
                           .format(test_root=args.test_data_root))
117
    fix_commands = []
118
    for ref in reference_files:
119
        if not corresponding_product_built(args.build_root, ref):
120
            continue
121
122
        compiled_profile = get_matching_compiled_profile_filename(args.build_root, ref)
123
        if not compiled_profile:
124
            msg = ("Unexpectedly unable to find compiled profile corresponding"
125
                   "to the test file {ref}, although the corresponding product has been built. "
126
                   "This indicates that a profile we have tests for is missing."
127
                   .format(ref=ref))
128
            raise RuntimeError(msg)
129
        difference = get_reference_vs_built_difference(ref, compiled_profile)
130
        if not difference.empty:
131
            comprehensive_profile_name = get_profile_name_from_reference_filename(ref)
132
            report_comparison(comprehensive_profile_name, difference)
133
            fix_commands.append(
134
                "cp '{compiled}' '{reference}'"
135
                .format(compiled=compiled_profile, reference=ref)
136
            )
137
138
    if fix_commands:
139
        msg = (
140
            "If changes to mentioned profiles are intentional, "
141
            "copy those compiled files, so they become the new reference:\n{fixes}\n"
142
            "Please remember that if you change a profile that is extended by other profiles, "
143
            "changes propagate to derived profiles. "
144
            "If those changes are unwanted, you have to supress them "
145
            "using explicit selections or !unselections in derived profiles."
146
            .format(test_root=args.test_data_root, fixes="\n".join(fix_commands)))
147
        print(msg, file=sys.stderr)
148
    sys.exit(bool(fix_commands))
149
150
151
if __name__ == "__main__":
152
    main()
153