Add recipe_diff function and helpers.

This takes a pair of Recipe objects and returns a list of diff dicts
that include what was changed between the two recipes.
This commit is contained in:
Brian C. Lane 2017-11-14 16:16:48 -08:00
parent a76e95dcb5
commit 052a8ba991
2 changed files with 123 additions and 0 deletions

View File

@ -718,3 +718,83 @@ def is_parent_diff(repo, filename, tree, parent):
diff_opts.set_pathspec([filename]) diff_opts.set_pathspec([filename])
diff = Git.Diff.new_tree_to_tree(repo, parent.get_tree(), tree, diff_opts) diff = Git.Diff.new_tree_to_tree(repo, parent.get_tree(), tree, diff_opts)
return diff.get_num_deltas() > 0 return diff.get_num_deltas() > 0
def find_name(name, lst):
"""Find the dict matching the name in a list and return it.
:param name: Name to search for
:type name: str
:param lst: List of dict's with "name" field
:returns: First dict with matching name, or None
:rtype: dict or None
"""
for e in lst:
if e["name"] == name:
return e
return None
def diff_items(title, old_items, new_items):
"""Return the differences between two lists of dicts.
:param title: Title of the entry
:type title: str
:param old_items: List of item dicts with "name" field
:type old_items: list(dict)
:param new_items: List of item dicts with "name" field
:type new_items: list(dict)
:returns: List of diff dicts with old/new entries
:rtype: list(dict)
"""
diffs = []
old_names = set(m["name"] for m in old_items)
new_names = set(m["name"] for m in new_items)
added_items = new_names.difference(old_names)
added_items = sorted(added_items, key=lambda n: n.lower())
removed_items = old_names.difference(new_names)
removed_items = sorted(removed_items, key=lambda n: n.lower())
same_items = old_names.intersection(new_names)
same_items = sorted(same_items, key=lambda n: n.lower())
for name in added_items:
diffs.append({"old":None,
"new":{title:find_name(name, new_items)}})
for name in removed_items:
diffs.append({"old":{title:find_name(name, old_items)},
"new":None})
for name in same_items:
old_item = find_name(name, old_items)
new_item = find_name(name, new_items)
if old_item != new_item:
diffs.append({"old":{title:old_item},
"new":{title:new_item}})
return diffs
def recipe_diff(old_recipe, new_recipe):
"""Diff two versions of a recipe
:param old_recipe: The old version of the recipe
:type old_recipe: Recipe
:param new_recipe: The new version of the recipe
:type new_recipe: Recipe
:returns: A list of diff dict entries with old/new
:rtype: list(dict)
"""
diffs = []
# These cannot be added or removed, just different
for element in ["name", "description", "version"]:
if old_recipe[element] != new_recipe[element]:
diffs.append({"old":{element.title():old_recipe[element]},
"new":{element.title():new_recipe[element]}})
diffs.extend(diff_items("Module", old_recipe["modules"], new_recipe["modules"]))
diffs.extend(diff_items("Package", old_recipe["packages"], new_recipe["packages"]))
return diffs

View File

@ -39,6 +39,25 @@ class BasicRecipeTest(unittest.TestCase):
result_dict = eval(f_dict.read()) result_dict = eval(f_dict.read())
self.input_toml.append((f_toml.read(), result_dict)) self.input_toml.append((f_toml.read(), result_dict))
self.old_modules = [recipes.RecipeModule("toml", "2.1"),
recipes.RecipeModule("bash", "4.*"),
recipes.RecipeModule("httpd", "3.7.*")]
self.old_packages = [recipes.RecipePackage("python", "2.7.*"),
recipes.RecipePackage("parted", "3.2")]
self.new_modules = [recipes.RecipeModule("toml", "2.1"),
recipes.RecipeModule("httpd", "3.8.*"),
recipes.RecipeModule("openssh", "2.8.1")]
self.new_packages = [recipes.RecipePackage("python", "2.7.*"),
recipes.RecipePackage("parted", "3.2"),
recipes.RecipePackage("git", "2.13.*")]
self.modules_result = [{"new": {"Modules": {"version": "2.8.1", "name": "openssh"}},
"old": None},
{"new": None,
"old": {"Modules": {"name": "bash", "version": "4.*"}}},
{"new": {"Modules": {"version": "3.8.*", "name": "httpd"}},
"old": {"Modules": {"version": "3.7.*", "name": "httpd"}}}]
self.packages_result = [{"new": {"Packages": {"name": "git", "version": "2.13.*"}}, "old": None}]
@classmethod @classmethod
def tearDownClass(self): def tearDownClass(self):
pass pass
@ -97,6 +116,30 @@ class BasicRecipeTest(unittest.TestCase):
new_version = recipe.bump_version("0.0.1") new_version = recipe.bump_version("0.0.1")
self.assertEqual(new_version, "0.1.1") self.assertEqual(new_version, "0.1.1")
def find_name_test(self):
"""Test the find_name function"""
test_list = [{"name":"dog"}, {"name":"cat"}, {"name":"squirrel"}]
self.assertEqual(recipes.find_name("dog", test_list), {"name":"dog"})
self.assertEqual(recipes.find_name("cat", test_list), {"name":"cat"})
self.assertEqual(recipes.find_name("squirrel", test_list), {"name":"squirrel"})
def diff_items_test(self):
"""Test the diff_items function"""
self.assertEqual(recipes.diff_items("Modules", self.old_modules, self.new_modules), self.modules_result)
self.assertEqual(recipes.diff_items("Packages", self.old_packages, self.new_packages), self.packages_result)
def recipe_diff_test(self):
"""Test the recipe_diff function"""
old_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.1.1", self.old_modules, self.old_packages)
new_recipe = recipes.Recipe("test-recipe", "A recipe used for testing", "0.3.1", self.new_modules, self.new_packages)
result = [{'new': {'Version': '0.3.1'}, 'old': {'Version': '0.1.1'}},
{'new': {'Module': {'name': 'openssh', 'version': '2.8.1'}}, 'old': None},
{'new': None, 'old': {'Module': {'name': 'bash', 'version': '4.*'}}},
{'new': {'Module': {'name': 'httpd', 'version': '3.8.*'}},
'old': {'Module': {'name': 'httpd', 'version': '3.7.*'}}},
{'new': {'Package': {'name': 'git', 'version': '2.13.*'}}, 'old': None}]
self.assertEqual(recipes.recipe_diff(old_recipe, new_recipe), result)
class GitRecipesTest(unittest.TestCase): class GitRecipesTest(unittest.TestCase):
@classmethod @classmethod