2016-02-01 09:50:28 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2018-10-11 08:07:35 +00:00
|
|
|
import difflib
|
|
|
|
import errno
|
2016-02-26 09:31:43 +00:00
|
|
|
import os
|
2018-10-11 08:07:35 +00:00
|
|
|
import shutil
|
|
|
|
import tempfile
|
|
|
|
from collections import defaultdict
|
|
|
|
|
2021-02-12 11:50:59 +00:00
|
|
|
from unittest import mock
|
2018-10-11 08:07:35 +00:00
|
|
|
import six
|
|
|
|
from kobo.rpmlib import parse_nvr
|
|
|
|
|
2016-05-25 11:39:02 +00:00
|
|
|
try:
|
|
|
|
import unittest2 as unittest
|
|
|
|
except ImportError:
|
|
|
|
import unittest
|
2016-02-01 09:50:28 +00:00
|
|
|
|
2017-04-27 13:21:36 +00:00
|
|
|
from pungi.util import get_arch_variant_data
|
2019-10-02 07:40:18 +00:00
|
|
|
from pungi import paths, checks
|
|
|
|
from pungi.module_util import Modulemd
|
2016-02-11 14:15:36 +00:00
|
|
|
|
|
|
|
|
2023-06-14 11:08:31 +00:00
|
|
|
GIT_WITH_CREDS = [
|
|
|
|
"git",
|
|
|
|
"-c",
|
|
|
|
"credential.useHttpPath=true",
|
|
|
|
"-c",
|
|
|
|
"credential.helper=!ch",
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2018-10-11 08:07:35 +00:00
|
|
|
class BaseTestCase(unittest.TestCase):
|
|
|
|
def assertFilesEqual(self, fn1, fn2):
|
2020-01-22 10:02:22 +00:00
|
|
|
with open(fn1, "rb") as f1:
|
|
|
|
lines1 = f1.read().decode("utf-8").splitlines()
|
|
|
|
with open(fn2, "rb") as f2:
|
|
|
|
lines2 = f2.read().decode("utf-8").splitlines()
|
|
|
|
diff = "\n".join(
|
|
|
|
difflib.unified_diff(lines1, lines2, fromfile="EXPECTED", tofile="ACTUAL")
|
|
|
|
)
|
|
|
|
self.assertEqual(diff, "", "Files differ:\n" + diff)
|
2018-10-11 08:07:35 +00:00
|
|
|
|
2018-10-12 12:50:46 +00:00
|
|
|
def assertFileContent(self, fn, expected):
|
2020-01-22 10:02:22 +00:00
|
|
|
with open(fn, "rb") as f:
|
|
|
|
lines = f.read().decode("utf-8").splitlines()
|
|
|
|
diff = "\n".join(
|
|
|
|
difflib.unified_diff(
|
|
|
|
lines, expected.splitlines(), fromfile="EXPECTED", tofile="ACTUAL"
|
|
|
|
)
|
2018-10-12 12:50:46 +00:00
|
|
|
)
|
2020-01-22 10:02:22 +00:00
|
|
|
self.assertEqual(diff, "", "Files differ:\n" + diff)
|
2018-10-12 12:50:46 +00:00
|
|
|
|
2018-10-11 08:07:35 +00:00
|
|
|
|
|
|
|
class PungiTestCase(BaseTestCase):
|
2016-02-11 14:15:36 +00:00
|
|
|
def setUp(self):
|
|
|
|
self.topdir = tempfile.mkdtemp()
|
|
|
|
|
|
|
|
def tearDown(self):
|
2016-04-05 06:40:54 +00:00
|
|
|
try:
|
|
|
|
shutil.rmtree(self.topdir)
|
|
|
|
except OSError as err:
|
|
|
|
if err.errno != errno.ENOENT:
|
|
|
|
raise
|
2016-02-01 09:50:28 +00:00
|
|
|
|
2016-12-07 14:57:35 +00:00
|
|
|
def assertValidConfig(self, conf):
|
2018-11-21 09:56:22 +00:00
|
|
|
self.assertEqual(checks.validate(conf, offline=True), ([], []))
|
2016-12-07 14:57:35 +00:00
|
|
|
|
2019-08-01 14:17:39 +00:00
|
|
|
def _make_pkgset_phase(self, names):
|
|
|
|
pkgsets = []
|
|
|
|
for name in names:
|
|
|
|
pkgset = mock.Mock(paths={})
|
|
|
|
pkgset.name = name
|
|
|
|
for arch in ("x86_64", "amd64"):
|
|
|
|
pkgset.paths[arch] = os.path.join(
|
|
|
|
self.topdir, "work", arch, "repo", name
|
|
|
|
)
|
|
|
|
pkgsets.append(pkgset)
|
|
|
|
return mock.Mock(package_sets=pkgsets)
|
|
|
|
|
2016-02-01 09:50:28 +00:00
|
|
|
|
2016-11-23 08:54:34 +00:00
|
|
|
class MockVariant(mock.Mock):
|
2018-07-19 12:47:06 +00:00
|
|
|
def __init__(self, is_empty=False, name=None, *args, **kwargs):
|
2017-04-26 11:54:00 +00:00
|
|
|
super(MockVariant, self).__init__(*args, is_empty=is_empty, **kwargs)
|
2020-01-22 10:02:22 +00:00
|
|
|
self.parent = kwargs.get("parent", None)
|
2017-05-04 18:46:06 +00:00
|
|
|
self.arch_mmds = {}
|
2018-03-20 07:52:49 +00:00
|
|
|
self.module_uid_to_koji_tag = {}
|
2017-04-26 11:54:00 +00:00
|
|
|
self.variants = {}
|
2019-07-26 14:06:53 +00:00
|
|
|
self.pkgsets = set()
|
2018-03-14 07:17:50 +00:00
|
|
|
self.modules = None
|
2022-11-03 10:05:53 +00:00
|
|
|
self.modular_koji_tags = None
|
2018-07-19 12:47:06 +00:00
|
|
|
self.name = name
|
2018-10-01 13:38:14 +00:00
|
|
|
self.nsvc_to_pkgset = defaultdict(lambda: mock.Mock(rpms_by_arch={}))
|
2017-01-26 08:44:45 +00:00
|
|
|
|
2016-11-23 08:54:34 +00:00
|
|
|
def __str__(self):
|
|
|
|
return self.uid
|
|
|
|
|
2017-04-26 11:54:00 +00:00
|
|
|
def get_variants(self, arch=None, types=None):
|
2020-01-22 10:02:22 +00:00
|
|
|
return [
|
|
|
|
v
|
|
|
|
for v in list(self.variants.values())
|
|
|
|
if (not arch or arch in v.arches) and (not types or v.type in types)
|
|
|
|
]
|
2017-04-26 11:54:00 +00:00
|
|
|
|
2017-08-30 08:21:41 +00:00
|
|
|
def get_modules(self, arch=None, types=None):
|
|
|
|
return []
|
|
|
|
|
2018-03-20 09:37:43 +00:00
|
|
|
def get_modular_koji_tags(self, arch=None, types=None):
|
|
|
|
return []
|
|
|
|
|
2019-05-10 13:37:14 +00:00
|
|
|
def add_fake_module(self, nsvc, rpm_nvrs=None, with_artifacts=False, mmd_arch=None):
|
2018-03-16 06:15:01 +00:00
|
|
|
if not Modulemd:
|
|
|
|
# No support for modules
|
2018-03-13 13:29:13 +00:00
|
|
|
return
|
|
|
|
name, stream, version, context = nsvc.split(":")
|
2019-07-01 10:11:39 +00:00
|
|
|
module_stream = Modulemd.ModuleStreamV2.new(name, stream)
|
|
|
|
module_stream.set_version(int(version))
|
|
|
|
module_stream.set_context(context)
|
|
|
|
module_stream.set_summary("foo")
|
|
|
|
module_stream.set_description("foo")
|
|
|
|
module_stream.add_module_license("GPL")
|
2018-03-13 13:29:13 +00:00
|
|
|
|
|
|
|
if rpm_nvrs:
|
|
|
|
for rpm_nvr in rpm_nvrs:
|
|
|
|
rpm_name = parse_nvr(rpm_nvr)["name"]
|
2019-07-01 10:11:39 +00:00
|
|
|
component = Modulemd.ComponentRpm.new(rpm_nvr)
|
2018-03-13 13:29:13 +00:00
|
|
|
component.set_name(rpm_name)
|
|
|
|
component.set_rationale("Needed for test")
|
2019-07-01 10:11:39 +00:00
|
|
|
module_stream.add_component(component)
|
|
|
|
if with_artifacts:
|
|
|
|
module_stream.add_rpm_artifact(rpm_nvr)
|
2018-03-13 13:29:13 +00:00
|
|
|
|
|
|
|
if self.modules is None:
|
|
|
|
self.modules = []
|
|
|
|
self.modules.append(":".join([name, stream, version]))
|
2019-05-10 13:37:14 +00:00
|
|
|
if mmd_arch:
|
2019-07-01 10:11:39 +00:00
|
|
|
nsvc = module_stream.get_nsvc()
|
|
|
|
self.arch_mmds.setdefault(mmd_arch, {})[nsvc] = module_stream
|
|
|
|
return module_stream
|
2018-03-13 13:29:13 +00:00
|
|
|
|
2016-11-23 08:54:34 +00:00
|
|
|
|
2020-04-01 08:00:48 +00:00
|
|
|
class MockPackageSet(dict):
|
|
|
|
def __init__(self, *args):
|
|
|
|
for pkg in args:
|
|
|
|
self[pkg.path] = pkg
|
|
|
|
|
|
|
|
|
|
|
|
class MockPkg(object):
|
|
|
|
def __init__(self, path, is_system_release=False, **kwargs):
|
|
|
|
self.path = path
|
|
|
|
self.is_system_release = is_system_release
|
|
|
|
filename = os.path.basename(path)
|
|
|
|
self.nvr, self.arch, _ = filename.rsplit(".", 2)
|
|
|
|
self.name, self.version, self.release = self.nvr.rsplit("-", 2)
|
|
|
|
for k, v in kwargs.items():
|
|
|
|
setattr(self, k, v)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return self.nvr
|
|
|
|
|
|
|
|
def __lt__(self, another):
|
|
|
|
return self.nvr < another.nvr
|
|
|
|
|
|
|
|
|
2017-02-22 02:48:05 +00:00
|
|
|
class IterableMock(mock.Mock):
|
|
|
|
def __iter__(self):
|
|
|
|
return iter([])
|
|
|
|
|
|
|
|
|
2016-02-22 14:58:34 +00:00
|
|
|
class DummyCompose(object):
|
2016-02-11 14:15:36 +00:00
|
|
|
def __init__(self, topdir, config):
|
2016-02-11 15:45:56 +00:00
|
|
|
self.supported = True
|
2020-01-22 10:02:22 +00:00
|
|
|
self.compose_date = "20151203"
|
|
|
|
self.compose_type_suffix = ".t"
|
|
|
|
self.compose_type = "test"
|
2016-02-01 09:50:28 +00:00
|
|
|
self.compose_respin = 0
|
2020-01-22 10:02:22 +00:00
|
|
|
self.compose_id = "Test-20151203.0.t"
|
2016-03-16 07:33:43 +00:00
|
|
|
self.compose_label = None
|
2016-04-04 07:38:48 +00:00
|
|
|
self.compose_label_major_version = None
|
2020-01-22 10:02:22 +00:00
|
|
|
self.image_release = "20151203.t.0"
|
|
|
|
self.image_version = "25"
|
2016-02-01 09:50:28 +00:00
|
|
|
self.ci_base = mock.Mock(
|
2020-01-22 10:02:22 +00:00
|
|
|
release_id="Test-1.0",
|
2016-02-01 09:50:28 +00:00
|
|
|
release=mock.Mock(
|
2020-01-22 10:02:22 +00:00
|
|
|
short="test", version="1.0", is_layered=False, type_suffix=""
|
2016-02-01 09:50:28 +00:00
|
|
|
),
|
|
|
|
)
|
2016-02-11 14:15:36 +00:00
|
|
|
self.topdir = topdir
|
2016-08-22 14:08:25 +00:00
|
|
|
self.conf = load_config(PKGSET_REPOS, **config)
|
2018-11-21 09:56:22 +00:00
|
|
|
checks.validate(self.conf, offline=True)
|
2016-02-11 14:15:36 +00:00
|
|
|
self.paths = paths.Paths(self)
|
2018-04-17 12:49:46 +00:00
|
|
|
self.has_comps = True
|
2016-02-01 09:50:28 +00:00
|
|
|
self.variants = {
|
2020-01-22 10:02:22 +00:00
|
|
|
"Server": MockVariant(
|
|
|
|
uid="Server",
|
|
|
|
arches=["x86_64", "amd64"],
|
|
|
|
type="variant",
|
|
|
|
id="Server",
|
|
|
|
name="Server",
|
|
|
|
),
|
|
|
|
"Client": MockVariant(
|
|
|
|
uid="Client",
|
|
|
|
arches=["amd64"],
|
|
|
|
type="variant",
|
|
|
|
id="Client",
|
|
|
|
name="Client",
|
|
|
|
),
|
|
|
|
"Everything": MockVariant(
|
|
|
|
uid="Everything",
|
|
|
|
arches=["x86_64", "amd64"],
|
|
|
|
type="variant",
|
|
|
|
id="Everything",
|
|
|
|
name="Everything",
|
|
|
|
),
|
2016-02-01 09:50:28 +00:00
|
|
|
}
|
2016-11-09 09:20:55 +00:00
|
|
|
self.all_variants = self.variants.copy()
|
2016-11-18 20:33:03 +00:00
|
|
|
|
|
|
|
# for PhaseLoggerMixin
|
2018-11-14 14:41:36 +00:00
|
|
|
self._logger = mock.Mock(name="compose._logger")
|
2016-11-18 20:33:03 +00:00
|
|
|
self._logger.handlers = [mock.Mock()]
|
|
|
|
|
2016-02-26 09:31:43 +00:00
|
|
|
self.log_info = mock.Mock()
|
2016-02-01 09:50:28 +00:00
|
|
|
self.log_error = mock.Mock()
|
2016-02-11 15:45:56 +00:00
|
|
|
self.log_debug = mock.Mock()
|
2016-02-29 08:21:49 +00:00
|
|
|
self.log_warning = mock.Mock()
|
2020-01-22 10:02:22 +00:00
|
|
|
self.get_image_name = mock.Mock(return_value="image-name")
|
2019-02-12 11:09:08 +00:00
|
|
|
self.image = mock.Mock(
|
2021-03-02 10:19:05 +00:00
|
|
|
path="Client/i386/iso/image.iso",
|
|
|
|
can_fail=False,
|
|
|
|
size=123,
|
|
|
|
_max_size=None,
|
2019-02-12 11:09:08 +00:00
|
|
|
)
|
2020-01-22 10:02:22 +00:00
|
|
|
self.im = mock.Mock(images={"Client": {"amd64": [self.image]}})
|
2016-02-29 11:58:21 +00:00
|
|
|
self.old_composes = []
|
2020-01-22 10:02:22 +00:00
|
|
|
self.config_dir = "/home/releng/config"
|
2016-04-06 08:41:07 +00:00
|
|
|
self.notifier = None
|
2016-04-13 11:44:17 +00:00
|
|
|
self.attempt_deliverable = mock.Mock()
|
|
|
|
self.fail_deliverable = mock.Mock()
|
|
|
|
self.require_deliverable = mock.Mock()
|
2018-05-18 08:17:41 +00:00
|
|
|
self.should_create_yum_database = True
|
2018-08-15 07:20:40 +00:00
|
|
|
self.cache_region = None
|
2021-02-08 10:42:07 +00:00
|
|
|
self.containers_metadata = {}
|
2021-08-06 12:02:54 +00:00
|
|
|
self.load_old_compose_config = mock.Mock(return_value=None)
|
2016-02-01 09:50:28 +00:00
|
|
|
|
2016-11-09 12:39:01 +00:00
|
|
|
def setup_optional(self):
|
2020-01-22 10:02:22 +00:00
|
|
|
self.all_variants["Server-optional"] = MockVariant(
|
|
|
|
uid="Server-optional", arches=["x86_64"], type="optional"
|
|
|
|
)
|
|
|
|
self.all_variants["Server-optional"].parent = self.variants["Server"]
|
|
|
|
self.variants["Server"].variants["optional"] = self.all_variants[
|
|
|
|
"Server-optional"
|
|
|
|
]
|
2017-04-26 11:54:00 +00:00
|
|
|
|
|
|
|
def setup_addon(self):
|
2020-01-22 10:02:22 +00:00
|
|
|
self.all_variants["Server-HA"] = MockVariant(
|
|
|
|
uid="Server-HA", arches=["x86_64"], type="addon", is_empty=False
|
|
|
|
)
|
|
|
|
self.all_variants["Server-HA"].parent = self.variants["Server"]
|
|
|
|
self.variants["Server"].variants["HA"] = self.all_variants["Server-HA"]
|
2016-11-09 12:39:01 +00:00
|
|
|
|
|
|
|
def get_variants(self, arch=None, types=None):
|
2020-01-22 10:02:22 +00:00
|
|
|
return [
|
|
|
|
v
|
|
|
|
for v in list(self.all_variants.values())
|
|
|
|
if (not arch or arch in v.arches) and (not types or v.type in types)
|
|
|
|
]
|
2016-02-01 09:50:28 +00:00
|
|
|
|
|
|
|
def can_fail(self, variant, arch, deliverable):
|
2020-01-22 10:02:22 +00:00
|
|
|
failable = get_arch_variant_data(
|
|
|
|
self.conf, "failable_deliverables", arch, variant
|
|
|
|
)
|
2016-02-01 09:50:28 +00:00
|
|
|
return deliverable in failable
|
2016-02-11 15:45:56 +00:00
|
|
|
|
|
|
|
def get_arches(self):
|
|
|
|
result = set()
|
2017-09-05 08:01:21 +00:00
|
|
|
for variant in list(self.variants.values()):
|
2016-02-11 15:45:56 +00:00
|
|
|
result |= set(variant.arches)
|
2016-05-12 11:41:53 +00:00
|
|
|
return sorted(result)
|
2016-02-26 09:31:43 +00:00
|
|
|
|
2017-01-09 07:40:24 +00:00
|
|
|
def mkdtemp(self, suffix="", prefix="tmp"):
|
2017-04-27 13:21:36 +00:00
|
|
|
return tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=self.topdir)
|
2017-01-09 07:40:24 +00:00
|
|
|
|
2016-02-26 09:31:43 +00:00
|
|
|
|
2023-05-16 14:17:34 +00:00
|
|
|
def touch(path, content=None, mode=None):
|
2016-02-26 09:31:43 +00:00
|
|
|
"""Helper utility that creates an dummy file in given location. Directories
|
|
|
|
will be created."""
|
2020-01-22 10:02:22 +00:00
|
|
|
content = content or (path + "\n")
|
2016-02-26 09:31:43 +00:00
|
|
|
try:
|
|
|
|
os.makedirs(os.path.dirname(path))
|
|
|
|
except OSError:
|
|
|
|
pass
|
2017-09-05 08:01:21 +00:00
|
|
|
if not isinstance(content, six.binary_type):
|
|
|
|
content = content.encode()
|
2020-01-22 10:02:22 +00:00
|
|
|
with open(path, "wb") as f:
|
2016-02-29 12:35:55 +00:00
|
|
|
f.write(content)
|
2023-05-16 14:17:34 +00:00
|
|
|
if mode:
|
|
|
|
os.chmod(path, mode)
|
2016-10-12 13:42:22 +00:00
|
|
|
return path
|
2016-02-29 08:21:49 +00:00
|
|
|
|
|
|
|
|
2020-01-22 10:02:22 +00:00
|
|
|
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixtures")
|
2016-04-22 09:15:06 +00:00
|
|
|
|
|
|
|
|
2016-02-29 08:21:49 +00:00
|
|
|
def copy_fixture(fixture_name, dest):
|
2016-04-22 09:15:06 +00:00
|
|
|
src = os.path.join(FIXTURE_DIR, fixture_name)
|
2016-02-29 08:21:49 +00:00
|
|
|
touch(dest)
|
|
|
|
shutil.copy2(src, dest)
|
2016-03-10 12:22:11 +00:00
|
|
|
|
|
|
|
|
2016-03-23 09:40:16 +00:00
|
|
|
def boom(*args, **kwargs):
|
2020-01-22 10:02:22 +00:00
|
|
|
raise Exception("BOOM")
|
2016-08-22 14:08:25 +00:00
|
|
|
|
|
|
|
|
2020-01-22 10:02:22 +00:00
|
|
|
def mk_boom(cls=Exception, msg="BOOM"):
|
2017-07-27 09:59:03 +00:00
|
|
|
def b(*args, **kwargs):
|
|
|
|
raise cls(msg)
|
2020-01-22 10:02:22 +00:00
|
|
|
|
2017-07-27 09:59:03 +00:00
|
|
|
return b
|
|
|
|
|
|
|
|
|
2021-03-02 10:19:05 +00:00
|
|
|
PKGSET_REPOS = dict(
|
|
|
|
pkgset_source="repos",
|
|
|
|
pkgset_repos={},
|
|
|
|
)
|
2016-08-22 14:08:25 +00:00
|
|
|
|
|
|
|
BASE_CONFIG = dict(
|
2020-01-22 10:02:22 +00:00
|
|
|
release_short="test",
|
|
|
|
release_name="Test",
|
|
|
|
release_version="1.0",
|
|
|
|
variants_file="variants.xml",
|
|
|
|
createrepo_checksum="sha256",
|
|
|
|
gather_method="deps",
|
2016-08-22 14:08:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def load_config(data={}, **kwargs):
|
|
|
|
conf = dict()
|
|
|
|
conf.update(BASE_CONFIG)
|
|
|
|
conf.update(data)
|
|
|
|
conf.update(kwargs)
|
|
|
|
return conf
|
2017-09-11 06:15:34 +00:00
|
|
|
|
|
|
|
|
2019-08-02 08:30:09 +00:00
|
|
|
def fake_run_in_threads(func, params, threads=None):
|
|
|
|
"""Like run_in_threads from Kobo, but actually runs tasks serially."""
|
|
|
|
for num, param in enumerate(params):
|
|
|
|
func(None, param, num)
|