Skip to content

Commit 225ca30

Browse files
authored
Refactor the ArtifactInfo class (teemtee#4526)
An effort towards resolving: teemtee#4055 (part-1) following changes made: 1. All subclasses of `ArtifactInfo` now removed 2. Updated attributs of `ArtifactInfo` to what was discussed during the artifact plugin design sync (`id`, `version`, `name`, `location`, `provider`). The `__str__` of this container represents: `<provider-id>-<nvra>` 3. Introduced `Version` object and its subclass (`RpmVersion` with its factory methods) to represent `nvra` & `nevra` construction 4. Refactored all classes (src and tests) where the above was referenced 5. Removed `ArtifactInfoT` generic since see point 1.
1 parent 887a8ac commit 225ca30

File tree

17 files changed

+343
-395
lines changed

17 files changed

+343
-395
lines changed

tests/unit/artifact/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def mock_api(method, *args, **kwargs):
4242
if method == "listBuildRPMs":
4343
return mock_rpms
4444
if method == "getBuild":
45-
return {"id": MOCK_BUILD_ID_KOJI_BREW}
45+
return {"id": MOCK_BUILD_ID_KOJI_BREW, "package_name": "test-package"}
4646
return None
4747

4848
mock_call_api.side_effect = mock_api
@@ -86,11 +86,13 @@ def mock_api(method, *args, **kwargs):
8686
return [{"build_id": MOCK_BUILD_ID_KOJI_BREW}] if has_build else []
8787
if method == "listBuildRPMs":
8888
return mock_rpms
89+
if method == "getBuild":
90+
return {"id": MOCK_BUILD_ID_KOJI_BREW, "package_name": "test-package"}
8991
if method == "getTaskDescendents":
9092
task_id = args[0] if args else kwargs.get('taskID')
9193
return {str(task_id): None, str(task_id + 1): None}
9294
if method == "listTaskOutput":
93-
return ["foo.rpm", "bar.rpm"]
95+
return ["foo-1.0-1.fc43.x86_64.rpm", "bar-2.0-1.fc43.x86_64.rpm"]
9496
return None
9597

9698
mock_call_api.side_effect = mock_api

tests/unit/artifact/conftest.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
import pytest
44

5+
import tmt.plugins
56
from tmt.steps.prepare import install
67
from tmt.steps.prepare.artifact import get_artifact_provider
78
from tmt.steps.prepare.artifact.providers import koji as koji_module
89
from tmt.steps.prepare.artifact.providers.brew import BrewArtifactProvider
9-
from tmt.steps.prepare.artifact.providers.copr_repository import CoprRepositoryProvider
1010
from tmt.steps.prepare.artifact.providers.koji import KojiArtifactProvider
1111

1212

13+
@pytest.fixture(scope='session')
14+
def _load_plugins():
15+
tmt.plugins.explore(tmt.Logger.create())
16+
17+
1318
@pytest.fixture
1419
def mock_pathinfo():
1520
mock_pathinfo = MagicMock()
@@ -23,33 +28,31 @@ def mock_koji(mock_pathinfo):
2328
mock_koji = MagicMock()
2429
mock_koji.PathInfo.return_value = mock_pathinfo
2530

31+
def mock_initialize_session(self, api_url=None, top_url=None):
32+
self._top_url = top_url or "http://koji.example.com/"
33+
self._api_url = api_url or "http://koji.example.com/kojihub"
34+
return MagicMock()
35+
2636
with (
27-
patch.object(KojiArtifactProvider, "_initialize_session", return_value=MagicMock()),
28-
patch.object(
29-
KojiArtifactProvider,
30-
"_rpm_url",
31-
side_effect=lambda rpm: f"http://koji.example.com/{rpm['name']}.rpm",
32-
),
37+
patch.object(KojiArtifactProvider, "_initialize_session", mock_initialize_session),
3338
patch.object(koji_module, "koji", mock_koji),
3439
):
3540
yield mock_koji
3641

3742

3843
@pytest.fixture
3944
def mock_brew(mock_koji):
40-
with (
41-
patch.object(BrewArtifactProvider, "_initialize_session", return_value=MagicMock()),
42-
patch.object(
43-
BrewArtifactProvider,
44-
"_rpm_url",
45-
side_effect=lambda rpm: f"http://brew.example.com/{rpm['name']}.rpm",
46-
),
47-
):
45+
def mock_initialize_session(self, api_url=None, top_url=None):
46+
self._top_url = top_url or "http://brew.example.com/"
47+
self._api_url = api_url or "http://brew.example.com/brewhub"
48+
return MagicMock()
49+
50+
with patch.object(BrewArtifactProvider, "_initialize_session", mock_initialize_session):
4851
yield mock_koji
4952

5053

5154
@pytest.fixture
52-
def artifact_provider(root_logger):
55+
def artifact_provider(root_logger, _load_plugins):
5356
def get_provider(provider_id: str, repository_priority: int = 50):
5457
provider_class = get_artifact_provider(provider_id)
5558
return provider_class(

tests/unit/artifact/test_base.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
import re
22
from unittest.mock import MagicMock
33

4-
from tmt.steps.prepare.artifact.providers import ArtifactInfo, ArtifactProvider
4+
from tmt.steps.prepare.artifact.providers import ArtifactInfo, ArtifactProvider, Version
55

66

7-
class MockArtifactInfo(ArtifactInfo):
8-
@property
9-
def id(self) -> str:
10-
return "mock.rpm"
11-
12-
@property
13-
def location(self) -> str:
14-
return "http://example.com/mock.rpm"
15-
16-
17-
class MockProvider(ArtifactProvider[MockArtifactInfo]):
7+
class MockProvider(ArtifactProvider):
188
def _extract_provider_id(self, raw_provider_id: str) -> str:
199
return raw_provider_id.split(":", 1)[1]
2010

2111
@property
2212
def artifacts(self):
23-
return [MockArtifactInfo(_raw_artifact={})]
13+
return [
14+
ArtifactInfo(
15+
version=Version(name="mock", version="1.0", release="1", arch="x86_64"),
16+
location="http://example.com/mock-1.0-1.x86_64.rpm",
17+
provider=self,
18+
)
19+
]
2420

2521
def _download_artifact(self, artifact, guest, destination):
2622
destination.write_text("ok")
@@ -43,8 +39,7 @@ def test_download_artifacts(tmp_path, root_logger):
4339
provider = MockProvider("mock:123", repository_priority=50, logger=root_logger)
4440

4541
paths = provider.fetch_contents(guest, tmp_path, [])
46-
47-
file_path = tmp_path / "mock.rpm"
42+
file_path = tmp_path / "mock-1.0-1.x86_64.rpm"
4843
assert file_path in paths
4944
assert file_path.exists()
5045
assert file_path.read_text() == "ok"

tests/unit/artifact/test_brew.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ def test_brew_valid_draft_build(mock_brew, mock_call_api, artifact_provider):
3838
for i in range(2)
3939
]
4040
mock_call_api.side_effect = (
41-
lambda method, *a, **kw: mock_rpms if method == "listBuildRPMs" else {"id": draft_id}
41+
lambda method, *a, **kw: mock_rpms
42+
if method == "listBuildRPMs"
43+
else {"id": draft_id, "package_name": "test-package"}
4244
)
4345

4446
provider = artifact_provider(f"brew.build:{draft_id}")
@@ -53,7 +55,6 @@ def test_brew_valid_task_id_scratch_build(mock_brew, mock_call_api, artifact_pro
5355

5456
provider = artifact_provider(f"brew.task:{task_id}")
5557
assert isinstance(provider, BrewTask)
56-
provider._top_url = "http://brew.example.com"
5758
tasks = list(provider._get_task_children(task_id))
5859

5960
assert len(tasks) == 2

tests/unit/artifact/test_copr_build.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_copr_valid_build_non_pulp(artifact_provider):
1515
assert not provider.is_pulp
1616

1717
for artifact in provider.artifacts:
18-
assert artifact._raw_artifact["url"].startswith(provider.result_url)
18+
assert artifact.location.startswith(provider.result_url)
1919

2020

2121
def test_copr_pulp_build(artifact_provider, monkeypatch):
@@ -29,8 +29,8 @@ def test_copr_pulp_build(artifact_provider, monkeypatch):
2929
assert len(artifacts) == len(MOCK_RPMS_PULP)
3030

3131
for artifact in artifacts:
32-
first_letter = artifact._raw_artifact["name"][0]
33-
assert f"/Packages/{first_letter}/" in artifact._raw_artifact["url"]
32+
first_letter = artifact.name[0]
33+
assert f"/Packages/{first_letter}/" in artifact.location
3434

3535

3636
def test_copr_invalid_build_id(artifact_provider):

tests/unit/artifact/test_file.py

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,20 @@
33
import pytest
44

55
from tmt.log import Logger
6-
from tmt.steps.prepare.artifact.providers.file import (
7-
PackageAsFileArtifactInfo,
8-
PackageAsFileArtifactProvider,
9-
)
6+
from tmt.steps.prepare.artifact.providers import RpmVersion
107
from tmt.utils import Path
118

129

1310
@pytest.mark.parametrize(
1411
("pattern", "expected_names"),
1512
[
16-
("foo.rpm", {"foo.rpm"}),
17-
("package-*.rpm", {f"package-{i}.rpm" for i in range(3)}),
13+
("foo-1.0-1.fc43.x86_64.rpm", {"foo-1.0-1.fc43.x86_64"}),
14+
("package-*.rpm", {f"package-{i}-1.0-1.fc43.x86_64" for i in range(3)}),
1815
],
1916
)
2017
def test_file_artifact_provider_patterns(tmp_path, pattern, expected_names, artifact_provider):
21-
# Create test files
2218
for name in expected_names:
23-
(tmp_path / name).touch()
19+
(tmp_path / f"{name}.rpm").touch()
2420

2521
provider = artifact_provider(f"file:{tmp_path}/{pattern}")
2622
artifacts = provider.artifacts
@@ -35,26 +31,65 @@ def test_file_artifact_provider_deduplicates_globs(tmp_path, caplog, artifact_pr
3531
for dirname in ("foo", "bar"):
3632
subdir = tmp_path / dirname
3733
subdir.mkdir()
38-
(subdir / "baz.rpm").touch()
34+
(subdir / "baz-1.0-1.fc43.x86_64.rpm").touch()
3935

40-
provider = artifact_provider(f"file:{tmp_path}/*/baz.rpm")
36+
provider = artifact_provider(f"file:{tmp_path}/*/baz-1.0-1.fc43.x86_64.rpm")
4137
artifacts = provider.artifacts
4238

4339
assert len(artifacts) == 1
44-
assert artifacts[0].id == "baz.rpm"
40+
assert artifacts[0].id == "baz-1.0-1.fc43.x86_64"
4541
assert "Duplicate artifact" in caplog.text
4642

4743

4844
def test_download_artifact(tmp_path, artifact_provider):
4945
# Test file download
50-
test_file = tmp_path / "foo.rpm"
46+
test_file = tmp_path / "foo-1.0-1.fc43.x86_64.rpm"
5147
test_file.touch()
5248
file_provider = artifact_provider(f"file:{test_file}")
5349
guest = MagicMock()
54-
file_provider._download_artifact(file_provider.artifacts[0], guest, Path("/remote/foo.rpm"))
50+
file_provider._download_artifact(
51+
file_provider.artifacts[0],
52+
guest,
53+
Path("/remote/foo-1.0-1.fc43.x86_64.rpm"),
54+
)
5555
guest.push.assert_called_once()
5656

5757
# Test URL download
58-
url_provider = artifact_provider("file:https://example.com/foo.rpm")
59-
url_provider._download_artifact(url_provider.artifacts[0], guest, Path("/remote/foo.rpm"))
58+
url_provider = artifact_provider("file:https://example.com/foo-1.0-1.fc43.x86_64.rpm")
59+
url_provider._download_artifact(
60+
url_provider.artifacts[0],
61+
guest,
62+
Path("/remote/foo-1.0-1.fc43.x86_64.rpm"),
63+
)
6064
guest.execute.assert_called_once()
65+
66+
67+
@pytest.mark.parametrize(
68+
("filename", "expected"),
69+
[
70+
(
71+
"bash-5.1.8-6.el9.x86_64.rpm",
72+
{
73+
"name": "bash",
74+
"epoch": 0,
75+
"version": "5.1.8",
76+
"release": "6.el9",
77+
"arch": "x86_64",
78+
},
79+
),
80+
(
81+
"tmt+export-polarion-1.61.0.dev17+gf29b2e83e-1.fc41.x86_64.rpm",
82+
{
83+
"name": "tmt+export-polarion",
84+
"epoch": 0,
85+
"version": "1.61.0.dev17+gf29b2e83e",
86+
"release": "1.fc41",
87+
"arch": "x86_64",
88+
},
89+
),
90+
],
91+
)
92+
def test_version_from_filename_parsing(filename, expected):
93+
version = RpmVersion.from_filename(filename)
94+
95+
assert vars(version) == expected

tests/unit/artifact/test_koji.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
from tmt.steps.prepare.artifact.providers import RpmVersion
34
from tmt.steps.prepare.artifact.providers.koji import (
45
KojiArtifactProvider,
56
KojiBuild,
@@ -68,3 +69,64 @@ def test_koji_valid_task_id_scratch_build(mock_koji, mock_call_api, artifact_pro
6869
assert task_id in tasks
6970
assert provider.build_id is None
7071
assert len(provider.artifacts) == 2
72+
73+
74+
@pytest.mark.parametrize(
75+
("rpm_meta", "expected"),
76+
[
77+
(
78+
{"name": "bash", "version": "5.1.8", "release": "6.el9", "arch": "x86_64"},
79+
{
80+
"name": "bash",
81+
"epoch": 0,
82+
"version": "5.1.8",
83+
"release": "6.el9",
84+
"arch": "x86_64",
85+
"nvra": "bash-5.1.8-6.el9.x86_64",
86+
},
87+
),
88+
(
89+
{
90+
"name": "docker-ce",
91+
"version": "20.10.7",
92+
"release": "3.el8",
93+
"arch": "x86_64",
94+
"epoch": 1,
95+
},
96+
{
97+
"name": "docker-ce",
98+
"epoch": 1,
99+
"version": "20.10.7",
100+
"release": "3.el8",
101+
"arch": "x86_64",
102+
"nvra": "docker-ce-20.10.7-3.el8.x86_64",
103+
},
104+
),
105+
(
106+
{
107+
"name": "tmt+export-polarion",
108+
"version": "1.61.0.dev17+gf29b2e83e",
109+
"release": "1.fc41",
110+
"arch": "x86_64",
111+
},
112+
{
113+
"name": "tmt+export-polarion",
114+
"epoch": 0,
115+
"version": "1.61.0.dev17+gf29b2e83e",
116+
"release": "1.fc41",
117+
"arch": "x86_64",
118+
"nvra": "tmt+export-polarion-1.61.0.dev17+gf29b2e83e-1.fc41.x86_64",
119+
},
120+
),
121+
],
122+
)
123+
def test_version_from_rpm_meta_parsing(rpm_meta, expected):
124+
version = RpmVersion.from_rpm_meta(rpm_meta)
125+
126+
assert version.name == expected["name"]
127+
assert version.epoch == expected["epoch"]
128+
assert version.version == expected["version"]
129+
assert version.release == expected["release"]
130+
assert version.arch == expected["arch"]
131+
assert version.nvra == expected["nvra"]
132+
assert str(version) == expected["nvra"]

0 commit comments

Comments
 (0)