summaryrefslogtreecommitdiffstats
path: root/tests/test_debputy_plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_debputy_plugin.py')
-rw-r--r--tests/test_debputy_plugin.py1246
1 files changed, 1246 insertions, 0 deletions
diff --git a/tests/test_debputy_plugin.py b/tests/test_debputy_plugin.py
new file mode 100644
index 0000000..a5d7758
--- /dev/null
+++ b/tests/test_debputy_plugin.py
@@ -0,0 +1,1246 @@
+import os
+import textwrap
+from typing import Sequence
+
+import pytest
+
+from debputy.exceptions import (
+ DebputyManifestVariableRequiresDebianDirError,
+ DebputySubstitutionError,
+)
+from debputy.manifest_parser.base_types import SymbolicMode
+from debputy.manifest_parser.util import AttributePath
+from debputy.plugin.api import virtual_path_def
+from debputy.plugin.api.spec import DSD
+from debputy.plugin.api.test_api import (
+ build_virtual_file_system,
+ package_metadata_context,
+)
+from debputy.plugin.api.test_api import manifest_variable_resolution_context
+from debputy.plugin.api.test_api.test_impl import initialize_plugin_under_test_preloaded
+from debputy.plugin.api.test_api.test_spec import DetectedService
+from debputy.plugin.debputy.debputy_plugin import initialize_debputy_features
+from debputy.plugin.debputy.private_api import load_libcap
+from debputy.plugin.debputy.service_management import SystemdServiceContext
+from debputy.plugin.debputy.types import DebputyCapability
+
+
+def test_debputy_packager_provided_files():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ ppf_by_stem = plugin.packager_provided_files_by_stem()
+ # Verify that all the files are loaded
+ assert set(ppf_by_stem.keys()) == {
+ "tmpfiles",
+ "sysusers",
+ "bash-completion",
+ "pam",
+ "ppp.ip-up",
+ "ppp.ip-down",
+ "logrotate",
+ "logcheck.cracking",
+ "logcheck.violations",
+ "logcheck.violations.ignore",
+ "logcheck.ignore.workstation",
+ "logcheck.ignore.server",
+ "logcheck.ignore.paranoid",
+ "mime",
+ "sharedmimeinfo",
+ "if-pre-up",
+ "if-up",
+ "if-down",
+ "if-post-down",
+ "cron.hourly",
+ "cron.daily",
+ "cron.weekly",
+ "cron.monthly",
+ "cron.yearly",
+ "cron.d",
+ "initramfs-hook",
+ "modprobe",
+ "gsettings-override",
+ "lintian-overrides",
+ "bug-script",
+ "bug-control",
+ "bug-presubj",
+ "changelog",
+ "NEWS",
+ "copyright",
+ "README.Debian",
+ "TODO",
+ "doc-base",
+ "shlibs",
+ "symbols",
+ "alternatives",
+ "init",
+ "default",
+ "templates",
+ # dh_installsytemd
+ "mount",
+ "path",
+ "service",
+ "socket",
+ "target",
+ "timer",
+ "@path",
+ "@service",
+ "@socket",
+ "@target",
+ "@timer",
+ }
+ # Verify the post_rewrite_hook
+ assert (
+ ppf_by_stem["logcheck.ignore.paranoid"].compute_dest("foo.bar")[1] == "foo_bar"
+ )
+ # Verify custom formats work
+ assert ppf_by_stem["tmpfiles"].compute_dest("foo.bar")[1] == "foo.bar.conf"
+ assert ppf_by_stem["sharedmimeinfo"].compute_dest("foo.bar")[1] == "foo.bar.xml"
+ assert ppf_by_stem["modprobe"].compute_dest("foo.bar")[1] == "foo.bar.conf"
+ assert (
+ ppf_by_stem["gsettings-override"].compute_dest("foo.bar", assigned_priority=20)[
+ 1
+ ]
+ == "20_foo.bar.gschema.override"
+ )
+
+
+def test_debputy_docbase_naming() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ doc_base_pff = plugin.packager_provided_files_by_stem()["doc-base"]
+ fs_root = build_virtual_file_system(
+ [virtual_path_def("foo.doc-base", content="Document: bar")]
+ )
+ _, basename = doc_base_pff.compute_dest("foo", path=fs_root["foo.doc-base"])
+ assert basename == "foo.bar"
+
+
+def test_debputy_adr_examples() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ issues = plugin.automatic_discard_rules_examples_with_issues()
+ assert not issues
+
+
+def test_debputy_metadata_detector_gsettings_dependencies():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ # By default, the plugin will not add a substvars
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector("gsettings-dependencies", fs_root)
+ assert "misc:Depends" not in metadata.substvars
+
+ # It will not react if there is only directories or non-files
+ fs_root = build_virtual_file_system(["./usr/share/glib-2.0/schemas/some-dir/"])
+ metadata = plugin.run_metadata_detector("gsettings-dependencies", fs_root)
+ assert "misc:Depends" not in metadata.substvars
+
+ # However, it will if there is a file beneath the schemas dir
+ fs_root = build_virtual_file_system(["./usr/share/glib-2.0/schemas/foo.xml"])
+ metadata = plugin.run_metadata_detector("gsettings-dependencies", fs_root)
+ assert (
+ metadata.substvars["misc:Depends"]
+ == "dconf-gsettings-backend | gsettings-backend"
+ )
+
+
+def test_debputy_metadata_detector_initramfs_hooks():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "initramfs-hooks"
+
+ # By default, the plugin will not add a trigger
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.triggers == []
+
+ # It will not react if the directory is empty
+ fs_root = build_virtual_file_system(
+ [
+ # Use an absolute path to verify that also work (it should and third-party plugin are likely
+ # use absolute paths)
+ "/usr/share/initramfs-tools/hooks/"
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.triggers == []
+
+ # However, it will if there is a file beneath the schemas dir
+ fs_root = build_virtual_file_system(["./usr/share/initramfs-tools/hooks/some-hook"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ result = [t.serialized_format() for t in metadata.triggers]
+ assert result == ["activate-noawait update-initramfs"]
+
+
+def test_debputy_metadata_detector_systemd_tmpfiles():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "systemd-tmpfiles"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ # It only reacts to ".conf" files
+ fs_root = build_virtual_file_system(["./usr/lib/tmpfiles.d/foo"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ "./usr/lib/tmpfiles.d/foo.conf",
+ "./etc/tmpfiles.d/foo.conf",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 1
+ snippet = snippets[0]
+ assert snippet.maintscript == "postinst"
+ assert snippet.registration_method == "on_configure"
+ # The snippet should use "systemd-tmpfiles [...] --create foo.conf ..."
+ assert "--create foo.conf" in snippet.plugin_provided_script
+ # The "foo.conf" should only be listed once
+ assert snippet.plugin_provided_script.count("foo.conf") == 1
+
+
+def test_debputy_metadata_detector_systemd_sysusers():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "systemd-sysusers"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ # It only reacts to ".conf" files
+ fs_root = build_virtual_file_system(["./usr/lib/sysusers.d/foo"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(["./usr/lib/sysusers.d/foo.conf"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 1
+ snippet = snippets[0]
+ assert snippet.maintscript == "postinst"
+ assert snippet.registration_method == "on_configure"
+ # The snippet should use "systemd-sysusers [...] foo.conf ..."
+ assert "systemd-sysusers" in snippet.plugin_provided_script
+ assert "foo.conf" in snippet.plugin_provided_script
+ # The "foo.conf" should only be listed once
+ assert snippet.plugin_provided_script.count("foo.conf") == 1
+
+
+def test_debputy_metadata_detector_xfonts():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "xfonts"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+ assert "misc:Depends" not in metadata.substvars
+
+ # It ignores files in the X11 dir and directories starting with ".".
+ fs_root = build_virtual_file_system(
+ ["./usr/share/fonts/X11/foo", "./usr/share/fonts/X11/.a/"]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+ assert "misc:Depends" not in metadata.substvars
+
+ fs_root = build_virtual_file_system(
+ [
+ "./usr/share/fonts/X11/some-font-dir/",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert metadata.substvars["misc:Depends"] == "xfonts-utils"
+ assert len(snippets) == 2
+ assert set(s.maintscript for s in snippets) == {"postinst", "postrm"}
+ postinst_snippet = metadata.maintscripts(maintscript="postinst")[0]
+ postrm_snippet = metadata.maintscripts(maintscript="postrm")[0]
+
+ assert postinst_snippet.maintscript == "postinst"
+ assert postinst_snippet.registration_method == "unconditionally_in_script"
+ assert (
+ "update-fonts-scale some-font-dir"
+ not in postinst_snippet.plugin_provided_script
+ )
+ assert "--x11r7-layout some-font-dir" in postinst_snippet.plugin_provided_script
+ assert (
+ f"update-fonts-alias --include" not in postinst_snippet.plugin_provided_script
+ )
+
+ assert postrm_snippet.maintscript == "postrm"
+ assert postrm_snippet.registration_method == "unconditionally_in_script"
+ assert (
+ "update-fonts-scale some-font-dir" not in postrm_snippet.plugin_provided_script
+ )
+ assert "--x11r7-layout some-font-dir" in postrm_snippet.plugin_provided_script
+ assert f"update-fonts-alias --exclude" not in postrm_snippet.plugin_provided_script
+
+
+def test_debputy_metadata_detector_xfonts_scale_and_alias():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+
+ metadata_detector_id = "xfonts"
+ package_name = "bar"
+ fs_root = build_virtual_file_system(
+ [
+ "./usr/share/fonts/X11/some-font-dir/",
+ f"./etc/X11/xfonts/some-font-dir/{package_name}.scale",
+ f"./etc/X11/xfonts/some-font-dir/{package_name}.alias",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(
+ metadata_detector_id,
+ fs_root,
+ package_metadata_context(
+ package_fields={
+ "Package": package_name,
+ }
+ ),
+ )
+ snippets = metadata.maintscripts()
+ assert metadata.substvars["misc:Depends"] == "xfonts-utils"
+ assert len(snippets) == 2
+ assert set(s.maintscript for s in snippets) == {"postinst", "postrm"}
+ postinst_snippet = metadata.maintscripts(maintscript="postinst")[0]
+ postrm_snippet = metadata.maintscripts(maintscript="postrm")[0]
+
+ assert postinst_snippet.maintscript == "postinst"
+ assert postinst_snippet.registration_method == "unconditionally_in_script"
+ assert "update-fonts-scale some-font-dir" in postinst_snippet.plugin_provided_script
+ assert "--x11r7-layout some-font-dir" in postinst_snippet.plugin_provided_script
+ assert (
+ f"update-fonts-alias --include /etc/X11/xfonts/some-font-dir/{package_name}.alias some-font-dir"
+ in postinst_snippet.plugin_provided_script
+ )
+
+ assert postrm_snippet.maintscript == "postrm"
+ assert postrm_snippet.registration_method == "unconditionally_in_script"
+ assert "update-fonts-scale some-font-dir" in postrm_snippet.plugin_provided_script
+ assert "--x11r7-layout some-font-dir" in postrm_snippet.plugin_provided_script
+ assert (
+ f"update-fonts-alias --exclude /etc/X11/xfonts/some-font-dir/{package_name}.alias some-font-dir"
+ in postrm_snippet.plugin_provided_script
+ )
+
+
+def test_debputy_metadata_detector_icon_cache():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "icon-cache"
+ icon_dir = "usr/share/icons"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ # Ignored subdirs (dh_icons ignores them too)
+ f"./{icon_dir}/gnome/foo.png",
+ f"./{icon_dir}/hicolor/foo.png",
+ # Unknown image format, so it does not trigger the update-icon-caches call
+ f"./{icon_dir}/subdir-a/unknown-image-format.img",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ f"./{icon_dir}/subdir-a/foo.png",
+ f"./{icon_dir}/subdir-b/subsubdir/bar.svg",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 2
+ assert set(s.maintscript for s in snippets) == {"postinst", "postrm"}
+ postinst_snippet = metadata.maintscripts(maintscript="postinst")[0]
+ postrm_snippet = metadata.maintscripts(maintscript="postrm")[0]
+
+ assert postinst_snippet.registration_method == "on_configure"
+ assert postrm_snippet.registration_method == "unconditionally_in_script"
+
+ # Directory order is stable according to the BinaryPackagePath API.
+ assert (
+ f"update-icon-caches /{icon_dir}/subdir-a /{icon_dir}/subdir-b"
+ in postinst_snippet.plugin_provided_script
+ )
+ assert (
+ f"update-icon-caches /{icon_dir}/subdir-a /{icon_dir}/subdir-b"
+ in postrm_snippet.plugin_provided_script
+ )
+
+
+def test_debputy_metadata_detector_kernel_modules():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "kernel-modules"
+ module_dir = "lib/modules"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ # Ignore files directly in the path or with wrong extension
+ f"./{module_dir}/README",
+ f"./{module_dir}/3.11/ignored-file.txt",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ f"./{module_dir}/3.11/foo.ko",
+ f"./usr/{module_dir}/3.12/bar.ko.xz",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 4 # Two for each version
+ assert set(s.maintscript for s in snippets) == {"postinst", "postrm"}
+ postinst_snippets = metadata.maintscripts(maintscript="postinst")
+ postrm_snippets = metadata.maintscripts(maintscript="postrm")
+
+ assert len(postinst_snippets) == 2
+ assert len(postrm_snippets) == 2
+ assert {s.registration_method for s in postinst_snippets} == {"on_configure"}
+ assert {s.registration_method for s in postrm_snippets} == {
+ "unconditionally_in_script"
+ }
+
+ assert (
+ "depmod -a -F /boot/System.map-3.11 3.11"
+ in postinst_snippets[0].plugin_provided_script
+ )
+ assert (
+ "depmod -a -F /boot/System.map-3.12 3.12"
+ in postinst_snippets[1].plugin_provided_script
+ )
+
+ assert (
+ "depmod -a -F /boot/System.map-3.11 3.11"
+ in postrm_snippets[0].plugin_provided_script
+ )
+ assert (
+ "depmod -a -F /boot/System.map-3.12 3.12"
+ in postrm_snippets[1].plugin_provided_script
+ )
+
+
+def test_debputy_metadata_detector_dpkg_shlibdeps():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "dpkg-shlibdeps"
+ skip_root_dir = "usr/lib/debug/"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(
+ [
+ "./usr/share/doc/foo/copyright",
+ virtual_path_def("./usr/lib/debputy/test.py", fs_path=__file__),
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert "shlibs:Depends" not in metadata.substvars
+
+ fs_root = build_virtual_file_system(
+ [
+ # Verify that certain directories are skipped as promised
+ virtual_path_def(f"./{skip_root_dir}/bin/ls", fs_path="/bin/ls")
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert "shlibs:Depends" not in metadata.substvars
+
+ # But we detect ELF binaries elsewhere
+ fs_root = build_virtual_file_system(
+ [virtual_path_def(f"./bin/ls", fs_path="/bin/ls")]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ # Do not make assertions about the content of `shlibs:Depends` as
+ # package name and versions change over time.
+ assert "shlibs:Depends" in metadata.substvars
+
+ # Re-run to verify it runs for udebs as well
+ metadata = plugin.run_metadata_detector(
+ metadata_detector_id,
+ fs_root,
+ context=package_metadata_context(
+ package_fields={"Package-Type": "udeb"},
+ ),
+ )
+ assert "shlibs:Depends" in metadata.substvars
+
+
+def test_debputy_metadata_detector_pycompile_files():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "pycompile-files"
+ module_dir = "usr/lib/python3/dist-packages"
+
+ # By default, the plugin will not add anything
+ fs_root = build_virtual_file_system(["./bin/ls"])
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ # Ignore files in unknown directories by default
+ "./random-dir/foo.py",
+ # Must be in "dist-packages" to count
+ "./usr/lib/python3/foo.py",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ assert metadata.maintscripts() == []
+
+ fs_root = build_virtual_file_system(
+ [
+ f"./{module_dir}/debputy/foo.py",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 2
+ assert set(s.maintscript for s in snippets) == {"postinst", "prerm"}
+ postinst_snippets = metadata.maintscripts(maintscript="postinst")
+ prerm_snippets = metadata.maintscripts(maintscript="prerm")
+
+ assert len(postinst_snippets) == 1
+ assert len(prerm_snippets) == 1
+ assert {s.registration_method for s in postinst_snippets} == {"on_configure"}
+ assert {s.registration_method for s in prerm_snippets} == {
+ "unconditionally_in_script"
+ }
+
+ assert "py3compile -p foo" in postinst_snippets[0].plugin_provided_script
+
+ assert "py3clean -p foo" in prerm_snippets[0].plugin_provided_script
+
+
+def test_debputy_metadata_detector_pycompile_files_private_package_dir():
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ metadata_detector_id = "pycompile-files"
+ module_dir = "usr/share/foo"
+
+ fs_root = build_virtual_file_system(
+ [
+ f"./{module_dir}/debputy/foo.py",
+ ]
+ )
+ metadata = plugin.run_metadata_detector(metadata_detector_id, fs_root)
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 2
+ assert set(s.maintscript for s in snippets) == {"postinst", "prerm"}
+ postinst_snippets = metadata.maintscripts(maintscript="postinst")
+ prerm_snippets = metadata.maintscripts(maintscript="prerm")
+
+ assert len(postinst_snippets) == 1
+ assert len(prerm_snippets) == 1
+ assert {s.registration_method for s in postinst_snippets} == {"on_configure"}
+ assert {s.registration_method for s in prerm_snippets} == {
+ "unconditionally_in_script"
+ }
+
+ assert (
+ f"py3compile -p foo /{module_dir}"
+ in postinst_snippets[0].plugin_provided_script
+ )
+
+ assert "py3clean -p foo" in prerm_snippets[0].plugin_provided_script
+
+
+def _extract_service(
+ services: Sequence[DetectedService[DSD]], name: str
+) -> DetectedService[DSD]:
+ v = [s for s in services if name in s.names]
+ assert len(v) == 1
+ return v[0]
+
+
+def test_system_service_detection() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ systemd_service_root_dir = "usr/lib/systemd"
+ systemd_service_system_dir = f"{systemd_service_root_dir}/system"
+ systemd_service_user_dir = f"{systemd_service_root_dir}/user"
+
+ services, _ = plugin.run_service_detection_and_integrations(
+ "systemd", build_virtual_file_system([])
+ )
+ assert not services
+
+ services, _ = plugin.run_service_detection_and_integrations(
+ "systemd",
+ build_virtual_file_system(
+ [f"{systemd_service_system_dir}/", f"{systemd_service_user_dir}/"]
+ ),
+ )
+ assert not services
+
+ fs_root = build_virtual_file_system(
+ [
+ virtual_path_def(
+ f"{systemd_service_system_dir}/foo.service",
+ content=textwrap.dedent(
+ """\
+ Alias="myname.service"
+ [Install]
+ """
+ ),
+ ),
+ virtual_path_def(
+ f"{systemd_service_system_dir}/foo@.service",
+ content=textwrap.dedent(
+ """\
+ # dh_installsystemd ignores template services - we do for now as well.
+ Alias="ignored.service"
+ [Install]
+ """
+ ),
+ ),
+ virtual_path_def(
+ f"{systemd_service_system_dir}/alias.service", link_target="foo.service"
+ ),
+ virtual_path_def(f"{systemd_service_system_dir}/bar.timer", content=""),
+ ]
+ )
+ services, metadata = plugin.run_service_detection_and_integrations(
+ "systemd",
+ fs_root,
+ service_context_type_hint=SystemdServiceContext,
+ )
+ assert len(services) == 2
+ assert {s.names[0] for s in services} == {"foo.service", "bar.timer"}
+ foo_service = _extract_service(services, "foo.service")
+ assert set(foo_service.names) == {
+ "foo.service",
+ "foo",
+ "alias",
+ "alias.service",
+ "myname.service",
+ "myname",
+ }
+ assert foo_service.type_of_service == "service"
+ assert foo_service.service_scope == "system"
+ assert foo_service.enable_by_default
+ assert foo_service.start_by_default
+ assert foo_service.default_upgrade_rule == "restart"
+ assert foo_service.service_context.had_install_section
+
+ bar_timer = _extract_service(services, "bar.timer")
+ assert set(bar_timer.names) == {"bar.timer"}
+ assert bar_timer.type_of_service == "timer"
+ assert bar_timer.service_scope == "system"
+ assert not bar_timer.enable_by_default
+ assert bar_timer.start_by_default
+ assert bar_timer.default_upgrade_rule == "restart"
+ assert not bar_timer.service_context.had_install_section
+
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 4
+ postinsts = metadata.maintscripts(maintscript="postinst")
+ assert len(postinsts) == 2
+ enable_postinst, start_postinst = postinsts
+ assert (
+ "deb-systemd-helper debian-installed foo.service"
+ in enable_postinst.plugin_provided_script
+ )
+ assert (
+ "deb-systemd-invoke start foo.service" in start_postinst.plugin_provided_script
+ )
+ assert (
+ "deb-systemd-invoke restart foo.service"
+ in start_postinst.plugin_provided_script
+ )
+
+
+def test_sysv_service_detection() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ init_dir = "etc/init.d"
+
+ services, _ = plugin.run_service_detection_and_integrations(
+ "sysvinit", build_virtual_file_system([])
+ )
+ assert not services
+
+ services, _ = plugin.run_service_detection_and_integrations(
+ "sysvinit",
+ build_virtual_file_system(
+ [
+ f"{init_dir}/",
+ ]
+ ),
+ )
+ assert not services
+
+ services, _ = plugin.run_service_detection_and_integrations(
+ "sysvinit",
+ build_virtual_file_system(
+ [
+ virtual_path_def(
+ f"{init_dir}/README",
+ mode=0o644,
+ ),
+ ]
+ ),
+ )
+ assert not services
+
+ fs_root = build_virtual_file_system(
+ [
+ virtual_path_def(
+ f"{init_dir}/foo",
+ mode=0o755,
+ ),
+ ]
+ )
+ services, metadata = plugin.run_service_detection_and_integrations(
+ "sysvinit", fs_root
+ )
+ assert len(services) == 1
+ assert {s.names[0] for s in services} == {"foo"}
+ foo_service = _extract_service(services, "foo")
+ assert set(foo_service.names) == {"foo"}
+ assert foo_service.type_of_service == "service"
+ assert foo_service.service_scope == "system"
+ assert foo_service.enable_by_default
+ assert foo_service.start_by_default
+ assert foo_service.default_upgrade_rule == "restart"
+
+ snippets = metadata.maintscripts()
+ assert len(snippets) == 4
+ postinsts = metadata.maintscripts(maintscript="postinst")
+ assert len(postinsts) == 1
+ postinst = postinsts[0]
+ assert postinst.registration_method == "on_configure"
+ assert "" in postinst.plugin_provided_script
+ assert "update-rc.d foo defaults" in postinst.plugin_provided_script
+ assert (
+ "invoke-rc.d --skip-systemd-native foo start" in postinst.plugin_provided_script
+ )
+ assert (
+ "invoke-rc.d --skip-systemd-native foo restart"
+ in postinst.plugin_provided_script
+ )
+
+
+def test_debputy_manifest_variables() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ manifest_variables_no_dch = plugin.manifest_variables()
+ assert manifest_variables_no_dch.keys() == {
+ "DEB_BUILD_ARCH",
+ "DEB_BUILD_ARCH_ABI",
+ "DEB_BUILD_ARCH_BITS",
+ "DEB_BUILD_ARCH_CPU",
+ "DEB_BUILD_ARCH_ENDIAN",
+ "DEB_BUILD_ARCH_LIBC",
+ "DEB_BUILD_ARCH_OS",
+ "DEB_BUILD_GNU_CPU",
+ "DEB_BUILD_GNU_SYSTEM",
+ "DEB_BUILD_GNU_TYPE",
+ "DEB_BUILD_MULTIARCH",
+ "DEB_HOST_ARCH",
+ "DEB_HOST_ARCH_ABI",
+ "DEB_HOST_ARCH_BITS",
+ "DEB_HOST_ARCH_CPU",
+ "DEB_HOST_ARCH_ENDIAN",
+ "DEB_HOST_ARCH_LIBC",
+ "DEB_HOST_ARCH_OS",
+ "DEB_HOST_GNU_CPU",
+ "DEB_HOST_GNU_SYSTEM",
+ "DEB_HOST_GNU_TYPE",
+ "DEB_HOST_MULTIARCH",
+ "DEB_SOURCE",
+ "DEB_TARGET_ARCH",
+ "DEB_TARGET_ARCH_ABI",
+ "DEB_TARGET_ARCH_BITS",
+ "DEB_TARGET_ARCH_CPU",
+ "DEB_TARGET_ARCH_ENDIAN",
+ "DEB_TARGET_ARCH_LIBC",
+ "DEB_TARGET_ARCH_OS",
+ "DEB_TARGET_GNU_CPU",
+ "DEB_TARGET_GNU_SYSTEM",
+ "DEB_TARGET_GNU_TYPE",
+ "DEB_TARGET_MULTIARCH",
+ "DEB_VERSION",
+ "DEB_VERSION_EPOCH_UPSTREAM",
+ "DEB_VERSION_UPSTREAM",
+ "DEB_VERSION_UPSTREAM_REVISION",
+ "PACKAGE",
+ "SOURCE_DATE_EPOCH",
+ "_DEBPUTY_INTERNAL_NON_BINNMU_SOURCE",
+ "_DEBPUTY_SND_SOURCE_DATE_EPOCH",
+ "path:BASH_COMPLETION_DIR",
+ "path:GNU_INFO_DIR",
+ "token:CLOSE_CURLY_BRACE",
+ "token:DOUBLE_CLOSE_CURLY_BRACE",
+ "token:DOUBLE_OPEN_CURLY_BRACE",
+ "token:NEWLINE",
+ "token:NL",
+ "token:OPEN_CURLY_BRACE",
+ "token:TAB",
+ }
+
+ for v in [
+ "DEB_SOURCE",
+ "DEB_VERSION",
+ "DEB_VERSION_EPOCH_UPSTREAM",
+ "DEB_VERSION_UPSTREAM",
+ "DEB_VERSION_UPSTREAM_REVISION",
+ "SOURCE_DATE_EPOCH",
+ "_DEBPUTY_INTERNAL_NON_BINNMU_SOURCE",
+ "_DEBPUTY_SND_SOURCE_DATE_EPOCH",
+ ]:
+ with pytest.raises(DebputyManifestVariableRequiresDebianDirError):
+ manifest_variables_no_dch[v]
+
+ with pytest.raises(DebputySubstitutionError):
+ manifest_variables_no_dch["PACKAGE"]
+
+ dch_content = textwrap.dedent(
+ """\
+ mscgen (1:0.20-15) unstable; urgency=medium
+
+ * Irrelevant stuff here...
+ * Also, some details have been tweaked for better testing
+
+ -- Niels Thykier <niels@thykier.net> Mon, 09 Oct 2023 14:50:06 +0000
+ """
+ )
+
+ debian_dir = build_virtual_file_system(
+ [virtual_path_def("changelog", content=dch_content)]
+ )
+ resolution_context = manifest_variable_resolution_context(debian_dir=debian_dir)
+ manifest_variables = plugin.manifest_variables(
+ resolution_context=resolution_context
+ )
+
+ assert manifest_variables["DEB_SOURCE"] == "mscgen"
+ assert manifest_variables["DEB_VERSION"] == "1:0.20-15"
+ assert manifest_variables["_DEBPUTY_INTERNAL_NON_BINNMU_SOURCE"] == "1:0.20-15"
+
+ assert manifest_variables["DEB_VERSION_EPOCH_UPSTREAM"] == "1:0.20"
+ assert manifest_variables["DEB_VERSION_UPSTREAM"] == "0.20"
+ assert manifest_variables["DEB_VERSION_UPSTREAM_REVISION"] == "0.20-15"
+ assert manifest_variables["SOURCE_DATE_EPOCH"] == "1696863006"
+ assert manifest_variables["_DEBPUTY_SND_SOURCE_DATE_EPOCH"] == "1696863006"
+
+ # This one remains unresolvable
+ with pytest.raises(DebputySubstitutionError):
+ manifest_variables["PACKAGE"]
+
+ static_values = {
+ "path:BASH_COMPLETION_DIR": "/usr/share/bash-completion/completions",
+ "path:GNU_INFO_DIR": "/usr/share/info",
+ }
+
+ for k, v in static_values.items():
+ assert manifest_variables[k] == v
+
+ dch_content_bin_nmu = textwrap.dedent(
+ """\
+ mscgen (1:0.20-15+b4) unstable; urgency=medium, binary-only=yes
+
+ * Some binNMU entry here
+
+ -- Niels Thykier <niels@thykier.net> Mon, 10 Nov 2023 16:01:17 +0000
+
+ mscgen (1:0.20-15) unstable; urgency=medium
+
+ * Irrelevant stuff here...
+ * Also, some details have been tweaked for better testing
+
+ -- Niels Thykier <niels@thykier.net> Mon, 09 Oct 2023 14:50:06 +0000
+ """
+ )
+
+ debian_dir_bin_nmu = build_virtual_file_system(
+ [virtual_path_def("changelog", content=dch_content_bin_nmu)]
+ )
+ resolution_context_bin_nmu = manifest_variable_resolution_context(
+ debian_dir=debian_dir_bin_nmu
+ )
+ manifest_variables_bin_nmu = plugin.manifest_variables(
+ resolution_context=resolution_context_bin_nmu
+ )
+
+ assert manifest_variables_bin_nmu["DEB_SOURCE"] == "mscgen"
+ assert manifest_variables_bin_nmu["DEB_VERSION"] == "1:0.20-15+b4"
+ assert (
+ manifest_variables_bin_nmu["_DEBPUTY_INTERNAL_NON_BINNMU_SOURCE"] == "1:0.20-15"
+ )
+
+ assert manifest_variables_bin_nmu["DEB_VERSION_EPOCH_UPSTREAM"] == "1:0.20"
+ assert manifest_variables_bin_nmu["DEB_VERSION_UPSTREAM"] == "0.20"
+ assert manifest_variables_bin_nmu["DEB_VERSION_UPSTREAM_REVISION"] == "0.20-15+b4"
+ assert manifest_variables_bin_nmu["SOURCE_DATE_EPOCH"] == "1699632077"
+ assert manifest_variables_bin_nmu["_DEBPUTY_SND_SOURCE_DATE_EPOCH"] == "1696863006"
+
+
+def test_cap_validator() -> None:
+ has_libcap, _, is_valid_cap = load_libcap()
+
+ if not has_libcap:
+ if os.environ.get("DEBPUTY_REQUIRE_LIBCAP", "") != "":
+ pytest.fail("Could not load libcap, but DEBPUTY_REQUIRE_CAP was non-empty")
+ pytest.skip("Could not load libcap.so")
+ assert not is_valid_cap("foo")
+ assert is_valid_cap("cap_dac_override,cap_bpf,cap_net_admin=ep")
+
+
+def test_clean_la_files() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+ fs_root = build_virtual_file_system(
+ [virtual_path_def("usr/bin/foo", content="#!/bin/sh\n")]
+ )
+ # Does nothing by default
+ plugin.run_package_processor(
+ "clean-la-files",
+ fs_root,
+ )
+
+ la_file_content = textwrap.dedent(
+ """\
+ dependency_libs = 'foo bar'
+ another_line = 'foo bar'
+ """
+ )
+ expected_content = textwrap.dedent(
+ """\
+ dependency_libs = ''
+ another_line = 'foo bar'
+ """
+ )
+ la_file_content_no_change = expected_content
+ expected_content = textwrap.dedent(
+ """\
+ dependency_libs = ''
+ another_line = 'foo bar'
+ """
+ )
+
+ fs_root = build_virtual_file_system(
+ [
+ virtual_path_def("usr/lib/libfoo.la", materialized_content=la_file_content),
+ virtual_path_def(
+ "usr/lib/libfoo-unchanged.la",
+ content=la_file_content_no_change,
+ ),
+ ]
+ )
+
+ plugin.run_package_processor(
+ "clean-la-files",
+ fs_root,
+ )
+ for basename in ("libfoo.la", "libfoo-unchanged.la"):
+ la_file = fs_root.lookup(f"usr/lib/{basename}")
+ assert la_file is not None and la_file.is_file
+ if basename == "libfoo-unchanged.la":
+ # it should never have been rewritten
+ assert not la_file.has_fs_path
+ else:
+ assert la_file.has_fs_path
+ with la_file.open() as fd:
+ rewritten_content = fd.read()
+ assert rewritten_content == expected_content
+
+
+def test_strip_nondeterminism() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+
+ fs_root = build_virtual_file_system(
+ [
+ # Note, we are only testing a negative example as a positive example crashes
+ # because we do not have a SOURCE_DATE_EPOCH value/substitution
+ virtual_path_def("test/not-really-a-png.png", content="Not a PNG")
+ ]
+ )
+
+ plugin.run_package_processor(
+ "strip-nondeterminism",
+ fs_root,
+ )
+
+
+def test_translate_capabilities() -> None:
+ attribute_path = AttributePath.test_path()
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+
+ fs_root = build_virtual_file_system([virtual_path_def("usr/bin/foo", mode=0o4755)])
+
+ foo = fs_root.lookup("usr/bin/foo")
+ assert foo is not None
+ assert foo.is_file
+ assert foo.is_read_write
+
+ metadata_no_cap = plugin.run_metadata_detector(
+ "translate-capabilities",
+ fs_root,
+ )
+
+ assert not metadata_no_cap.maintscripts(maintscript="postinst")
+
+ cap = foo.metadata(DebputyCapability)
+ assert not cap.is_present
+ assert cap.can_write
+ cap.value = DebputyCapability(
+ capabilities="cap_net_raw+ep",
+ capability_mode=SymbolicMode.parse_filesystem_mode(
+ "u-s",
+ attribute_path["cap_mode"],
+ ),
+ definition_source="test",
+ )
+ metadata_w_cap = plugin.run_metadata_detector(
+ "translate-capabilities",
+ fs_root,
+ )
+
+ postinsts = metadata_w_cap.maintscripts(maintscript="postinst")
+ assert len(postinsts) == 1
+ postinst = postinsts[0]
+ assert postinst.registration_method == "on_configure"
+ assert "setcap cap_net_raw+ep " in postinst.plugin_provided_script
+ assert "chmod u-s " in postinst.plugin_provided_script
+
+
+def test_pam_auth_update() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+
+ fs_root = build_virtual_file_system(["usr/bin/foo"])
+
+ empty_metadata = plugin.run_metadata_detector("pam-auth-update", fs_root)
+ assert not empty_metadata.maintscripts()
+
+ fs_root = build_virtual_file_system(["/usr/share/pam-configs/foo-pam"])
+
+ pam_metadata = plugin.run_metadata_detector("pam-auth-update", fs_root)
+ postinsts = pam_metadata.maintscripts(maintscript="postinst")
+ assert len(postinsts) == 1
+ prerms = pam_metadata.maintscripts(maintscript="prerm")
+ assert len(prerms) == 1
+
+ postinst = postinsts[0]
+ assert postinst.registration_method == "on_configure"
+ assert "pam-auth-update --package" in postinst.plugin_provided_script
+
+ prerms = prerms[0]
+ assert prerms.registration_method == "on_before_removal"
+ assert "pam-auth-update --package --remove foo-pam" in prerms.plugin_provided_script
+
+
+def test_auto_depends_solink() -> None:
+ plugin = initialize_plugin_under_test_preloaded(
+ 1,
+ initialize_debputy_features,
+ plugin_name="debputy",
+ load_debputy_plugin=False,
+ )
+
+ fs_root = build_virtual_file_system(["usr/bin/foo"])
+
+ empty_metadata = plugin.run_metadata_detector(
+ "auto-depends-arch-any-solink",
+ fs_root,
+ )
+ assert "misc:Depends" not in empty_metadata.substvars
+ fs_root = build_virtual_file_system(
+ [
+ "usr/lib/x86_64-linux-gnu/libfoo.la",
+ virtual_path_def(
+ "usr/lib/x86_64-linux-gnu/libfoo.so", link_target="libfoo.so.1"
+ ),
+ ]
+ )
+
+ still_empty_metadata = plugin.run_metadata_detector(
+ "auto-depends-arch-any-solink",
+ fs_root,
+ )
+ assert "misc:Depends" not in still_empty_metadata.substvars
+
+ libfoo1_fs_root = build_virtual_file_system(
+ [
+ virtual_path_def(
+ "usr/lib/x86_64-linux-gnu/libfoo.so.1", link_target="libfoo.so.1.0.0"
+ ),
+ ]
+ )
+
+ context_correct = package_metadata_context(
+ package_fields={
+ "Package": "libfoo-dev",
+ },
+ accessible_package_roots=[
+ (
+ {
+ "Package": "libfoo1",
+ "Architecture": "any",
+ },
+ libfoo1_fs_root,
+ )
+ ],
+ )
+ sodep_metadata = plugin.run_metadata_detector(
+ "auto-depends-arch-any-solink",
+ fs_root,
+ context=context_correct,
+ )
+ assert "misc:Depends" in sodep_metadata.substvars
+ assert sodep_metadata.substvars["misc:Depends"] == "libfoo1 (= ${binary:Version})"
+
+ context_incorrect = package_metadata_context(
+ package_fields={"Package": "libfoo-dev", "Architecture": "all"},
+ accessible_package_roots=[
+ (
+ {
+ "Package": "foo",
+ "Architecture": "all",
+ },
+ build_virtual_file_system([]),
+ )
+ ],
+ )
+ sodep_metadata = plugin.run_metadata_detector(
+ "auto-depends-arch-any-solink",
+ fs_root,
+ context=context_incorrect,
+ )
+ assert "misc:Depends" not in sodep_metadata.substvars
+
+ context_too_many_matches = package_metadata_context(
+ package_fields={"Package": "libfoo-dev"},
+ accessible_package_roots=[
+ (
+ {
+ "Package": "libfoo1-a",
+ "Architecture": "any",
+ },
+ libfoo1_fs_root,
+ ),
+ (
+ {
+ "Package": "libfoo1-b",
+ "Architecture": "any",
+ },
+ libfoo1_fs_root,
+ ),
+ ],
+ )
+ sodep_metadata = plugin.run_metadata_detector(
+ "auto-depends-arch-any-solink",
+ fs_root,
+ context=context_too_many_matches,
+ )
+ assert "misc:Depends" not in sodep_metadata.substvars