debian-forge/test/mod/test_osbuild.py
Christian Kellner 7a6c2df910 stage: add module information about itself
Add a new `info` property that holds the `meta.ModuleInfo` info
for the stage. This gives each instance of a stage access to
meta (or class) information about it, i.e. its schema, docs but,
more importantly, also its name and path to the executable.
Thefore the `name` property is coverted into a transient property
which access the `name` member of `info`.
Change the `formats/v1` load mechanism to carry a new `index`
argument which is used to load the `ModuleInfo` for each stage.
Adapt all tests to load the info as well when creating stages.
2021-01-18 17:44:46 +01:00

269 lines
8.7 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."""
index = osbuild.meta.Index(os.curdir)
cases = [
{},
{"assembler": None},
{"stages": []},
{"build": {}},
{"build": None}
]
for pipeline in cases:
manifest = {"pipeline": pipeline}
with self.subTest(pipeline):
desc = fmt.describe(fmt.load(manifest, index))
self.assertEqual(desc["pipeline"], {})
@unittest.skipUnless(test.TestBase.can_bind_mount(), "root-only")
def test_stage_run(self):
index = osbuild.meta.Index(os.curdir)
info = index.get_module_info("Stage", "org.osbuild.noop")
stage = osbuild.Stage(info, {}, 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):
index = osbuild.meta.Index(os.curdir)
test_info = index.get_module_info("Stage", "org.osbuild.test")
build = osbuild.Pipeline("org.osbuild.test")
build.add_stage(test_info, {"one": 1})
pipeline = osbuild.Pipeline("org.osbuild.test", build.tree_id)
pipeline.add_stage(test_info, {"one": 2})
pipeline.set_assembler("org.osbuild.test")
manifest = osbuild.Manifest([build, 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