debian-forge/test/mod/test_fmt_v2.py
Christian Kellner c25857020d test/fmt_v2: add simple check for input references
Specifically this test checks that the order given in the manifest is
preserved when loaded, i.e. the internal dict has the keys ordered in
the same way, independently in which way they were specified -- list
or object.
2022-04-21 16:39:58 +02:00

441 lines
13 KiB
Python

#
# Tests specific for version 2 of the format
#
import copy
import itertools
import os
import unittest
import osbuild
import osbuild.meta
BASIC_PIPELINE = {
"version": "2",
"sources": {
"org.osbuild.curl": {
"items": {
"sha256:6eeebf21f245bf0d6f58962dc49b6dfb51f55acb6a595c6b9cbe9628806b80a4":
"https://internet/curl-7.69.1-1.fc32.x86_64.rpm",
}
},
"org.osbuild.ostree": {
"items": {
"439911411ce7868a7b058c2a660e421991eb2df10e2bdce1fa559bd4390105d1": {
"remote": {
"url": "file:///repo",
"gpgkeys": ["data"]
}
}
}
}
},
"pipelines": [
{
"name": "build",
"runner": "org.osbuild.linux",
"stages": [
{
"type": "org.osbuild.noop",
"options": {"zero": 0}
}
]
},
{
"name": "tree",
"build": "name:build",
"stages": [
{
"type": "org.osbuild.noop",
"options": {"one": 1}
}
]
},
{
"name": "assembler",
"build": "name:build",
"stages": [
{
"type": "org.osbuild.noop",
"options": {"one": 3},
"inputs": {
"tree": {
"type": "org.osbuild.tree",
"origin": "org.osbuild.pipeline",
"references": {
"name:tree": {}
}
}
},
"devices": {
"root": {
"type": "org.osbuild.loopback",
"options": {
"filename": "empty.img"
}
},
"boot": {
"type": "org.osbuild.loopback",
"options": {
"filename": "empty.img"
}
},
},
"mounts": [
{
"name": "root",
"type": "org.osbuild.noop",
"source": "root",
"target": "/",
},
{
"name": "boot",
"type": "org.osbuild.noop",
"source": "boot",
"target": "/boot",
}
]
}
]
}
]
}
BAD_SHA = "sha256:15a654d32efaa75b5df3e2481939d0393fe1746696cc858ca094ccf8b76073cd"
BAD_REF_PIPELINE = {
"version": "2",
"sources": {
"org.osbuild.curl": {
"items": {
"sha256:c540ca8c5e21ba5f063286c94a088af2aac0b15bc40df6fd562d40154c10f4a1": "",
}
}
},
"pipelines": [
{
"name": "build",
"stages": [
{
"type": "org.osbuild.rpm",
"inputs": {
"packages": {
"type": "org.osbuild.files",
"origin": "org.osbuild.source",
"references": {
BAD_SHA: {}
}
}
}
}
]
}
]
}
INPUT_REFERENCES = {
"version": "2",
"sources": {
"org.osbuild.curl": {
"items": {
"sha256:6eeebf21f245bf0d6f58962dc49b6dfb51f55acb6a595c6b9cbe9628806b80a4":
"https://internet/curl-7.69.1-1.fc32.x86_64.rpm",
"sha256:184a0c274d4efa84a2f6d0a128aae87e2fa231fe9067b4a4dc8f886fa6f1dc18":
"https://internet/kernel-5.11.12-300.fc34.x86_64.rpm"
}
},
},
"pipelines": [
{
"name": "os",
"stages": [
{
"type": "org.osbuild.rpm",
"inputs": {
"packages": {
"type": "org.osbuild.files",
"origin": "org.osbuild.source",
"references": [
"sha256:6eeebf21f245bf0d6f58962dc49b6dfb51f55acb6a595c6b9cbe9628806b80a4",
"sha256:184a0c274d4efa84a2f6d0a128aae87e2fa231fe9067b4a4dc8f886fa6f1dc18"
]
}
}
}
],
},
]
}
class TestFormatV2(unittest.TestCase):
def setUp(self):
self.index = osbuild.meta.Index(os.curdir)
self.maxDiff = None
def load_manifest(self, desc):
info = self.index.detect_format_info(desc)
self.assertIsNotNone(info)
fmt = info.module
self.assertIsNotNone(fmt)
manifest = fmt.load(desc, self.index)
return manifest, fmt
def assert_validation(self, result):
if result.valid:
return
msg = "Validation failed:\n"
msg += "\n".join(str(e) for e in result.errors)
self.fail(msg)
def test_load(self):
desc = BASIC_PIPELINE
info = self.index.detect_format_info(desc)
assert info, "Failed to detect format"
fmt = info.module
self.assertEqual(fmt.VERSION, "2")
manifest = fmt.load(desc, self.index)
self.assertIsNotNone(manifest)
self.assertTrue(manifest.pipelines)
self.assertTrue(len(manifest.pipelines) == 3)
build = manifest["build"]
self.assertIsNotNone(build)
tree = manifest["tree"]
self.assertIsNotNone(tree)
self.assertIsNotNone(tree.build)
self.assertEqual(tree.build, build.id)
self.assertEqual(tree.runner, "org.osbuild.linux")
assembler = manifest["assembler"]
self.assertIsNotNone(assembler)
self.assertIsNotNone(assembler.build)
self.assertEqual(assembler.build, build.id)
self.assertEqual(assembler.runner, "org.osbuild.linux")
def test_format_info(self):
index = self.index
lst = index.list_formats()
self.assertIn("osbuild.formats.v2", lst)
# the basic test manifest
info = index.detect_format_info(BASIC_PIPELINE)
self.assertEqual(info.version, "2")
def test_describe(self):
manifest, fmt = self.load_manifest(BASIC_PIPELINE)
desc = fmt.describe(manifest)
self.assertIsNotNone(desc)
self.assertEqual(BASIC_PIPELINE, desc)
def test_validation(self):
desc = BASIC_PIPELINE
_, fmt = self.load_manifest(desc)
res = fmt.validate(desc, self.index)
self.assert_validation(res)
def test_load_bad_ref_manifest(self):
desc = BAD_REF_PIPELINE
info = self.index.detect_format_info(desc)
self.assertIsNotNone(info)
fmt = info.module
self.assertIsNotNone(fmt)
with self.assertRaises(ValueError) as ex:
fmt.load(desc, self.index)
self.assertTrue(str(ex.exception).find(BAD_SHA) > -1,
"The unknown source reference is not included in the exception")
def test_mounts(self):
BASE = {
"version": "2",
"pipelines": [
{
"name": "test",
"runner": "org.osbuild.linux",
"stages": [
{
"type": "org.osbuild.noop",
"options": {"zero": 0},
"devices": {
"root": {
"type": "org.osbuild.loopback",
"options": {
"filename": "empty.img"
}
}
},
"mounts": []
}
]
}
]
}
# verify the device
pipeline = copy.deepcopy(BASE)
stage = pipeline["pipelines"][0]["stages"][0]
mounts = stage["mounts"]
mounts.extend([{
"name": "root",
"type": "org.osbuild.noop",
"source": "root",
"target": "/",
}])
manifest, _ = self.load_manifest(pipeline)
self.assertIsNotNone(manifest)
test = manifest["test"]
self.assertIsNotNone(test)
stage = test.stages[0]
root = stage.mounts["root"]
self.assertIsNotNone(root)
self.assertIsNotNone(root.device)
self.assertEqual(root.device.name, "root")
# duplicated mount
pipeline = copy.deepcopy(BASE)
stage = pipeline["pipelines"][0]["stages"][0]
mounts = stage["mounts"]
mounts.extend([{
"name": "root",
"type": "org.osbuild.noop",
"source": "root",
"target": "/",
}, {
"name": "root",
"type": "org.osbuild.noop",
"source": "root",
"target": "/",
}])
with self.assertRaises(ValueError):
self.load_manifest(pipeline)
# mount without a device
pipeline = copy.deepcopy(BASE)
stage = pipeline["pipelines"][0]["stages"][0]
mounts = stage["mounts"]
mounts.extend([{
"name": "boot",
"type": "org.osbuild.noop",
"source": "boot",
"target": "/boot",
}])
with self.assertRaises(ValueError):
self.load_manifest(pipeline)
def test_device_sorting(self):
fmt = self.index.get_format_info("osbuild.formats.v2").module
assert(fmt)
self_cycle = {
"a": {"parent": "a"},
}
with self.assertRaises(ValueError):
fmt.sort_devices(self_cycle)
cycle = {
"a": {"parent": "b"},
"b": {"parent": "a"},
}
with self.assertRaises(ValueError):
fmt.sort_devices(cycle)
missing_parent = {
"a": {"parent": "b"},
"b": {"parent": "c"},
}
with self.assertRaises(ValueError):
fmt.sort_devices(missing_parent)
def ensure_sorted(devices):
check = {}
for name, dev in devices.items():
parent = dev.get("parent")
if parent:
assert parent in check
check[name] = dev
assert devices == check
devices = {
"a": {"parent": "d"},
"b": {"parent": "a"},
"c": {"parent": None},
"d": {"parent": "c"},
}
for check in itertools.permutations(devices.keys()):
before = {name: devices[name] for name in check}
ensure_sorted(fmt.sort_devices(before))
def check_input_references(self, desc):
info = self.index.detect_format_info(desc)
assert info, "Failed to detect format"
fmt = info.module
self.assertEqual(fmt.VERSION, "2")
res = fmt.validate(desc, self.index)
self.assert_validation(res)
manifest = fmt.load(desc, self.index)
self.assertIsNotNone(manifest)
pl = manifest.get("os")
assert pl is not None
packages = pl.stages[0].inputs["packages"]
assert packages is not None
assert len(packages.refs) == 2
refs = [
"sha256:6eeebf21f245bf0d6f58962dc49b6dfb51f55acb6a595c6b9cbe9628806b80a4",
"sha256:184a0c274d4efa84a2f6d0a128aae87e2fa231fe9067b4a4dc8f886fa6f1dc18"
]
keys = list(packages.refs.keys())
assert keys == refs
def test_input_references(self):
# assert that the input references are ordered properly, i.e.
# their order is preserved as specified in the manifest
desc = INPUT_REFERENCES
self.check_input_references(desc)
inputs = desc["pipelines"][0]["stages"][0]["inputs"]["packages"]
refs = inputs["references"]
# check references as maps
inputs["references"] = {
k: {} for k in refs
}
self.check_input_references(desc)