debian-forge/test/mod/test_osbuild.py
Christian Kellner d25936a028 formats: describe now takes a manifest
Instead of a pipeline, describe now takes a Manifest instance.
The reason is that a manifest fully describes the build, which
includes the sources. Now that the describe function takes the
manifest, the sources can be included as well.
Adapt the tests to refelect that change.
2021-01-09 18:09:47 +01:00

262 lines
8.4 KiB
Python

#
# Basic tests for a collection of osbuild modules.
#
import json
import os
import pathlib
import sys
import tempfile
import unittest
import osbuild
import osbuild.meta
from osbuild.formats import v1 as fmt
from osbuild.monitor import NullMonitor
from osbuild.pipeline import detect_host_runner
from .. import test
class TestDescriptions(unittest.TestCase):
def test_canonical(self):
"""Degenerate case. Make sure we always return the same canonical
description when passing empty or null values."""
cases = [
{},
{"assembler": None},
{"stages": []},
{"build": {}},
{"build": None}
]
for pipeline in cases:
manifest = {"pipeline": pipeline}
with self.subTest(pipeline):
desc = fmt.describe(fmt.load(manifest))
self.assertEqual(desc["pipeline"], {})
@unittest.skipUnless(test.TestBase.can_bind_mount(), "root-only")
def test_stage_run(self):
stage = osbuild.Stage("org.osbuild.noop", {}, None, None, {})
with tempfile.TemporaryDirectory() as tmpdir:
data = pathlib.Path(tmpdir, "data")
cache = pathlib.Path(tmpdir, "cache")
root = pathlib.Path("/")
runner = detect_host_runner()
monitor = NullMonitor(sys.stderr.fileno())
libdir = os.path.abspath(os.curdir)
for p in [data, cache]:
p.mkdir()
res = stage.run(data, runner, root, cache, monitor, libdir)
self.assertEqual(res.success, True)
self.assertEqual(res.id, stage.id)
@unittest.skipUnless(test.TestBase.can_bind_mount(), "root-only")
def test_assembler_run(self):
asm = osbuild.Assembler("org.osbuild.noop", None, None, {})
with tempfile.TemporaryDirectory() as tmpdir:
data = pathlib.Path(tmpdir, "data")
cache = pathlib.Path(tmpdir, "cache")
output = pathlib.Path(tmpdir, "output")
root = pathlib.Path("/")
runner = detect_host_runner()
monitor = NullMonitor(sys.stderr.fileno())
libdir = os.path.abspath(os.curdir)
for p in [data, cache, output]:
p.mkdir()
res = asm.run(data, runner, root, monitor, libdir, output)
self.assertEqual(res.success, True)
self.assertEqual(res.id, asm.id)
def test_pipeline(self):
build = osbuild.Pipeline("org.osbuild.test")
build.add_stage("org.osbuild.test", {}, {"one": 1})
pipeline = osbuild.Pipeline("org.osbuild.test", build)
pipeline.add_stage("org.osbuild.test", {}, {"one": 2})
pipeline.set_assembler("org.osbuild.test")
manifest = osbuild.Manifest(pipeline, {})
self.assertEqual(fmt.describe(manifest), {
"pipeline": {
"build": {
"pipeline": {
"stages": [
{
"name": "org.osbuild.test",
"options": {"one": 1}
}
]
},
"runner": "org.osbuild.test"
},
"stages": [
{
"name": "org.osbuild.test",
"options": {"one": 2}
}
],
"assembler": {
"name": "org.osbuild.test"
}
}
})
def test_moduleinfo(self):
index = osbuild.meta.Index(os.curdir)
modules = []
for klass in ("Stage", "Assembler", "Source"):
mods = index.list_modules_for_class(klass)
modules += [(klass, module) for module in mods]
self.assertTrue(modules)
for module in modules:
klass, name = module
try:
info = osbuild.meta.ModuleInfo.load(os.curdir, klass, name)
schema = osbuild.meta.Schema(info.schema, name)
res = schema.check()
if not res:
err = "\n ".join(str(e) for e in res)
self.fail(str(res) + "\n " + err)
except json.decoder.JSONDecodeError as e:
msg = f"{klass} '{name}' has invalid STAGE_OPTS\n\t" + str(e)
self.fail(msg)
def test_schema(self):
schema = osbuild.meta.Schema(None)
self.assertFalse(schema)
schema = osbuild.meta.Schema({"type": "bool"}) # should be 'boolean'
self.assertFalse(schema.check().valid)
self.assertFalse(schema)
schema = osbuild.meta.Schema({"type": "array", "minItems": 3})
self.assertTrue(schema.check().valid)
self.assertTrue(schema)
res = schema.validate([1, 2])
self.assertFalse(res)
res = schema.validate([1, 2, 3])
self.assertTrue(res)
def test_validation(self):
index = osbuild.meta.Index(os.curdir)
# an empty manifest is OK
res = fmt.validate({}, index)
self.assertEqual(res.valid, True)
# something totally invalid (by Ondřej Budai)
totally_invalid = {
"osbuild": {
"state": "awesome",
"but": {
"input-validation": 1
}
}
}
res = fmt.validate(totally_invalid, index)
self.assertEqual(res.valid, False)
# The top-level 'osbuild' is an additional property
self.assertEqual(len(res), 1)
# This is missing the runner
no_runner = {
"pipeline": {
"build": {
"pipeline": {}
}
}
}
res = fmt.validate(no_runner, index)
self.assertEqual(res.valid, False)
self.assertEqual(len(res), 1) # missing runner
lst = res[".pipeline.build"]
self.assertEqual(len(lst), 1)
# de-dup issues: the manifest checking will report
# the extra element and the recursive build pipeline
# check will also report that same error; make sure
# they get properly de-duplicated
no_runner_extra = {
"pipeline": {
"build": { # missing runner
"pipeline": {
"extra": True, # should not be there
"stages": [{
"name": "org.osbuild.chrony",
"options": {
"timeservers": "string" # should be an array
}
}]
}
}
}
}
res = fmt.validate(no_runner_extra, index)
self.assertEqual(res.valid, False)
self.assertEqual(len(res), 3)
lst = res[".pipeline.build.pipeline"]
self.assertEqual(len(lst), 1) # should only have one
lst = res[".pipeline.build.pipeline.stages[0].options.timeservers"]
self.assertEqual(len(lst), 1) # should only have one
# stage issues
stage_check = {
"pipeline": {
"stages": [{
"name": "org.osbuild.grub2",
"options": {
"uefi": {
"install": False,
# missing "vendor"
},
# missing rootfs or root_fs_uuid
}
}]
}
}
res = fmt.validate(stage_check, index)
self.assertEqual(res.valid, False)
self.assertEqual(len(res), 2)
lst = res[".pipeline.stages[0].options"]
self.assertEqual(len(lst), 1) # missing rootfs
lst = res[".pipeline.stages[0].options.uefi"]
self.assertEqual(len(lst), 1) # missing "osname"
assembler_check = {
"pipeline": {
"assembler": {
"name": "org.osbuild.tar",
"options": {
"compression": "foobar"
}
}
}
}
res = fmt.validate(assembler_check, index)
self.assertEqual(res.valid, False)
self.assertEqual(len(res), 2)
lst = res[".pipeline.assembler.options"]
self.assertEqual(len(lst), 1) # missing "filename"
lst = res[".pipeline.assembler.options.compression"]
self.assertEqual(len(lst), 1) # wrong compression method