From f96eb4e3f4e43ca2984c85662942cda42b954a53 Mon Sep 17 00:00:00 2001 From: anxdpanic Date: Thu, 14 Feb 2019 13:01:26 -0500 Subject: [PATCH] add check for circular dependencies --- README.md | 2 + kodi_addon_checker/addons/Repository.py | 14 ++++-- kodi_addon_checker/check_addon.py | 2 + kodi_addon_checker/check_dependencies.py | 52 ++++++++++++++++++++++ tests/test_check_dependencies.py | 39 ++++++++++++++++ tests/test_data/Circular_depend/addon.xml | 23 ++++++++++ tests/test_data/Circular_depend/addons.xml | 25 +++++++++++ 7 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 tests/test_check_dependencies.py create mode 100644 tests/test_data/Circular_depend/addon.xml create mode 100644 tests/test_data/Circular_depend/addons.xml diff --git a/README.md b/README.md index fb9fdb8c..2bfbbd65 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ It can also be used locally for detecting problems in your addons. - Check for valid forum, source and website value in addon.xml +- Check for circular dependencies + All of the validation and checks are done according to the kodi [addon rules](https://kodi.wiki/view/Add-on_rules) ## Installation diff --git a/kodi_addon_checker/addons/Repository.py b/kodi_addon_checker/addons/Repository.py index 766d2e9d..e1c20b4c 100644 --- a/kodi_addon_checker/addons/Repository.py +++ b/kodi_addon_checker/addons/Repository.py @@ -15,13 +15,19 @@ class Repository(object): - def __init__(self, version, path): + def __init__(self, version, path, local_xml=False): super(Repository, self).__init__() self.version = version self.path = path - gz_file = requests.get(path, timeout=(10, 10)).content - with gzip.open(BytesIO(gz_file), 'rb') as xml_file: - content = xml_file.read() + + if local_xml: + with open(path, 'rb') as xml_file: + content = xml_file.read() + else: + gz_file = requests.get(path, timeout=(10, 10)).content + with gzip.open(BytesIO(gz_file), 'rb') as xml_file: + content = xml_file.read() + tree = ET.fromstring(content) self.addons = [] for addon in tree.findall("addon"): diff --git a/kodi_addon_checker/check_addon.py b/kodi_addon_checker/check_addon.py index 42d1e112..755c6b0e 100644 --- a/kodi_addon_checker/check_addon.py +++ b/kodi_addon_checker/check_addon.py @@ -58,6 +58,8 @@ def start(addon_path, args, all_repo_addons, config=None): check_dependencies.check_reverse_dependencies(addon_report, addon_id, args.branch, all_repo_addons) + check_dependencies.check_circular_dependencies(addon_report, all_repo_addons, parsed_xml, args.branch) + check_files.check_file_permission(addon_report, file_index) check_files.check_for_invalid_xml_files(addon_report, file_index) diff --git a/kodi_addon_checker/check_dependencies.py b/kodi_addon_checker/check_dependencies.py index 50ba340b..8eefb2f4 100644 --- a/kodi_addon_checker/check_dependencies.py +++ b/kodi_addon_checker/check_dependencies.py @@ -132,6 +132,58 @@ def check_reverse_dependencies(report: Report, addon: str, branch_name: str, all .format(", ".join(sorted([r.id for r in rdependsLowerBranch])), len(rdependsLowerBranch)))) +def check_circular_dependencies(report: Report, all_repo_addons: dict, parsed_xml, branch_name: str): + """Check for any circular dependencies in addon.xml file and reports them + :all_repo_addons: dictionary return by all_repo_addon() function + :parsed_xml: parsed addon.xml file + :branch_name: name of the kodi branch/version + """ + root_addon = Addon(parsed_xml) + ignore_list = _get_ignore_list(branch_name) + + dependencies = [] + dependency_tree = {} + + # add dependencies of `addon` to dependencies list + def add_dependencies(addon): + new_depends = [] + for dependency in addon.dependencies: + # don't check ignored or "xbmc.python" for dependencies + if dependency.id in ignore_list or dependency.id in VERSION_ATTRB: + continue + new_depends.append(dependency) + + if addon.id not in dependency_tree: + dependency_tree[addon.id] = [] + if new_depends: + dependency_tree[addon.id] = [d.id for d in new_depends] + dependencies.extend(new_depends) + + add_dependencies(root_addon) + + i = 0 + # add all dependencies and their dependencies + while i < len(dependencies): + if dependencies[i].id not in dependency_tree: + for branch, repo in sorted(all_repo_addons.items()): + found_addon = repo.find(dependencies[i].id) + if found_addon: + add_dependencies(found_addon) + i += 1 + + if dependency_tree: + circular_dependencies = [] + dependencies = list(dependency_tree.keys()) + + for depend in dependencies: + if any(root_addon.id == dep_id for dep_id in dependency_tree.get(depend, [])): + circular_dependencies.append(depend) + + if circular_dependencies: + report.add(Record(PROBLEM, "Circular dependencies: {}" + .format(", ".join(sorted(set(circular_dependencies)))))) + + def _get_ignore_list(branch_name): if branch_name == "leia": diff --git a/tests/test_check_dependencies.py b/tests/test_check_dependencies.py new file mode 100644 index 00000000..45a78a00 --- /dev/null +++ b/tests/test_check_dependencies.py @@ -0,0 +1,39 @@ +import unittest + +from os.path import abspath, dirname, join +import xml.etree.ElementTree as ET + +from kodi_addon_checker.addons.Repository import Repository +from kodi_addon_checker.check_dependencies import check_circular_dependencies +from kodi_addon_checker.common import load_plugins +from kodi_addon_checker.record import Record +from kodi_addon_checker.reporter import ReportManager +from kodi_addon_checker.report import Report + + +HERE = abspath(dirname(__file__)) + + +class TestCheckDependencies(unittest.TestCase): + + def setUp(self): + load_plugins() + ReportManager.enable(["array"]) + self.report = Report("") + self.branch = 'krypton' + + def test_check_circular_dependency(self): + self.path = join(HERE, 'test_data', 'Circular_depend') + addon_xml = join(self.path, "addon.xml") + addons_xml = join(self.path, "addons.xml") + + parsed_xml = ET.parse(addon_xml).getroot() + all_repo_addons = {self.branch: Repository(self.branch, addons_xml, local_xml=True)} + + check_circular_dependencies(self.report, all_repo_addons, parsed_xml, self.branch) + + records = [Record.__str__(r) for r in ReportManager.getEnabledReporters()[0].reports] + self.output = [record for record in records if record.startswith("ERROR: Circular")] + self.expected = ["ERROR: Circular dependencies: plugin.test.one"] + + self.assertListEqual(self.expected, self.output) diff --git a/tests/test_data/Circular_depend/addon.xml b/tests/test_data/Circular_depend/addon.xml new file mode 100644 index 00000000..921380cb --- /dev/null +++ b/tests/test_data/Circular_depend/addon.xml @@ -0,0 +1,23 @@ + + + + + + + + video + + + Testing + Testing + en + This is just a test + all + GPL-3.0-only + + icon.png + fanart.jpg + + Testing 123 + + diff --git a/tests/test_data/Circular_depend/addons.xml b/tests/test_data/Circular_depend/addons.xml new file mode 100644 index 00000000..bfb3ce0a --- /dev/null +++ b/tests/test_data/Circular_depend/addons.xml @@ -0,0 +1,25 @@ + + + + + + + + + video + + + Testing + Testing + en + This is just a test + all + GPL-3.0-only + + icon.png + fanart.jpg + + Testing 123 + + +