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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
#!/usr/bin/python3
from __future__ import print_function
import argparse
import glob
import os.path
import sys
import ssg.products
import ssg.yaml
from tests.common import stability
IGNORED_PROPERTIES = (
"product_dir",
)
def describe_modification(intro, changeset):
if not changeset:
return ""
msg = intro
for what, (initial, final) in changeset.items():
msg += " - {what} from '{initial}' -> '{final}'\n".format(
what=what, initial=initial, final=final)
return msg
def describe_change(difference, name):
msg = ""
msg += stability.describe_changeset(
"Following properties were added to the {name} product:\n".format(name=name),
difference.added,
)
msg += stability.describe_changeset(
"Following properties were removed from the {name} product:\n".format(name=name),
difference.removed,
)
msg += describe_modification(
"Following properties got different values in the {name} product:\n".format(name=name),
difference.modified,
)
return msg.rstrip()
def compare_dictionaries(reference, sample):
reference_keys = set(reference.keys())
sample_keys = set(sample.keys())
result = stability.Difference()
result.added = list(sample_keys.difference(reference_keys))
result.removed = list(reference_keys.difference(sample_keys))
for key, value in reference.items():
if sample.get(key, value) != value:
result.modified[key] = (value, sample[key])
for item in IGNORED_PROPERTIES:
result.remove_item_from_comparison(item)
return result
def get_references_filenames(ref_root):
return glob.glob(os.path.join(ref_root, "*.yml"))
def corresponding_product_built(build_dir, product_id):
return os.path.isdir(os.path.join(build_dir, product_id))
def get_matching_compiled_product_filename(build_dir, product_id):
ref_path_components = reference_fname.split(os.path.sep)
matching_filename = os.path.join(build_dir, product_id, "product.yml")
if os.path.isfile(matching_filename):
return matching_filename
def get_reference_vs_built_difference(ref_product, built_product):
ref_dict = dict()
ref_dict.update(ref_product)
built_dict = dict()
built_dict.update(built_product)
difference = compare_dictionaries(ref_dict, built_dict)
return difference
def inform_and_append_fix_based_on_reference_compiled_product(ref, build_root):
ref_product = ssg.products.Product(ref)
product_id = ref_product["product"]
if not corresponding_product_built(build_root, product_id):
return True
compiled_path = os.path.join(build_root, product_id, "product.yml")
compiled_product = ssg.products.Product(compiled_path)
difference = get_reference_vs_built_difference(ref_product, compiled_product)
all_ok = difference.empty
return all_ok
def compare_compiled_products_with_reference_data(test_data_root, build_root):
reference_files = get_references_filenames(test_data_root)
if not reference_files:
raise RuntimeError("Unable to find any reference compiled products in {test_root}"
.format(test_root=test_data_root))
all_ok = True
for ref in reference_files:
all_ok &= inform_and_append_fix_based_on_reference_compiled_product(
ref, build_root)
if not all_ok:
products_dir = os.path.join(os.path.dirname(__file__), "..", "products")
msg = (
"If changes to mentioned products are intentional, "
"execute this script with PYTHONPATH set to the project root "
"to regenerate reference products: \n"
"{script_name} --update-reference-data {build_root} {test_data_root}\n"
"If those changes are unwanted, take a look at product properties "
"that likely cause these changes."
.format(script_name=__file__, build_root=os.path.normpath(products_dir),
test_data_root=test_data_root))
print(msg, file=sys.stderr)
return all_ok
def write_product_without_keys(product, path, exclude_keys=frozenset()):
product_data = dict()
product_data.update(product)
for key in exclude_keys:
product_data.pop(key, None)
with open(path, "w") as f:
ssg.yaml.ordered_dump(product_data, f)
def update_reference_data(test_data_root, products_dir):
properties_dir = os.path.join(products_dir, os.path.pardir, "product_properties")
product_yamls = glob.glob(os.path.join(products_dir, "*", "product.yml"))
for fname in product_yamls:
product = ssg.products.Product(fname)
product.read_properties_from_directory(properties_dir)
out_fname = os.path.join(test_data_root, product["product"] + ".yml")
write_product_without_keys(product, out_fname, IGNORED_PROPERTIES)
print("Updated test data of {num_products} products".format(num_products=len(product_yamls)))
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"build_root", help="A directory that contains product-named subdirectories "
"with product.yml files. Typically the build root, or products source root "
"if the script is executed in the update mode")
parser.add_argument("test_data_root", help="Root of reference data containing "
"reference files named <product>.yml")
parser.add_argument(
"--update-reference-data", action="store_true", default=False,
help="If supplied with this option, "
"and the build root is the product root, update all of the test data")
args = parser.parse_args()
if args.update_reference_data:
update_reference_data(args.test_data_root, args.build_root)
sys.exit(0)
all_ok = compare_compiled_products_with_reference_data(args.test_data_root, args.build_root)
return_code = int(not all_ok)
sys.exit(return_code)
if __name__ == "__main__":
main()
|