diff --git a/koji_wrapper/tag.py b/koji_wrapper/tag.py index 1ea64e6..004f5f8 100644 --- a/koji_wrapper/tag.py +++ b/koji_wrapper/tag.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ KojiTag Module """ +from toolchest.rpm.rpmvercmp import rpmvercmp from koji_wrapper.wrapper import KojiWrapper from koji_wrapper.validators import validate_required, validate_str_or_list @@ -89,8 +90,23 @@ def _filter_tagged(self, tagged_builds): return self.tagged_list def latest_by_nvr(self): - # TODO: implement/port _find_latest logic - pass + """ + Return the build from the cached builds list (listTagged) that has the + greatest NVR. This uses the toolchest library to also account for + timestamps and other things that make the koji '--latest' flag not + always return the desired result. + """ + greatest_nvr = None + for build in self.builds(): + if greatest_nvr: + # Compare previously found greatest nvr with the next item in + # the list and only change greatest_nvr if the new item is + # greater. + if rpmvercmp(build['nvr'], greatest_nvr['nvr']) == 1: + greatest_nvr = build + else: + greatest_nvr = build + return greatest_nvr def builds_by_attribute(self, attribute): """ diff --git a/koji_wrapper/test_support/__init__.py b/koji_wrapper/test_support/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/koji_wrapper/test_support/sample_data.py b/koji_wrapper/test_support/sample_data.py new file mode 100644 index 0000000..7c0d5d2 --- /dev/null +++ b/koji_wrapper/test_support/sample_data.py @@ -0,0 +1,170 @@ +package_builds_by_tag = [ + { + "build_id": 3275129, + "completion_time": "2024-09-09 03:25:39.568158", + "create_event": 60169004, + "creation_event_id": 60168954, + "creation_time": "2024-09-09 03:16:56.806868", + "draft": False, + "epoch": None, + "id": 3275129, + "name": "important-container", + "nvr": "important-container-18.0.0-26.1725851546", + "owner_id": 4119, + "owner_name": "builderbot1", + "package_id": 84908, + "package_name": "important-container", + "promoter_id": None, + "promoter_name": None, + "promotion_time": None, + "release": "26.1725851546", + "start_time": "2024-09-09 03:16:56.802744", + "state": 1, + "tag_id": 139665, + "tag_name": "some_release", + "task_id": 64054213, + "version": "18.0.0", + "volume_id": 0, + "volume_name": "DEFAULT" + }, + { + "build_id": 3266643, + "completion_time": "2024-09-04 08:34:18.374651", + "create_event": 60067873, + "creation_event_id": 60067708, + "creation_time": "2024-09-04 08:27:48.275598", + "draft": False, + "epoch": None, + "id": 3266643, + "name": "important-container", + "nvr": "important-container-18.0.0-29", + "owner_id": 6716, + "owner_name": "builderbot2", + "package_id": 84908, + "package_name": "important-container", + "promoter_id": None, + "promoter_name": None, + "promotion_time": None, + "release": "29", + "start_time": "2024-09-04 08:27:48.270981", + "state": 1, + "tag_id": 139665, + "tag_name": "some_release", + "task_id": 63935082, + "version": "18.0.0", + "volume_id": 0, + "volume_name": "DEFAULT" + }, + { + "build_id": 3264161, + "completion_time": "2024-09-03 18:35:07.165203", + "create_event": 60055330, + "creation_event_id": 60055144, + "creation_time": "2024-09-03 18:21:12.624179", + "draft": False, + "epoch": None, + "id": 3264161, + "name": "important-container", + "nvr": "important-container-18.0.0-26.1725387536", + "owner_id": 4119, + "owner_name": "builderbot1", + "package_id": 84908, + "package_name": "important-container", + "promoter_id": None, + "promoter_name": None, + "promotion_time": None, + "release": "26.1725387536", + "start_time": "2024-09-03 18:21:12.616153", + "state": 1, + "tag_id": 139665, + "tag_name": "some_release", + "task_id": 63908214, + "version": "18.0.0", + "volume_id": 0, + "volume_name": "DEFAULT" + }, + { + "build_id": 3262636, + "completion_time": "2024-09-03 06:52:41.849530", + "create_event": 60042926, + "creation_event_id": 60042686, + "creation_time": "2024-09-03 06:43:17.553500", + "draft": False, + "epoch": None, + "id": 3262636, + "name": "important-container", + "nvr": "important-container-18.0.0-26.1725345668", + "owner_id": 4119, + "owner_name": "builderbot1", + "package_id": 84908, + "package_name": "important-container", + "promoter_id": None, + "promoter_name": None, + "promotion_time": None, + "release": "26.1725345668", + "start_time": "2024-09-03 06:43:17.529565", + "state": 1, + "tag_id": 139665, + "tag_name": "some_release", + "task_id": 63878460, + "version": "18.0.0", + "volume_id": 0, + "volume_name": "DEFAULT" + }, + { + "build_id": 3253101, + "completion_time": "2024-08-28 05:39:45.143113", + "create_event": 59926605, + "creation_event_id": 59926588, + "creation_time": "2024-08-28 05:33:36.432830", + "draft": False, + "epoch": None, + "id": 3253101, + "name": "important-container", + "nvr": "important-container-18.0.0-28", + "owner_id": 6716, + "owner_name": "builderbot2", + "package_id": 84908, + "package_name": "important-container", + "promoter_id": None, + "promoter_name": None, + "promotion_time": None, + "release": "28", + "start_time": "2024-08-28 05:33:36.423307", + "state": 1, + "tag_id": 139665, + "tag_name": "some_release", + "task_id": 63734338, + "version": "18.0.0", + "volume_id": 0, + "volume_name": "DEFAULT" + }, + { + "build_id": 3242053, + "completion_time": "2024-08-22 04:58:34.746643", + "create_event": 59810140, + "creation_event_id": 59810034, + "creation_time": "2024-08-22 04:47:36.487052", + "draft": False, + "epoch": None, + "id": 3242053, + "name": "important-container", + "nvr": "important-container-18.0.0-27", + "owner_id": 6716, + "owner_name": "builderbot2", + "package_id": 84908, + "package_name": "important-container", + "promoter_id": None, + "promoter_name": None, + "promotion_time": None, + "release": "27", + "start_time": "2024-08-22 04:47:36.482356", + "state": 1, + "tag_id": 139665, + "tag_name": "some_release", + "task_id": 63579965, + "version": "18.0.0", + "volume_id": 0, + "volume_name": "DEFAULT" + } +] diff --git a/tests/conftest.py b/tests/conftest.py index a5d92a0..5605dc3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,13 @@ import pytest +import koji_wrapper.test_support.sample_data as test_data + + +@pytest.fixture() +def builds_for_tag(): + yield test_data.package_builds_by_tag + @pytest.fixture() def sample_build(): diff --git a/tests/unit/test_koji_tag.py b/tests/unit/test_koji_tag.py index eed176d..2babfda 100644 --- a/tests/unit/test_koji_tag.py +++ b/tests/unit/test_koji_tag.py @@ -84,7 +84,7 @@ def test_passes_builds_extra_args(sample_tagged_builds): kt = build_tag('foo') kt.session.listTagged = MagicMock(return_value=sample_tagged_builds) assert kt.builds(inherit=True) == sample_tagged_builds - kt.session.listTagged.assert_called() + kt.session.listTagged.assert_called_with('foo', inherit=True) def test_caches_builds(sample_tagged_builds): @@ -145,6 +145,21 @@ def test_filters_builds_by_both(sample_tagged_builds): assert len(filtered) == 0 +def test_selects_latest_build_by_nvr(builds_for_tag): + """ + GIVEN we have a KojiTag object filtered by tag and package + WHEN we call latest_by_nvr + THEN we get the build object with the latest nvr + """ + kt = build_tag('some_release') + kt.session.listTagged = MagicMock(return_value=builds_for_tag) + # In real usage, this is how we would narrow down the build list for the + # tested use case + assert kt.builds(package='important-container') == builds_for_tag + latest = kt.latest_by_nvr() + assert latest['nvr'] == 'important-container-18.0.0-29' + + def test_gets_attribute_for_builds_in_list(sample_tagged_builds): """ GIVEN we have a KojiTag object