diff --git a/test/run/test_exports.py b/test/run/test_exports.py new file mode 100644 index 00000000..12b4cfe7 --- /dev/null +++ b/test/run/test_exports.py @@ -0,0 +1,89 @@ +import contextlib +import json +import os +import pathlib +import shutil +import subprocess + +import pytest + +from .. import test + + +@pytest.fixture(name="jsondata", scope="module") +def jsondata_fixture(): + return json.dumps({ + "version": "2", + "pipelines": [ + { + "name": "image", + "stages": [ + { + "type": "org.osbuild.truncate", + "options": { + "filename": "foo.img", + "size": "10", + }, + }, + { + # cannot use org.osbuild.chown as it needs the chown + # binary in the stage + "type": "org.osbuild.testing.injectpy", + "options": { + "code": [ + 'import os', + 'os.chown(f"{tree}/foo.img", 1000, 1000)', + ], + }, + }, + ] + } + ] + }) + + +@pytest.fixture(name="osb", scope="module") +def osbuild_fixture(): + with test.OSBuild() as osb: + yield osb + + +@pytest.fixture(name="testing_libdir", scope="module") +def testing_libdir_fixture(tmpdir_factory): + tests_path = pathlib.Path(__file__).parent.parent + project_path = tests_path.parent + testing_libdir_path = tests_path / "stages" + fake_libdir_path = tmpdir_factory.mktemp("fake-libdir") + # there must be an empty "osbild" dir for a "os.listdir(self._libdir)" + # in buildroot.py + (fake_libdir_path / "osbuild").mkdir() + # construct minimal viable libdir from current checkout + for d in ["stages", "runners", ]: + subprocess.run( + ["cp", "-a", os.fspath(project_path / d), f"{fake_libdir_path}"], + check=True) + # now inject testing stages + for p in testing_libdir_path.glob("org.osbuild.testing.*"): + shutil.copy2(p, fake_libdir_path / "stages") + yield fake_libdir_path + + +@pytest.mark.skipif(os.getuid() != 0, reason="root-only") +def test_exports_normal(osb, tmp_path, jsondata, testing_libdir): # pylint: disable=unused-argument + osb.compile(jsondata, output_dir=tmp_path, exports=["image"], libdir=testing_libdir) + expected_export = tmp_path / "image/foo.img" + assert expected_export.exists() + assert expected_export.stat().st_uid == 1000 + + +@pytest.mark.skipif(os.getuid() != 0, reason="root-only") +def test_exports_with_force_no_preserve_owner(osb, tmp_path, jsondata, testing_libdir): + with contextlib.ExitStack() as cm: + k = "OSBUILD_EXPORT_FORCE_NO_PRESERVE_OWNER" + os.environ[k] = "1" + cm.callback(os.unsetenv, k) + + osb.compile(jsondata, output_dir=tmp_path, exports=["image"], libdir=testing_libdir) + expected_export = tmp_path / "image/foo.img" + assert expected_export.exists() + assert expected_export.stat().st_uid == 0 diff --git a/test/stages/README.md b/test/stages/README.md new file mode 100644 index 00000000..1000bff9 --- /dev/null +++ b/test/stages/README.md @@ -0,0 +1,5 @@ +This directory contains stages that can only be used during testing and +will be dynamically added to the library directory. + +See `test/run/test_exports.py` for an example usage of the `pyinject` +stage. diff --git a/test/stages/org.osbuild.testing.injectpy b/test/stages/org.osbuild.testing.injectpy new file mode 100755 index 00000000..b842a8e7 --- /dev/null +++ b/test/stages/org.osbuild.testing.injectpy @@ -0,0 +1,27 @@ +#!/usr/bin/python3 +""" +Inject arbitrary python code. + +ONLY USE FOR TESTING +""" + +import sys + +import osbuild.api + +SCHEMA_2 = """ +"options": { + "additionalProperties": true +} +""" + + +def main(tree, options): # pylint: disable=unused-argument + script = "\n".join(options.get("code", [])) + exec(script) # pylint: disable=exec-used + + +if __name__ == '__main__': + args = osbuild.api.arguments() + r = main(args["tree"], args.get("options", {})) + sys.exit(r) diff --git a/test/test.py b/test/test.py index b9602e58..018bc4e0 100644 --- a/test/test.py +++ b/test/test.py @@ -347,7 +347,7 @@ class OSBuild(contextlib.AbstractContextManager): print(log) print("-- END ---------------------------------") - def compile(self, data_stdin, output_dir=None, checkpoints=None, check=False, exports=None): + def compile(self, data_stdin, output_dir=None, checkpoints=None, check=False, exports=None, libdir="."): """Compile an Artifact This takes a manifest as `data_stdin`, executes the pipeline, and @@ -377,7 +377,7 @@ class OSBuild(contextlib.AbstractContextManager): cmd_args = [sys.executable, "-m", "osbuild"] cmd_args += ["--json"] - cmd_args += ["--libdir", "."] + cmd_args += ["--libdir", libdir] cmd_args += ["--output-directory", output_dir] cmd_args += ["--store", self._cachedir]