From f922090dc6c43a555bb83e338adc7894016eef6e Mon Sep 17 00:00:00 2001 From: Sergey Kulanov Date: Wed, 8 Jun 2016 16:33:07 +0300 Subject: [PATCH] Allow to search mandatory packages in all forest Update requirements with filter option: "mandatory": { "enum": ["exact", "newest"] } 1) [RPM] We need to search the newest mandatory packages in all forest but not from the exact tree, also repo priority should be take into account 2) Add priority to gen_repository in tests, since this is mandatory parameter 3) [Tests] Add package10 with different version to check mandatory values: - None - "exact" - "newest" match for mandatory packages Change-Id: I36a1463b495075bf6c44a46b557c3ac524de0220 Closes-bug: #1585103 --- packetary/api/repositories.py | 28 +++++++++--- packetary/objects/packages_forest.py | 10 +---- packetary/schemas/requirements_schema.py | 4 +- packetary/tests/stubs/generator.py | 6 ++- packetary/tests/test_packages_forest.py | 15 +------ packetary/tests/test_repository_api.py | 54 ++++++++++++++++++++++-- 6 files changed, 81 insertions(+), 36 deletions(-) diff --git a/packetary/api/repositories.py b/packetary/api/repositories.py index e22110c..ec8c54c 100644 --- a/packetary/api/repositories.py +++ b/packetary/api/repositories.py @@ -21,12 +21,14 @@ import logging import six +from packetary import objects +from packetary import schemas + from packetary.api.context import Context from packetary.api.options import RepositoryCopyOptions from packetary.controllers import RepositoryController from packetary.library.functions import compose -from packetary import objects -from packetary import schemas +from packetary.objects.package_relation import PackageRelation from packetary.api.loaders import get_packages_traverse from packetary.api.loaders import load_package_relations @@ -37,6 +39,12 @@ from packetary.api.validators import declare_schema logger = logging.getLogger(__package__) +_MANDATORY = { + "exact": "=", + "newest": ">=", +} + + class RepositoryApi(object): """Provides high-level API to operate with repositories.""" @@ -149,16 +157,24 @@ class RepositoryApi(object): requirements.get('repositories'), package_relations.append ) for repo in repositories: + tree = forest.add_tree(repo.priority) self.controller.load_packages( repo, compose( - forest.add_tree(repo.priority).add, + tree.add, packages_traverse ) ) - return forest.get_packages( - package_relations, requirements.get('mandatory', True) - ) + mandatory = requirements.get('mandatory') + if mandatory: + for package in tree.mandatory_packages: + package_relations.append( + PackageRelation.from_args( + (package.name, + _MANDATORY[requirements['mandatory']], + package.version))) + + return forest.get_packages(package_relations) packages = set() self._load_packages(repositories, packages.add) diff --git a/packetary/objects/packages_forest.py b/packetary/objects/packages_forest.py index 1e7be01..f106b9b 100644 --- a/packetary/objects/packages_forest.py +++ b/packetary/objects/packages_forest.py @@ -44,12 +44,10 @@ class PackagesForest(object): tree = self.trees[priority] = PackagesTree() return tree - def get_packages(self, requirements, include_mandatory=False): + def get_packages(self, requirements): """Get the packages according requirements. :param requirements: the list of requirements - :param include_mandatory: if true, the mandatory packages will be - included to result :return list of packages to copy """ @@ -61,12 +59,6 @@ class PackagesForest(object): unresolved = set() stack = [(None, requirements)] - if include_mandatory: - for tree in six.itervalues(self.trees): - for mandatory in tree.mandatory_packages: - resolved.add(mandatory) - stack.append((mandatory, mandatory.requires)) - while stack: pkg, requirements = stack.pop() for required in requirements: diff --git a/packetary/schemas/requirements_schema.py b/packetary/schemas/requirements_schema.py index e532f75..3a3c51c 100644 --- a/packetary/schemas/requirements_schema.py +++ b/packetary/schemas/requirements_schema.py @@ -68,7 +68,7 @@ REQUIREMENTS_SCHEMA = { } }, "mandatory": { - "type": "boolean" - }, + "enum": ["exact", "newest"] + } } } diff --git a/packetary/tests/stubs/generator.py b/packetary/tests/stubs/generator.py index f836f89..cd8e80e 100644 --- a/packetary/tests/stubs/generator.py +++ b/packetary/tests/stubs/generator.py @@ -20,10 +20,12 @@ from packetary import objects def gen_repository(name="test", url="file:///test", - architecture="x86_64", origin="Test", **kwargs): + architecture="x86_64", priority=99, + origin="Test", **kwargs): """Helper to create Repository object with default attributes.""" url = kwargs.pop("uri", url) - return objects.Repository(name, url, architecture, origin, **kwargs) + return objects.Repository(name, url, architecture, priority, origin, + **kwargs) def gen_relation(name="test", version=None, alternative=None): diff --git a/packetary/tests/test_packages_forest.py b/packetary/tests/test_packages_forest.py index 0cd0704..afe513a 100644 --- a/packetary/tests/test_packages_forest.py +++ b/packetary/tests/test_packages_forest.py @@ -96,22 +96,11 @@ class TestPackagesForest(base.TestCase): p22, forest.find(generator.gen_relation("package2", [">=", 2])) ) - def test_get_packages_with_mandatory(self): + def test_get_packages(self): forest = PackagesForest() self._generate_packages(forest) packages = forest.get_packages( - [generator.gen_relation("package3")], True - ) - self.assertItemsEqual( - ["package1", "package2", "package3", "package4", "package5"], - (x.name for x in packages) - ) - - def test_get_packages_without_mandatory(self): - forest = PackagesForest() - self._generate_packages(forest) - packages = forest.get_packages( - [generator.gen_relation("package3")], False + [generator.gen_relation("package3")] ) self.assertItemsEqual( ["package2", "package3", "package5"], diff --git a/packetary/tests/test_repository_api.py b/packetary/tests/test_repository_api.py index 58de6d5..b92fe39 100644 --- a/packetary/tests/test_repository_api.py +++ b/packetary/tests/test_repository_api.py @@ -79,8 +79,12 @@ class TestRepositoryApi(base.TestCase): name='{0}_5'.format(r.name), repository=r, requires=[generator.gen_relation("unresolved")] ), + generator.gen_package( + name='package10', repository=r, mandatory=True, + requires=None, version=counter + 10 + ) ] - for r in self.repos + for counter, r in enumerate(self.repos) ] self.controller.load_packages.side_effect = self.packages @@ -139,7 +143,7 @@ class TestRepositoryApi(base.TestCase): self._generate_repositories(1) self._generate_packages() packages = self.api.get_packages(self.repos_data) - self.assertEqual(5, len(self.packages[0])) + self.assertEqual(6, len(self.packages[0])) self.assertItemsEqual(self.packages[0], packages) jsonschema_mock.validate.assert_called_once_with( self.repos_data, self.api._get_repositories_data_schema() @@ -148,10 +152,30 @@ class TestRepositoryApi(base.TestCase): def test_get_packages_by_requirements(self, jsonschema_mock): self._generate_repositories(2) self._generate_packages() + requirements = { + 'packages': [{"name": "repo0_1"}], + 'repositories': [{"name": "repo1"}] + } + packages = self.api.get_packages(self.repos_data, requirements) + expected_packages = [self.packages[0][0]] + self.packages[1] + self.assertItemsEqual( + [x.name for x in expected_packages], + [x.name for x in packages] + ) + repos_schema = self.api._get_repositories_data_schema() + jsonschema_mock.validate.assert_has_calls([ + mock.call(self.repos_data, repos_schema), + mock.call(requirements, schemas.REQUIREMENTS_SCHEMA) + ], any_order=True) + + def test_get_packages_by_requirements_newest_mandatory(self, + jsonschema_mock): + self._generate_repositories(2) + self._generate_packages() requirements = { 'packages': [{"name": "repo0_1"}], 'repositories': [{"name": "repo1"}], - 'mandatory': True + 'mandatory': "newest" } packages = self.api.get_packages(self.repos_data, requirements) expected_packages = self.packages[0][:3] + self.packages[1] @@ -165,6 +189,29 @@ class TestRepositoryApi(base.TestCase): mock.call(requirements, schemas.REQUIREMENTS_SCHEMA) ], any_order=True) + def test_get_packages_by_requirements_exact_mandatory(self, + jsonschema_mock): + self._generate_repositories(2) + self._generate_packages() + requirements = { + 'packages': [{"name": "repo0_1"}], + 'repositories': [{"name": "repo1"}], + 'mandatory': "exact" + } + packages = self.api.get_packages(self.repos_data, requirements) + expected_packages = self.packages[0][:3] + \ + [self.packages[0][-1]] + \ + self.packages[1] + self.assertItemsEqual( + [x.name for x in expected_packages], + [x.name for x in packages] + ) + repos_schema = self.api._get_repositories_data_schema() + jsonschema_mock.validate.assert_has_calls([ + mock.call(self.repos_data, repos_schema), + mock.call(requirements, schemas.REQUIREMENTS_SCHEMA) + ], any_order=True) + def test_clone_repositories_as_is(self, jsonschema_mock): self._generate_repositories(1) self._generate_packages() @@ -193,7 +240,6 @@ class TestRepositoryApi(base.TestCase): requirements = { 'packages': [{"name": "repo0_1"}], 'repositories': [{"name": "repo1"}], - 'mandatory': False } self.controller.assign_packages.return_value = [0, 1, 1] * 3 stats = self.api.clone_repositories(