From 92f064dd987e08c9dfeff3084c3f786eba2ebcc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Palancher?= Date: Wed, 4 Jun 2025 17:19:40 +0200 Subject: [PATCH] patches: introduce module Create patches module to hold all functions called by validdiff action designed to parse patches files to detect modified and removed packages. This refactoring is mainly done with the purpose of making the Controller module lighter. Corresponding tests are also moved in a new testing module. --- lib/rift/Controller.py | 169 +---------------- lib/rift/patches.py | 213 ++++++++++++++++++++++ tests/Controller.py | 358 ++---------------------------------- tests/patches.py | 402 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 631 insertions(+), 511 deletions(-) create mode 100644 lib/rift/patches.py create mode 100644 tests/patches.py diff --git a/lib/rift/Controller.py b/lib/rift/Controller.py index 6a466fce..439403a1 100644 --- a/lib/rift/Controller.py +++ b/lib/rift/Controller.py @@ -52,12 +52,13 @@ from rift.Mock import Mock from rift.Package import Package, Test from rift.Repository import LocalRepository, ProjectArchRepositories -from rift.RPM import RPM, Spec, RPMLINT_CONFIG_V1, RPMLINT_CONFIG_V2 +from rift.RPM import RPM, Spec from rift.TempDir import TempDir from rift.TestResults import TestCase, TestResults from rift.TextTable import TextTable from rift.VM import VM from rift.sync import RepoSyncFactory +from rift.patches import get_packages_from_patch def message(msg): @@ -988,48 +989,6 @@ def action_sync(args, config): ) synchronizer.run() - -def get_packages_from_patch(patch, config, modules, staff): - """ - Return 2-tuple of dicts of updated and removed packages extracted from given - patch. - """ - updated = {} - removed = {} - patchedfiles = parse_unidiff(patch) - if not patchedfiles: - raise RiftError("Invalid patch detected (empty commit ?)") - - for patchedfile in patchedfiles: - modifies_packages = _validate_patched_file( - patchedfile, - config=config, - modules=modules, - staff=staff - ) - if not modifies_packages: - continue - pkg = _patched_file_updated_package( - patchedfile, - config=config, - modules=modules, - staff=staff - ) - if pkg is not None and pkg not in updated: - logging.info('Patch updates package %s', pkg.name) - updated[pkg.name] = pkg - pkg = _patched_file_removed_package( - patchedfile, - config=config, - modules=modules, - staff=staff - ) - if pkg is not None and pkg not in removed: - logging.info('Patch deletes package %s', pkg.name) - removed[pkg.name] = pkg - - return updated, removed - def create_staging_repo(config): """ Create and return staging temporary repository with a 2-tuple containing @@ -1230,130 +1189,6 @@ def action(config, args): return 0 -def _validate_patched_file(patched_file, config, modules, staff): - """ - Raise RiftError if patched_file is a binary file or does not match any known - file path in Rift project tree. - - Return True if the patched_file modifies a package or False otherwise. - """ - filepath = patched_file.path - names = filepath.split(os.path.sep) - - if filepath == config.get('staff_file'): - staff = Staff(config) - staff.load(filepath) - logging.info('Staff file is OK.') - return False - - if filepath == config.get('modules_file'): - modules = Modules(config, staff) - modules.load(filepath) - logging.info('Modules file is OK.') - return False - - if filepath == 'mock.tpl': - logging.debug('Ignoring mock template file: %s', filepath) - return False - - if filepath == '.gitignore': - logging.debug('Ignoring git file: %s', filepath) - return False - - if filepath == 'project.conf': - logging.debug('Ignoring project config file: %s', filepath) - return False - - if patched_file.binary: - raise RiftError(f"Binary file detected: {filepath}") - - if names[0] != config.get('packages_dir'): - raise RiftError(f"Unknown file pattern: {filepath}") - - return True - -def _patched_file_updated_package(patched_file, config, modules, staff): - """ - Return Package updated by patched_file, or None if either: - - - The patched_file modifies a package file that does not impact package - build result. - - The pached_file is removed. - - Raise RiftError if patched_file path does not match any known - packaging code file path. - """ - filepath = patched_file.path - names = filepath.split(os.path.sep) - fullpath = config.project_path(filepath) - pkg = None - - if patched_file.is_deleted_file: - logging.debug('Ignoring removed file: %s', filepath) - return None - - # Drop config.get('packages_dir') from list - names.pop(0) - - pkg = Package(names.pop(0), config, staff, modules) - - # info.yaml - if fullpath == pkg.metafile: - logging.info('Ignoring meta file') - return None - - # README file - if fullpath in pkg.docfiles: - logging.debug('Ignoring documentation file: %s', fullpath) - return None - - # backup specfile - if fullpath == f"{pkg.specfile}.orig": - logging.debug('Ignoring backup specfile') - return None - - # specfile - if fullpath == pkg.specfile: - logging.info('Detected spec file') - - # rpmlint config file - elif names in [RPMLINT_CONFIG_V1, RPMLINT_CONFIG_V2]: - logging.debug('Detecting rpmlint config file') - - # sources/ - elif fullpath.startswith(pkg.sourcesdir) and len(names) == 2: - logging.debug('Detecting source file: %s', names[1]) - - # tests/ - elif fullpath.startswith(pkg.testsdir): - logging.debug('Detecting test script: %s', filepath) - - else: - raise RiftError( - f"Unknown file pattern in '{pkg.name}' directory: {filepath}" - ) - - return pkg - -def _patched_file_removed_package(patched_file, config, modules, staff): - """ - Return Package removed by the patched_file or None if patched_file does not - remove any package. - """ - filepath = patched_file.path - names = filepath.split(os.path.sep) - fullpath = config.project_path(filepath) - - if not patched_file.is_deleted_file: - logging.debug('Ignoring not removed file: %s', filepath) - return None - - pkg = Package(names[1], config, staff, modules) - - if fullpath == pkg.metafile: - return pkg - - return None def main(args=None): """Main code of 'rift'""" diff --git a/lib/rift/patches.py b/lib/rift/patches.py new file mode 100644 index 00000000..3690af7a --- /dev/null +++ b/lib/rift/patches.py @@ -0,0 +1,213 @@ +# +# Copyright (C) 2025 CEA +# +# This file is part of Rift project. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. +# +""" +patches.py: + Package to parse patches files in unified diff format +""" +import os +import logging + +from unidiff import parse_unidiff +from rift import RiftError +from rift.Package import Package +from rift.RPM import RPMLINT_CONFIG_V1, RPMLINT_CONFIG_V2 +from rift.Config import Staff, Modules + + +def get_packages_from_patch(patch, config, modules, staff): + """ + Return 2-tuple of dicts of updated and removed packages extracted from given + patch. + """ + updated = {} + removed = {} + patchedfiles = parse_unidiff(patch) + if not patchedfiles: + raise RiftError("Invalid patch detected (empty commit ?)") + + for patchedfile in patchedfiles: + modifies_packages = _validate_patched_file( + patchedfile, + config=config, + modules=modules, + staff=staff + ) + if not modifies_packages: + continue + pkg = _patched_file_updated_package( + patchedfile, + config=config, + modules=modules, + staff=staff + ) + if pkg is not None and pkg not in updated: + logging.info('Patch updates package %s', pkg.name) + updated[pkg.name] = pkg + pkg = _patched_file_removed_package( + patchedfile, + config=config, + modules=modules, + staff=staff + ) + if pkg is not None and pkg not in removed: + logging.info('Patch deletes package %s', pkg.name) + removed[pkg.name] = pkg + + return updated, removed + + +def _validate_patched_file(patched_file, config, modules, staff): + """ + Raise RiftError if patched_file is a binary file or does not match any known + file path in Rift project tree. + + Return True if the patched_file modifies a package or False otherwise. + """ + filepath = patched_file.path + names = filepath.split(os.path.sep) + + if filepath == config.get('staff_file'): + staff = Staff(config) + staff.load(filepath) + logging.info('Staff file is OK.') + return False + + if filepath == config.get('modules_file'): + modules = Modules(config, staff) + modules.load(filepath) + logging.info('Modules file is OK.') + return False + + if filepath == 'mock.tpl': + logging.debug('Ignoring mock template file: %s', filepath) + return False + + if filepath == '.gitignore': + logging.debug('Ignoring git file: %s', filepath) + return False + + if filepath == 'project.conf': + logging.debug('Ignoring project config file: %s', filepath) + return False + + if patched_file.binary: + raise RiftError(f"Binary file detected: {filepath}") + + if names[0] != config.get('packages_dir'): + raise RiftError(f"Unknown file pattern: {filepath}") + + return True + + +def _patched_file_updated_package(patched_file, config, modules, staff): + """ + Return Package updated by patched_file, or None if either: + + - The patched_file modifies a package file that does not impact package + build result. + - The pached_file is removed. + + Raise RiftError if patched_file path does not match any known + packaging code file path. + """ + filepath = patched_file.path + names = filepath.split(os.path.sep) + fullpath = config.project_path(filepath) + pkg = None + + if patched_file.is_deleted_file: + logging.debug('Ignoring removed file: %s', filepath) + return None + + # Drop config.get('packages_dir') from list + names.pop(0) + + pkg = Package(names.pop(0), config, staff, modules) + + # info.yaml + if fullpath == pkg.metafile: + logging.info('Ignoring meta file') + return None + + # README file + if fullpath in pkg.docfiles: + logging.debug('Ignoring documentation file: %s', fullpath) + return None + + # backup specfile + if fullpath == f"{pkg.specfile}.orig": + logging.debug('Ignoring backup specfile') + return None + + # specfile + if fullpath == pkg.specfile: + logging.info('Detected spec file') + + # rpmlint config file + elif names in [RPMLINT_CONFIG_V1, RPMLINT_CONFIG_V2]: + logging.debug('Detecting rpmlint config file') + + # sources/ + elif fullpath.startswith(pkg.sourcesdir) and len(names) == 2: + logging.debug('Detecting source file: %s', names[1]) + + # tests/ + elif fullpath.startswith(pkg.testsdir): + logging.debug('Detecting test script: %s', filepath) + + else: + raise RiftError( + f"Unknown file pattern in '{pkg.name}' directory: {filepath}" + ) + + return pkg + + +def _patched_file_removed_package(patched_file, config, modules, staff): + """ + Return Package removed by the patched_file or None if patched_file does not + remove any package. + """ + filepath = patched_file.path + names = filepath.split(os.path.sep) + fullpath = config.project_path(filepath) + + if not patched_file.is_deleted_file: + logging.debug('Ignoring not removed file: %s', filepath) + return None + + pkg = Package(names[1], config, staff, modules) + + if fullpath == pkg.metafile: + return pkg + + return None diff --git a/tests/Controller.py b/tests/Controller.py index 562bcba1..8db6b2ae 100644 --- a/tests/Controller.py +++ b/tests/Controller.py @@ -9,15 +9,13 @@ import subprocess from io import StringIO -from unidiff import parse_unidiff from TestUtils import ( - make_temp_file, make_temp_dir, RiftTestCase, RiftProjectTestCase + make_temp_dir, RiftTestCase, RiftProjectTestCase ) from VM import GLOBAL_CACHE, VALID_IMAGE_URL, PROXY from rift.Controller import ( main, - get_packages_from_patch, remove_packages, make_parser, ) @@ -79,347 +77,19 @@ def test_action_query_on_bad_pkg(self): self.make_pkg(name='pkg2', metadata={}) self.assertEqual(main(['query']), 0) - def test_validdiff_readme(self): - """ Should allow README files """ - self.make_pkg() - patch_template = """ -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - packages: document 'pkg' - -diff --git a/packages/pkg/{0} b/packages/pkg/{0} -new file mode 100644 -index 0000000..e845566 ---- /dev/null -+++ b/packages/pkg/{0} -@@ -0,0 +1 @@ -+README -""" - - for fmt in '', 'rst', 'md', 'txt': - filename = 'README' - if fmt: - filename = "{0}.{1}".format(filename, fmt) - patch = make_temp_file(patch_template.format(filename)) - self.assertEqual(main(['validdiff', patch.name]), 0) - - def test_validdiff_binary(self): - """ Should fail if source file is a binary file """ - pkgname = 'pkg' - pkgvers = 1.0 - self.make_pkg(name=pkgname, version=pkgvers) - pkgsrc = os.path.join('packages', 'pkgname', 'sources', - '{0}-{1}.tar.gz'.format(pkgname, pkgvers)) - patch = make_temp_file(""" -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - packages: update 'pkg' sources - -diff --git /dev/null b/{0} -index fcd49dd..91ef207 100644 -Binary files a/sources/a.tar.gz and b/sources/a.tar.gz differ -""".format(pkgsrc)) - self.assert_except(RiftError, "Binary file detected: {0}".format(pkgsrc), - main, ['validdiff', patch.name]) - - def test_validdiff_binary_with_content(self): - """ Should fail if source file is a binary file (diff --binary) """ - pkgname = 'pkg' - pkgvers = 1.0 - self.make_pkg(name=pkgname, version=pkgvers) - pkgsrc = os.path.join('packages', 'pkgname', 'sources', - '{0}-{1}.tar.gz'.format(pkgname, pkgvers)) - patch = make_temp_file(""" -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - packages: update 'pkg' sources - -diff --git /dev/null b/{0} -index 6cd0ff6ec591f7f51a3479d7b66c6951a2b4afa9..91ef2076b67f3158ec1670fa7b88d88b2816aa91 100644 -GIT binary patch -literal 8 -PcmZQ%;Sf+z_{{#tQ1BL-x - -literal 4 -LcmZQ%;Sc}}-05kv| -""".format(pkgsrc)) - self.assert_except(RiftError, "Binary file detected: {0}".format(pkgsrc), - main, ['validdiff', patch.name]) - - def test_validdiff_package_removed(self): - """ Test detect removed package in patch""" - pkgname = 'pkg' - pkgvers = 1.0 - self.make_pkg(name=pkgname, version=pkgvers) - pkgsrc = os.path.join('packages', 'pkgname', 'sources', - '{0}-{1}.tar.gz'.format(pkgname, pkgvers)) - patch = make_temp_file(""" -diff --git a/packages/pkg/info.yaml b/packages/pkg/info.yaml -deleted file mode 100644 -index 32ac08e..0000000 ---- a/packages/pkg/info.yaml -+++ /dev/null -@@ -1,6 +0,0 @@ --package: -- maintainers: -- - Myself -- module: Great module -- origin: Vendor -- reason: Missing feature -diff --git a/packages/pkg/pkg.spec b/packages/pkg/pkg.spec -deleted file mode 100644 -index b92c49d..0000000 ---- a/packages/pkg/pkg.spec -+++ /dev/null -@@ -1,24 +0,0 @@ --Name: pkg --Version: 1.0 --Release: 1 --Summary: A package --Group: System Environment/Base --License: GPL --URL: http://nowhere.com/projects/%{{name}}/ --Source0: %{{name}}-%{{version}}.tar.gz --BuildArch: noarch --BuildRequires: br-package --Requires: another-package --Provides: pkg-provide --%description --A package --%prep --%build --# Nothing to build --%install --# Nothing to install --%files --# No files --%changelog --* Tue Feb 26 2019 Myself - 1.0-1 --- Update to 1.0 release -diff --git a/{0} b/{0} -deleted file mode 100644 -index 43bf48d..0000000 ---- a/{0} -+++ /dev/null -@@ -1 +0,0 @@ --ACACACACACACACAC -\ No newline at end of file -""".format(pkgsrc)) - - with open(patch.name) as p: - (updated, removed) = get_packages_from_patch( - p, self.config, self.modules, self.staff - ) - self.assertEqual(len(updated), 0) - self.assertEqual(len(removed), 1) - self.assertTrue('pkg' in removed.keys()) - - def test_validdiff_on_tests_directory(self): - """ Test if package tests directory structure is fine """ - patch = make_temp_file(""" -diff --git a/packages/pkg/tests/sources/deep/source.c b/packages/pkg/tests/sources/deep/source.c -new file mode 100644 -index 0000000..68344bf ---- /dev/null -+++ b/packages/pkg/tests/sources/deep/source.c -@@ -0,0 +1,4 @@ -+#include -+int main(int argc, char **argv){ -+ exit(0); -+} -\ No newline at end of file -""") - # Ensure package exists - self.make_pkg('pkg') - with open(patch.name, 'r') as f: - (updated, removed) = get_packages_from_patch( - f, self.config, self.modules, self.staff - ) - self.assertEqual(len(updated), 1) - self.assertEqual(len(removed), 0) - self.assertTrue('pkg' in updated.keys()) - - def test_validdiff_on_invalid_file(self): - """Test invalid project file is detected in patch""" - patch = make_temp_file(""" -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - project wrong file - -diff --git a/wrong b/wrong -new file mode 100644 -index 0000000..68344bf ---- a/wrong -+++ b/wrong -@@ -0,0 +1 @@ -+README -""") - self.assert_except(RiftError, "Unknown file pattern: wrong", - main, ['validdiff', patch.name]) - - def test_validdiff_on_invalid_pkg_file(self): - """Test invalid package file is detected in patch""" - patch = make_temp_file(""" -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - packages: Wrong file - -diff --git a/packages/pkg/wrong b/packages/pkg/wrong -new file mode 100644 -index 0000000..68344bf ---- a/packages/pkg/wrong -+++ b/packages/pkg/wrong -@@ -0,0 +1 @@ -+README -""") - self.assert_except(RiftError, "Unknown file pattern in 'pkg' directory: packages/pkg/wrong", - main, ['validdiff', patch.name]) - - def test_validdiff_on_info(self): - patch = make_temp_file(""" -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - packages: update 'pkg' infos - -diff --git a/packages/pkg/info.yaml b/packages/pkg/info.yaml -new file mode 100644 -index 0000000..68344bf ---- a/packages/pkg/info.yaml -+++ b/packages/pkg/info.yaml -@@ -2,5 +2,5 @@ package: - maintainers: - - Myself - module: Great module -- origin: Somewhere -+ origin: Elsewhere - reason: Missing feature -""") - self.make_pkg() - self.assertEqual(main(['validdiff', patch.name]), 0) - # For this patch, get_packages_from_patch() must not return updated nor - # removed packages. - with open(patch.name, 'r') as p: - (updated, removed) = get_packages_from_patch( - p, config=self.config, modules=self.modules, staff=self.staff - ) - self.assertEqual(len(updated), 0) - self.assertEqual(len(removed), 0) - - def test_validdiff_on_modules(self): - patch = make_temp_file(""" -commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - modules: add 'Section' - -diff --git a/packages/modules.yaml b/packages/modules.yaml -new file mode 100644 -index 0000000..68344bf ---- a/packages/modules.yaml -+++ b/packages/modules.yaml -@@ -0,0 +3 @@ -+modules: -+ User Tools: -+ manager: John Doe -""") - self.assertEqual(main(['validdiff', patch.name]), 0) - # For this patch, get_packages_from_patch() must not return updated nor - # removed packages. - with open(patch.name, 'r') as p: - (updated, removed) = get_packages_from_patch( - p, config=self.config, modules=self.modules, staff=self.staff - ) - self.assertEqual(len(updated), 0) - self.assertEqual(len(removed), 0) - - def test_rename_package(self): - """ Test if renaming a package trigger a build """ - pkgname = 'pkg' - pkgvers = 1.0 - self.make_pkg(name=pkgname, version=pkgvers) - patch = make_temp_file(""" -diff --git a/packages/pkg/pkg.spec b/packages/pkgnew/pkgnew.spec -similarity index 100% -rename from packages/pkg/pkg.spec -rename to packages/pkgnew/pkgnew.spec -diff --git a/packages/pkg/info.yaml b/packages/pkgnew/info.yaml -similarity index 100% -rename from packages/pkg/info.yaml -rename to packages/pkgnew/info.yaml -diff --git a/packages/pkg/sources/pkg-1.0.tar.gz b/packages/pkgnew/sources/pkgnew-1.0.tar.gz -similarity index 100% -rename from packages/pkg/sources/pkg-1.0.tar.gz -rename to packages/pkgnew/sources/pkgnew-1.0.tar.gz -""") - # For this patch, get_packages_from_patch() must return an updated - # package named pkgnew. - with open(patch.name, 'r') as p: - (updated, removed) = get_packages_from_patch( - p, config=self.config, modules=self.modules, staff=self.staff - ) - self.assertEqual(len(updated), 1) - self.assertEqual(len(removed), 0) - self.assertTrue('pkgnew' in updated.keys()) - - def test_rename_and_update_package(self): - """ Test if renaming and updating a package trigger a build """ - pkgname = 'pkg' - pkgvers = 1.0 - self.make_pkg(name=pkgname, version=pkgvers) - patch = make_temp_file(""" -commit f8c1a88ea96adfccddab0bf43c0a90f05ab26dc5 (HEAD -> playground) -Author: Myself -Date: Thu Apr 25 14:30:41 2019 +0200 - - packages: rename 'pkg' to 'pkgnew' - -diff --git a/packages/pkg/info.yaml b/packages/pkgnew/info.yaml -similarity index 100% -rename from packages/pkg/info.yaml -rename to packages/pkgnew/info.yaml -diff --git a/packages/pkg/pkg.spec b/packages/pkgnew/pkgnew.spec -similarity index 93% -rename from packages/pkg/pkg.spec -rename to packages/pkgnew/pkgnew.spec -index b92c49d..0fa690c 100644 ---- a/packages/pkg/pkg.spec -+++ b/packages/pkgnew/pkgnew.spec -@@ -1,6 +1,6 @@ --Name: pkg -+Name: pkgnew - Version: 1.0 --Release: 1 -+Release: 2 - Summary: A package - Group: System Environment/Base - License: GPL -diff --git a/packages/pkg/sources/pkg-1.0.tar.gz b/packages/pkgnew/sources/pkgnew-1.0.tar.gz -similarity index 100% -rename from packages/pkg/sources/pkg-1.0.tar.gz -rename to packages/pkgnew/sources/pkgnew-1.0.tar.gz -""") - # For this patch, get_packages_from_patch() must return an updated - # package named pkgnew. - with open(patch.name, 'r') as p: - (updated, removed) = get_packages_from_patch( - p, config=self.config, modules=self.modules, staff=self.staff - ) - self.assertEqual(len(updated), 1) - self.assertEqual(len(removed), 0) - self.assertTrue('pkgnew' in updated.keys()) + @patch('rift.Controller.remove_packages') + @patch('rift.Controller.validate_pkgs') + @patch('rift.Controller.get_packages_from_patch') + def test_action_validdiff(self, mock_get_packages_from_patch, + mock_validate_pkgs, mock_remove_packages): + """ Test validdiff action calls expected functions """ + mock_get_packages_from_patch.return_value = ( + {'pkg': Package('pkg', self.config, self.staff, self.modules)}, {} + ) + self.assertEqual(main(['validdiff', '/dev/null']), 0) + mock_get_packages_from_patch.assert_called_once() + mock_validate_pkgs.assert_called_once() + mock_remove_packages.assert_called_once() @patch('rift.Controller.ProjectArchRepositories') def test_remove_packages(self, mock_parepository_class): diff --git a/tests/patches.py b/tests/patches.py new file mode 100644 index 00000000..fd0c34ad --- /dev/null +++ b/tests/patches.py @@ -0,0 +1,402 @@ +# +# Copyright (C) 2025 CEA +# +# This file is part of Rift project. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. +# + +import os + +from TestUtils import make_temp_file, RiftProjectTestCase + +from rift import RiftError +from rift.patches import get_packages_from_patch + +class PatchTest(RiftProjectTestCase): + + def test_package_removed(self): + """ Test detect removed package in patch""" + pkgname = 'pkg' + pkgvers = 1.0 + self.make_pkg(name=pkgname, version=pkgvers) + pkgsrc = os.path.join('packages', 'pkgname', 'sources', + '{0}-{1}.tar.gz'.format(pkgname, pkgvers)) + patch = make_temp_file(""" +diff --git a/packages/pkg/info.yaml b/packages/pkg/info.yaml +deleted file mode 100644 +index 32ac08e..0000000 +--- a/packages/pkg/info.yaml ++++ /dev/null +@@ -1,6 +0,0 @@ +-package: +- maintainers: +- - Myself +- module: Great module +- origin: Vendor +- reason: Missing feature +diff --git a/packages/pkg/pkg.spec b/packages/pkg/pkg.spec +deleted file mode 100644 +index b92c49d..0000000 +--- a/packages/pkg/pkg.spec ++++ /dev/null +@@ -1,24 +0,0 @@ +-Name: pkg +-Version: 1.0 +-Release: 1 +-Summary: A package +-Group: System Environment/Base +-License: GPL +-URL: http://nowhere.com/projects/%{{name}}/ +-Source0: %{{name}}-%{{version}}.tar.gz +-BuildArch: noarch +-BuildRequires: br-package +-Requires: another-package +-Provides: pkg-provide +-%description +-A package +-%prep +-%build +-# Nothing to build +-%install +-# Nothing to install +-%files +-# No files +-%changelog +-* Tue Feb 26 2019 Myself - 1.0-1 +-- Update to 1.0 release +diff --git a/{0} b/{0} +deleted file mode 100644 +index 43bf48d..0000000 +--- a/{0} ++++ /dev/null +@@ -1 +0,0 @@ +-ACACACACACACACAC +\ No newline at end of file +""".format(pkgsrc)) + + with open(patch.name) as p: + (updated, removed) = get_packages_from_patch( + p, self.config, self.modules, self.staff + ) + self.assertEqual(len(updated), 0) + self.assertEqual(len(removed), 1) + self.assertTrue('pkg' in removed.keys()) + + def test_tests_directory(self): + """ Test if package tests directory structure is fine """ + patch = make_temp_file(""" +diff --git a/packages/pkg/tests/sources/deep/source.c b/packages/pkg/tests/sources/deep/source.c +new file mode 100644 +index 0000000..68344bf +--- /dev/null ++++ b/packages/pkg/tests/sources/deep/source.c +@@ -0,0 +1,4 @@ ++#include ++int main(int argc, char **argv){ ++ exit(0); ++} +\ No newline at end of file +""") + # Ensure package exists + self.make_pkg('pkg') + with open(patch.name, 'r') as f: + (updated, removed) = get_packages_from_patch( + f, self.config, self.modules, self.staff + ) + self.assertEqual(len(updated), 1) + self.assertEqual(len(removed), 0) + self.assertTrue('pkg' in updated.keys()) + + def test_invalid_file(self): + """Test invalid project file is detected in patch""" + patch = make_temp_file(""" +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + project wrong file + +diff --git a/wrong b/wrong +new file mode 100644 +index 0000000..68344bf +--- a/wrong ++++ b/wrong +@@ -0,0 +1 @@ ++README +""") + with open(patch.name, 'r') as f: + with self.assertRaisesRegex(RiftError, + "Unknown file pattern: wrong"): + get_packages_from_patch( + f, self.config, self.modules, self.staff + ) + + def test_invalid_pkg_file(self): + """Test invalid package file is detected in patch""" + patch = make_temp_file(""" +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + packages: Wrong file + +diff --git a/packages/pkg/wrong b/packages/pkg/wrong +new file mode 100644 +index 0000000..68344bf +--- a/packages/pkg/wrong ++++ b/packages/pkg/wrong +@@ -0,0 +1 @@ ++README +""") + with open(patch.name, 'r') as f: + with self.assertRaisesRegex( + RiftError, + "Unknown file pattern in 'pkg' directory: packages/pkg/wrong"): + get_packages_from_patch( + f, self.config, self.modules, self.staff + ) + + def test_info(self): + patch = make_temp_file(""" +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + packages: update 'pkg' infos + +diff --git a/packages/pkg/info.yaml b/packages/pkg/info.yaml +new file mode 100644 +index 0000000..68344bf +--- a/packages/pkg/info.yaml ++++ b/packages/pkg/info.yaml +@@ -2,5 +2,5 @@ package: + maintainers: + - Myself + module: Great module +- origin: Somewhere ++ origin: Elsewhere + reason: Missing feature +""") + self.make_pkg() + # For this patch, get_packages_from_patch() must not return updated nor + # removed packages. + with open(patch.name, 'r') as p: + (updated, removed) = get_packages_from_patch( + p, config=self.config, modules=self.modules, staff=self.staff + ) + self.assertEqual(len(updated), 0) + self.assertEqual(len(removed), 0) + + def test_modules(self): + patch = make_temp_file(""" +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + modules: add 'Section' + +diff --git a/packages/modules.yaml b/packages/modules.yaml +new file mode 100644 +index 0000000..68344bf +--- a/packages/modules.yaml ++++ b/packages/modules.yaml +@@ -0,0 +3 @@ ++modules: ++ User Tools: ++ manager: John Doe +""") + # For this patch, get_packages_from_patch() must not return updated nor + # removed packages. + with open(patch.name, 'r') as p: + (updated, removed) = get_packages_from_patch( + p, config=self.config, modules=self.modules, staff=self.staff + ) + self.assertEqual(len(updated), 0) + self.assertEqual(len(removed), 0) + + def test_readme(self): + """ Should allow README files """ + self.make_pkg() + patch_template = """ +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + packages: document 'pkg' + +diff --git a/packages/pkg/{0} b/packages/pkg/{0} +new file mode 100644 +index 0000000..e845566 +--- /dev/null ++++ b/packages/pkg/{0} +@@ -0,0 +1 @@ ++README +""" + + for fmt in '', 'rst', 'md', 'txt': + filename = 'README' + if fmt: + filename = "{0}.{1}".format(filename, fmt) + patch = make_temp_file(patch_template.format(filename)) + with open(patch.name, 'r') as f: + (updated, removed) = get_packages_from_patch( + f, self.config, self.modules, self.staff + ) + self.assertEqual(len(updated), 0) + self.assertEqual(len(removed), 0) + + def test_binary(self): + """ Should fail if source file is a binary file """ + pkgname = 'pkg' + pkgvers = 1.0 + self.make_pkg(name=pkgname, version=pkgvers) + pkgsrc = os.path.join('packages', 'pkgname', 'sources', + '{0}-{1}.tar.gz'.format(pkgname, pkgvers)) + patch = make_temp_file(""" +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + packages: update 'pkg' sources + +diff --git /dev/null b/{0} +index fcd49dd..91ef207 100644 +Binary files a/sources/a.tar.gz and b/sources/a.tar.gz differ +""".format(pkgsrc)) + with open(patch.name, 'r') as f: + with self.assertRaisesRegex( + RiftError, + "Binary file detected: {0}".format(pkgsrc)): + get_packages_from_patch( + f, self.config, self.modules, self.staff + ) + + def test_binary_with_content(self): + """ Should fail if source file is a binary file (diff --binary) """ + pkgname = 'pkg' + pkgvers = 1.0 + self.make_pkg(name=pkgname, version=pkgvers) + pkgsrc = os.path.join('packages', 'pkgname', 'sources', + '{0}-{1}.tar.gz'.format(pkgname, pkgvers)) + patch = make_temp_file(""" +commit 0ac8155e2655321ceb28bbf716ff66d1a9e30f29 (HEAD -> master) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + packages: update 'pkg' sources + +diff --git /dev/null b/{0} +index 6cd0ff6ec591f7f51a3479d7b66c6951a2b4afa9..91ef2076b67f3158ec1670fa7b88d88b2816aa91 100644 +GIT binary patch +literal 8 +PcmZQ%;Sf+z_{{#tQ1BL-x + +literal 4 +LcmZQ%;Sc}}-05kv| +""".format(pkgsrc)) + with open(patch.name, 'r') as f: + with self.assertRaisesRegex(RiftError, "Binary file detected: {0}".format(pkgsrc)): + get_packages_from_patch( + f, self.config, self.modules, self.staff + ) + + def test_rename_package(self): + """ Test if renaming a package trigger a build """ + pkgname = 'pkg' + pkgvers = 1.0 + self.make_pkg(name=pkgname, version=pkgvers) + patch = make_temp_file(""" +diff --git a/packages/pkg/pkg.spec b/packages/pkgnew/pkgnew.spec +similarity index 100% +rename from packages/pkg/pkg.spec +rename to packages/pkgnew/pkgnew.spec +diff --git a/packages/pkg/info.yaml b/packages/pkgnew/info.yaml +similarity index 100% +rename from packages/pkg/info.yaml +rename to packages/pkgnew/info.yaml +diff --git a/packages/pkg/sources/pkg-1.0.tar.gz b/packages/pkgnew/sources/pkgnew-1.0.tar.gz +similarity index 100% +rename from packages/pkg/sources/pkg-1.0.tar.gz +rename to packages/pkgnew/sources/pkgnew-1.0.tar.gz +""") + # For this patch, get_packages_from_patch() must return an updated + # package named pkgnew. + with open(patch.name, 'r') as p: + (updated, removed) = get_packages_from_patch( + p, config=self.config, modules=self.modules, staff=self.staff + ) + self.assertEqual(len(updated), 1) + self.assertEqual(len(removed), 0) + self.assertTrue('pkgnew' in updated.keys()) + + def test_rename_and_update_package(self): + """ Test if renaming and updating a package trigger a build """ + pkgname = 'pkg' + pkgvers = 1.0 + self.make_pkg(name=pkgname, version=pkgvers) + patch = make_temp_file(""" +commit f8c1a88ea96adfccddab0bf43c0a90f05ab26dc5 (HEAD -> playground) +Author: Myself +Date: Thu Apr 25 14:30:41 2019 +0200 + + packages: rename 'pkg' to 'pkgnew' + +diff --git a/packages/pkg/info.yaml b/packages/pkgnew/info.yaml +similarity index 100% +rename from packages/pkg/info.yaml +rename to packages/pkgnew/info.yaml +diff --git a/packages/pkg/pkg.spec b/packages/pkgnew/pkgnew.spec +similarity index 93% +rename from packages/pkg/pkg.spec +rename to packages/pkgnew/pkgnew.spec +index b92c49d..0fa690c 100644 +--- a/packages/pkg/pkg.spec ++++ b/packages/pkgnew/pkgnew.spec +@@ -1,6 +1,6 @@ +-Name: pkg ++Name: pkgnew + Version: 1.0 +-Release: 1 ++Release: 2 + Summary: A package + Group: System Environment/Base + License: GPL +diff --git a/packages/pkg/sources/pkg-1.0.tar.gz b/packages/pkgnew/sources/pkgnew-1.0.tar.gz +similarity index 100% +rename from packages/pkg/sources/pkg-1.0.tar.gz +rename to packages/pkgnew/sources/pkgnew-1.0.tar.gz +""") + # For this patch, get_packages_from_patch() must return an updated + # package named pkgnew. + with open(patch.name, 'r') as p: + (updated, removed) = get_packages_from_patch( + p, config=self.config, modules=self.modules, staff=self.staff + ) + self.assertEqual(len(updated), 1) + self.assertEqual(len(removed), 0) + self.assertTrue('pkgnew' in updated.keys())