From 1c3223d48d100595e09de345eb5c4367b3886735 Mon Sep 17 00:00:00 2001 From: anxdpanic Date: Sun, 20 Sep 2020 12:18:50 +0200 Subject: [PATCH] add check for circular dependencies --- README.md | 2 + kodi_addon_checker/addons/Repository.py | 27 ++++++--- kodi_addon_checker/check_addon.py | 2 + kodi_addon_checker/check_dependencies.py | 64 ++++++++++++++++++++++ tests/test_check_dependencies.py | 61 +++++++++++++++++++++ tests/test_data/Circular_depend/addon.xml | 23 ++++++++ tests/test_data/Circular_depend/addons.xml | 25 +++++++++ 7 files changed, 196 insertions(+), 8 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 31620ed7..6bbe8a89 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ It can also be used locally for detecting problems in your addons. - Check if all PO files are valid +- 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 c61319c0..f839f102 100644 --- a/kodi_addon_checker/addons/Repository.py +++ b/kodi_addon_checker/addons/Repository.py @@ -17,20 +17,31 @@ class Repository(): def __init__(self, version, path): + """Get information of all the addons + :version: Kodi version name for the repository + :path: path to the Kodi repository file + """ super().__init__() self.version = version self.path = path - # Recover from unreliable mirrors - session = requests.Session() - adapter = requests.adapters.HTTPAdapter(max_retries=5) - session.mount('http://', adapter) - session.mount('https://', adapter) + content = '' - content = session.get(path, timeout=(30, 30)).content + if path.startswith('http'): + # Recover from unreliable mirrors + session = requests.Session() + adapter = requests.adapters.HTTPAdapter(max_retries=5) + session.mount('http://', adapter) + session.mount('https://', adapter) - if path.endswith('.gz'): - with gzip.open(BytesIO(content), 'rb') as xml_file: + content = session.get(path, timeout=(30, 30)).content + + if path.endswith('.gz'): + with gzip.open(BytesIO(content), 'rb') as xml_file: + content = xml_file.read() + + if not content and path.endswith('.xml'): + with open(path, 'rb') as xml_file: content = xml_file.read() tree = ET.fromstring(content) diff --git a/kodi_addon_checker/check_addon.py b/kodi_addon_checker/check_addon.py index 339cfdd2..9809d92c 100644 --- a/kodi_addon_checker/check_addon.py +++ b/kodi_addon_checker/check_addon.py @@ -63,6 +63,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 247f370d..d9574569 100644 --- a/kodi_addon_checker/check_dependencies.py +++ b/kodi_addon_checker/check_dependencies.py @@ -200,6 +200,70 @@ 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 + :report: current report for adding result records + :all_repo_addons: dictionary return by all_repo_addon() function + :parsed_xml: parsed addon.xml file + :branch_name: name of the kodi branch/version + """ + addon = Addon(parsed_xml) + + dependency_tree = create_dependency_tree(addon, all_repo_addons, branch_name) + + if dependency_tree: + circular_dependencies = [] + + for depend in list(dependency_tree.keys()): + if addon.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 create_dependency_tree(addon: Addon, all_repo_addons: dict, branch_name: str): + """Create a dict of dependencies and their dependencies based on addon. + :addon: root addon for dependency tree, class kodi_addon_checker.addons.Addon.Addon + :all_repo_addons: dictionary return by all_repo_addon() function + :branch_name: name of the kodi branch/version + """ + ignore_list = _get_ignore_list(KodiVersion(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 for dependencies + if dependency.id in ignore_list: + 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(addon) + + i = 0 + # add all dependencies and their dependencies + while i < len(dependencies): + if dependencies[i].id not in dependency_tree: + for _, repo in sorted(all_repo_addons.items()): + found_addon = repo.find(dependencies[i].id) + if found_addon: + add_dependencies(found_addon) + i += 1 + + return dependency_tree + + def _get_ignore_list(kodi_version: KodiVersion): """Generate an dependency ignore list based on the branch name diff --git a/tests/test_check_dependencies.py b/tests/test_check_dependencies.py new file mode 100644 index 00000000..d3e1cd13 --- /dev/null +++ b/tests/test_check_dependencies.py @@ -0,0 +1,61 @@ +import unittest + +from os.path import abspath, dirname, join +import xml.etree.ElementTree as ET + +from kodi_addon_checker.addons.Addon import Addon +from kodi_addon_checker.addons.Repository import Repository +from kodi_addon_checker.check_dependencies import check_circular_dependencies +from kodi_addon_checker.check_dependencies import create_dependency_tree +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): + """Test dependency checks + """ + + def setUp(self): + """Test setup + """ + load_plugins() + ReportManager.enable(["array"]) + self.report = Report("") + self.branch = 'krypton' + self.path = join(HERE, 'test_data', 'Circular_depend') + + def test_check_circular_dependency(self): + """Test circular dependency check + """ + 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)} + + check_circular_dependencies(self.report, all_repo_addons, parsed_xml, self.branch) + + records = [Record.__str__(r) for r in ReportManager.getEnabledReporters()[0].reports] + output = [record for record in records if record.startswith("ERROR: Circular")] + expected = ["ERROR: Circular dependencies: plugin.test.one"] + + self.assertListEqual(expected, output) + + def test_dependency_tree_creation(self): + """Test dependency tree creation + """ + addon_xml = join(self.path, "addon.xml") + addons_xml = join(self.path, "addons.xml") + + parsed_xml = ET.parse(addon_xml).getroot() + addon = Addon(parsed_xml) + all_repo_addons = {self.branch: Repository(self.branch, addons_xml)} + + output = create_dependency_tree(addon, all_repo_addons, self.branch) + expected = {'plugin.test.two': ['plugin.test.one'], 'plugin.test.one': ['plugin.test.two']} + + self.assertEqual(expected, 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 + + +