diff options
Diffstat (limited to 'tests/test_migrations.py')
-rw-r--r-- | tests/test_migrations.py | 1767 |
1 files changed, 1767 insertions, 0 deletions
diff --git a/tests/test_migrations.py b/tests/test_migrations.py new file mode 100644 index 0000000..dc07d4c --- /dev/null +++ b/tests/test_migrations.py @@ -0,0 +1,1767 @@ +import io +import textwrap +from typing import Iterable, Callable, Optional, List, Tuple, Sequence + +import pytest + +from debputy.dh_migration.migrators import Migrator +from debputy.dh_migration.migrators_impl import ( + migrate_tmpfile, + migrate_lintian_overrides_files, + detect_pam_files, + migrate_doc_base_files, + migrate_installexamples_file, + migrate_installdocs_file, + migrate_install_file, + migrate_maintscript, + migrate_links_files, + detect_dh_addons, + migrate_not_installed_file, + migrate_installman_file, + migrate_bash_completion, + migrate_installinfo_file, + migrate_dh_installsystemd_files, + detect_obsolete_substvars, + MIGRATION_TARGET_DH_DEBPUTY, + MIGRATION_TARGET_DH_DEBPUTY_RRR, + detect_dh_addons_zz_debputy_rrr, +) +from debputy.dh_migration.models import ( + FeatureMigration, + AcceptableMigrationIssues, + UnsupportedFeature, +) +from debputy.highlevel_manifest import HighLevelManifest +from debputy.highlevel_manifest_parser import YAMLManifestParser +from debputy.plugin.api import virtual_path_def, VirtualPath +from debputy.plugin.api.test_api import ( + build_virtual_file_system, +) + + +DEBIAN_DIR_ENTRY = virtual_path_def(".", fs_path="/nowhere/debian") + + +@pytest.fixture() +def manifest_parser_pkg_foo_factory( + amd64_dpkg_architecture_variables, + dpkg_arch_query, + source_package, + package_single_foo_arch_all_cxt_amd64, + amd64_substitution, + no_profiles_or_build_options, + debputy_plugin_feature_set, +) -> Callable[[], YAMLManifestParser]: + # We need an empty directory to avoid triggering packager provided files. + debian_dir = build_virtual_file_system([]) + + def _factory(): + return YAMLManifestParser( + "debian/test-debputy.manifest", + source_package, + package_single_foo_arch_all_cxt_amd64, + amd64_substitution, + amd64_dpkg_architecture_variables, + dpkg_arch_query, + no_profiles_or_build_options, + debputy_plugin_feature_set, + debian_dir=debian_dir, + ) + + return _factory + + +@pytest.fixture(scope="session") +def accept_no_migration_issues() -> AcceptableMigrationIssues: + return AcceptableMigrationIssues(frozenset()) + + +@pytest.fixture(scope="session") +def accept_any_migration_issues() -> AcceptableMigrationIssues: + return AcceptableMigrationIssues(frozenset(["ALL"])) + + +@pytest.fixture +def empty_manifest_pkg_foo( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], +) -> HighLevelManifest: + return manifest_parser_pkg_foo_factory().build_manifest() + + +def run_migrator( + migrator: Migrator, + path: VirtualPath, + manifest: HighLevelManifest, + acceptable_migration_issues: AcceptableMigrationIssues, + *, + migration_target=MIGRATION_TARGET_DH_DEBPUTY, +) -> FeatureMigration: + feature_migration = FeatureMigration(migrator.__name__) + migrator( + path, + manifest, + acceptable_migration_issues, + feature_migration, + migration_target, + ) + return feature_migration + + +def _assert_unsupported_feature( + migrator: Migrator, + path: VirtualPath, + manifest: HighLevelManifest, + acceptable_migration_issues: AcceptableMigrationIssues, +): + with pytest.raises(UnsupportedFeature) as e: + run_migrator(migrator, path, manifest, acceptable_migration_issues) + return e + + +def _write_manifest(manifest: HighLevelManifest) -> str: + with io.StringIO() as fd: + manifest.mutable_manifest.write_to(fd) + return fd.getvalue() + + +def _verify_migrator_generates_parsable_manifest( + migrator: Migrator, + parser_factory: Callable[[], YAMLManifestParser], + acceptable_migration_issues: AcceptableMigrationIssues, + dh_config_name: str, + dh_config_content: str, + expected_manifest_content: str, + expected_warnings: Optional[List[str]] = None, + expected_renamed_paths: Optional[List[Tuple[str, str]]] = None, + expected_removals: Optional[List[str]] = None, + required_plugins: Optional[Sequence[str]] = tuple(), + dh_config_mode: Optional[int] = None, +) -> None: + # No file, no changes + empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + migration = run_migrator( + migrator, + empty_fs, + parser_factory().build_manifest(), + acceptable_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + if dh_config_mode is None: + if dh_config_content.startswith(("#!/usr/bin/dh-exec", "#! /usr/bin/dh-exec")): + dh_config_mode = 0o755 + else: + dh_config_mode = 0o644 + + # Test with a dh_config file now + fs_w_dh_config = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + dh_config_name, + fs_path=f"/nowhere/debian/{dh_config_name}", + content=dh_config_content, + mode=dh_config_mode, + ), + ] + ) + manifest = parser_factory().build_manifest() + + migration = run_migrator( + migrator, + fs_w_dh_config, + manifest, + acceptable_migration_issues, + ) + + assert migration.anything_to_do + if expected_warnings is not None: + assert migration.warnings == expected_warnings + else: + assert not migration.warnings + assert migration.remove_paths_on_success == [f"/nowhere/debian/{dh_config_name}"] + if expected_removals is None: + assert migration.remove_paths_on_success == [ + f"/nowhere/debian/{dh_config_name}" + ] + else: + assert migration.remove_paths_on_success == expected_removals + if expected_renamed_paths is not None: + assert migration.rename_paths_on_success == expected_renamed_paths + else: + assert not migration.rename_paths_on_success + assert tuple(migration.required_plugins) == tuple(required_plugins) + actual_manifest = _write_manifest(manifest) + assert actual_manifest == expected_manifest_content + + # Verify that the output is actually parsable + parser_factory().parse_manifest(fd=actual_manifest) + + +def test_migrate_tmpfile( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = migrate_tmpfile + empty_debian_dir = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + migration = run_migrator( + migrator, + empty_debian_dir, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + tmpfile_debian_dir = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def("tmpfile", fs_path="/nowhere/debian/tmpfile"), + ] + ) + + migration = run_migrator( + migrator, + tmpfile_debian_dir, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert migration.rename_paths_on_success == [ + ("/nowhere/debian/tmpfile", "/nowhere/debian/tmpfiles") + ] + + tmpfile_debian_dir = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + # Use real paths here to make `cmp -s` discover that they are the same + virtual_path_def("tmpfile", fs_path="debian/control"), + virtual_path_def("tmpfiles", fs_path="debian/control"), + ] + ) + + migration = run_migrator( + migrator, + tmpfile_debian_dir, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + assert migration.anything_to_do + assert not migration.warnings + assert migration.remove_paths_on_success == ["debian/control"] + assert not migration.rename_paths_on_success + + conflict_tmpfile_debian_dir = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + # Use real paths here to make `cmp -s` discover a difference + virtual_path_def("tmpfile", fs_path="debian/control"), + virtual_path_def("tmpfiles", fs_path="debian/changelog"), + ] + ) + + migration = run_migrator( + migrator, + conflict_tmpfile_debian_dir, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert len(migration.warnings) == 1 + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + conflict_tmpfile_debian_dir = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def("tmpfile", fs_path="/nowhere/debian/tmpfile"), + virtual_path_def("tmpfiles/", fs_path="/nowhere/debian/tmpfiles"), + ] + ) + + migration = run_migrator( + migrator, + conflict_tmpfile_debian_dir, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert len(migration.warnings) == 1 + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + conflict_tmpfile_debian_dir = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "tmpfile", + link_target="/nowhere/debian/tmpfiles", + fs_path="/nowhere/debian/tmpfile", + ), + ] + ) + + migration = run_migrator( + migrator, + conflict_tmpfile_debian_dir, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert len(migration.warnings) == 1 + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + +def test_migrate_lintian_overrides_files( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, + accept_any_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = migrate_lintian_overrides_files + no_override_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + single_noexec_override_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "foo.lintian-overrides", + fs_path="/nowhere/no-exec/debian/foo.lintian-overrides", + ), + ] + ) + single_exec_override_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "foo.lintian-overrides", + fs_path="/nowhere/exec/debian/foo.lintian-overrides", + mode=0o755, + ), + ] + ) + for no_issue_fs in [no_override_fs, single_noexec_override_fs]: + migration = run_migrator( + migrator, + no_issue_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + _assert_unsupported_feature( + migrator, + single_exec_override_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + migration = run_migrator( + migrator, + single_exec_override_fs, + empty_manifest_pkg_foo, + accept_any_migration_issues, + ) + + assert migration.anything_to_do + assert len(migration.warnings) == 1 + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + +def test_detect_pam_files( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_pam_files + empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + pam_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def("pam", fs_path="/nowhere/debian/foo.pam"), + ] + ) + + migration = run_migrator( + migrator, + empty_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert migration.assumed_compat is None + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + migration = run_migrator( + migrator, + pam_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert migration.assumed_compat == 14 + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + +def test_migrate_doc_base_files( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = migrate_doc_base_files + empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + doc_base_ok_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def("doc-base", fs_path="/nowhere/debian/doc-base"), + virtual_path_def( + "foo.doc-base.EX", fs_path="/nowhere/debian/foo.doc-base.EX" + ), + ] + ) + doc_base_migration_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "foo.doc-base.bar", fs_path="/nowhere/debian/foo.doc-base.bar" + ), + ] + ) + + for no_change_fs in [empty_fs, doc_base_ok_fs]: + migration = run_migrator( + migrator, + no_change_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + migration = run_migrator( + migrator, + doc_base_migration_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert migration.rename_paths_on_success == [ + ("/nowhere/debian/foo.doc-base.bar", "/nowhere/debian/foo.bar.doc-base") + ] + + +def test_migrate_dh_installsystemd_files( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = migrate_dh_installsystemd_files + empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + files_ok_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def("@service", fs_path="/nowhere/debian/@service"), + virtual_path_def("foo.@service", fs_path="/nowhere/debian/foo.@service"), + ] + ) + migration_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def("foo@.service", fs_path="/nowhere/debian/foo@.service"), + ] + ) + + for no_change_fs in [empty_fs, files_ok_fs]: + migration = run_migrator( + migrator, + no_change_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + + migration = run_migrator( + migrator, + migration_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert migration.rename_paths_on_success == [ + ("/nowhere/debian/foo@.service", "/nowhere/debian/foo.@service") + ] + + +def test_migrate_installexamples_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/* + bar + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-examples: + sources: + - foo/* + - bar + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_installexamples_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "examples", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_installinfo_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/* + bar + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-docs: + sources: + - foo/* + - bar + dest-dir: '{{path:GNU_INFO_DIR}}' + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_installinfo_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "info", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_installinfo_file_conditionals( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + #!/usr/bin/dh-exec + foo/* <!pkg.foo.noinfo> + bar <!pkg.foo.noinfo> + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-docs: + sources: + - foo/* + - bar + dest-dir: '{{path:GNU_INFO_DIR}}' + when: + build-profiles-matches: <!pkg.foo.noinfo> + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_installinfo_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "info", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_installexamples_file_single_source( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/* + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-examples: + source: foo/* + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_installexamples_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "examples", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_installdocs_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/* + bar + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-docs: + sources: + - foo/* + - bar + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_installdocs_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "docs", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_installdocs_file_single_source( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/* + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-docs: + source: foo/* + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_installdocs_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "docs", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + bar usr/bin + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + source: bar + dest-dir: usr/bin + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file_conditionals_simple_arch( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + #!/usr/bin/dh-exec + bar usr/bin [linux-any] + foo usr/bin [linux-any] + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - bar + - foo + dest-dir: usr/bin + when: + arch-matches: linux-any + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file_util_linux_locales( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + # Parts of the `d/util-linux-locales.install` file. It uses d/tmp for most of its paths + # and that breaks the default dest-dir (dh_install always strips --sourcedir, `debputy` + # currently does not) + dh_config_content = textwrap.dedent( + """\ + #!/usr/bin/dh-exec + usr/share/locale/*/*/util-linux.mo + + # bsdextrautils + debian/tmp/usr/share/man/*/man1/col.1 <!nodoc> + + debian/tmp/usr/share/man/*/man3/libblkid.3 <!nodoc> + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - usr/share/man/*/man1/col.1 + - usr/share/man/*/man3/libblkid.3 + when: + build-profiles-matches: <!nodoc> + - install: + source: usr/share/locale/*/*/util-linux.mo + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file_conditionals_simple_combined_cond( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + for cond in ["<!foo> <!bar> [linux-any]", "[linux-any] <!foo> <!bar>"]: + dh_config_content = textwrap.dedent( + """\ + #!/usr/bin/dh-exec + bar usr/bin {CONDITION} + foo usr/bin {CONDITION} + """ + ).format(CONDITION=cond) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - bar + - foo + dest-dir: usr/bin + when: + all-of: + - arch-matches: linux-any + - build-profiles-matches: <!foo> <!bar> + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file_conditionals_unknown_subst( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_any_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + #!/usr/bin/dh-exec + bar ${unknown_substvar} + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + definitions: + variables: + unknown_substvar: 'TODO: Provide variable value for unknown_substvar' + installations: + - install: + source: bar + dest-dir: '{{unknown_substvar}}' + """ + ) + expected_warning = ( + "TODO: MANUAL MIGRATION of unresolved substitution variable {{unknown_substvar}}" + ' from ./install line 2 token "${unknown_substvar}"' + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_any_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + expected_warnings=[expected_warning], + ) + + +def test_migrate_install_file_multidest( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + # Issue #66 + # - observed in kafs-client / the original install file copied in here. + + src/aklog-kafs usr/bin + src/kafs-check-config usr/bin + # + src/kafs-preload usr/sbin + # + src/kafs-dns usr/libexec + # + conf/cellservdb.conf usr/share/kafs-client + conf/client.conf etc/kafs + # + conf/kafs_dns.conf etc/request-key.d + # + conf/cellservdb.conf usr/share/kafs + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - src/aklog-kafs + - src/kafs-check-config + dest-dir: usr/bin + - install: + source: src/kafs-preload + dest-dir: usr/sbin + - install: + source: src/kafs-dns + dest-dir: usr/libexec + - install: + source: conf/client.conf + dest-dir: etc/kafs + - install: + source: conf/kafs_dns.conf + dest-dir: etc/request-key.d + - multi-dest-install: + source: conf/cellservdb.conf + dest-dirs: + - usr/share/kafs-client + - usr/share/kafs + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file_multidest_default_dest( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + # Relaed to issue #66 - testing corner case not present in the original install file + + src/aklog-kafs usr/bin + src/kafs-check-config usr/bin + # + src/kafs-preload usr/sbin + # + src/kafs-dns usr/libexec + # + usr/share/kafs-client/cellservdb.conf + conf/client.conf etc/kafs + # + conf/kafs_dns.conf etc/request-key.d + # + usr/share/kafs-client/cellservdb.conf usr/share/kafs + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - src/aklog-kafs + - src/kafs-check-config + dest-dir: usr/bin + - install: + source: src/kafs-preload + dest-dir: usr/sbin + - install: + source: src/kafs-dns + dest-dir: usr/libexec + - install: + source: conf/client.conf + dest-dir: etc/kafs + - install: + source: conf/kafs_dns.conf + dest-dir: etc/request-key.d + - multi-dest-install: + source: usr/share/kafs-client/cellservdb.conf + dest-dirs: + - usr/share/kafs + - usr/share/kafs-client + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_install_file_multidest_default_dest_warning( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + # Relaed to issue #66 - testing corner case not present in the original install file + + src/aklog-kafs usr/bin + src/kafs-check-config usr/bin + # + src/kafs-preload usr/sbin + # + src/kafs-dns usr/libexec + # + usr/share/kafs-*/cellservdb.conf + conf/client.conf etc/kafs + # + conf/kafs_dns.conf etc/request-key.d + # + usr/share/kafs-*/cellservdb.conf usr/share/kafs + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - src/aklog-kafs + - src/kafs-check-config + dest-dir: usr/bin + - install: + source: src/kafs-preload + dest-dir: usr/sbin + - install: + source: src/kafs-dns + dest-dir: usr/libexec + - install: + source: conf/client.conf + dest-dir: etc/kafs + - install: + source: conf/kafs_dns.conf + dest-dir: etc/request-key.d + - multi-dest-install: + source: usr/share/kafs-*/cellservdb.conf + dest-dirs: + - usr/share/kafs + - 'FIXME: usr/share/kafs-* (could not reliably compute the dest dir)' + """ + ) + warnings = [ + "TODO: FIXME left in dest-dir(s) of some installation rules." + " Please review these and remove the FIXME (plus correct as necessary)" + ] + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + expected_warnings=warnings, + ) + + +def test_migrate_installman_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + man/foo.1 man/bar.1 + man2/*.2 + man3/bar.3 man3/bar.de.3 + man/de/man3/bar.pl.3 + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install-man: + sources: + - man/foo.1 + - man/bar.1 + - man2/*.2 + - man/de/man3/bar.pl.3 + - install-man: + sources: + - man3/bar.3 + - man3/bar.de.3 + language: derive-from-basename + """ + ) + expected_warnings = [ + 'Detected manpages that might rely on "derive-from-basename" logic. Please double check' + " that the generated `install-man` rules are correct" + ] + _verify_migrator_generates_parsable_manifest( + migrate_installman_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "manpages", + dh_config_content, + expected_manifest_content, + expected_warnings=expected_warnings, + ) + + +def test_migrate_install_dh_exec_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + #!/usr/bin/dh-exec + + foo/script.sh => usr/bin/script + => usr/bin/bar + usr/bin/* usr/share/foo/extra/* usr/share/foo/extra + another-util usr/share/foo/extra + # This will not be merged with `=> usr/bin/bar` + usr/share/foo/features + usr/share/foo/bugs + some-file.txt usr/share/foo/online-doc + # TODO: Support migration of these + # pathA pathB conditional/arch [linux-anx] + # <!pkg.foo.condition> another-path conditional/profile + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + source: usr/bin/bar + - install: + source: foo/script.sh + as: usr/bin/script + - install: + sources: + - usr/bin/* + - usr/share/foo/extra/* + - another-util + dest-dir: usr/share/foo/extra + - install: + source: some-file.txt + dest-dir: usr/share/foo/online-doc + - install: + sources: + - usr/share/foo/features + - usr/share/foo/bugs + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_install_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "install", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_maintscript( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + rm_conffile /etc/foo.conf + mv_conffile /etc/bar.conf /etc/new-foo.conf 1.0~ bar + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + packages: + foo: + conffile-management: + - remove: + path: /etc/foo.conf + - rename: + source: /etc/bar.conf + target: /etc/new-foo.conf + prior-to-version: 1.0~ + owning-package: bar + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_maintscript, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "maintscript", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_not_installed_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/*.txt bar/${DEB_HOST_MULTIARCH}/*.so* + baz/script.sh + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - discard: + - foo/*.txt + - bar/{{DEB_HOST_MULTIARCH}}/*.so* + - baz/script.sh + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_not_installed_file, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "not-installed", + dh_config_content, + expected_manifest_content, + ) + + +def test_migrate_links_files( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + usr/share/target usr/bin/symlink + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + packages: + foo: + transformations: + - create-symlink: + path: usr/bin/symlink + target: /usr/share/target + """ + ) + _verify_migrator_generates_parsable_manifest( + migrate_links_files, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "links", + dh_config_content, + expected_manifest_content, + ) + + +def test_detect_obsolete_substvars( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_obsolete_substvars + + dctrl_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-debputy, + dh-sequence-foo, + + Package: foo + Architecture: any + Description: ... + Depends: ${misc:Depends}, + ${shlibs:Depends}, + bar (>= 1.0~) | baz, ${so:Depends}, + """ + ) + dctrl_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control", + content=dctrl_content, + ), + ] + ) + + migration = run_migrator( + migrator, + dctrl_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + msg = ( + "The following relationship substitution variables can be removed from foo:" + " ${misc:Depends}, ${shlibs:Depends}, ${so:Depends}" + ) + assert migration.anything_to_do + assert migration.warnings == [msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + +def test_detect_obsolete_substvars_remove_field( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_obsolete_substvars + + dctrl_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-debputy, + dh-sequence-foo, + + Package: foo + Architecture: any + Description: ... + Pre-Depends: ${misc:Pre-Depends} + Depends: bar (>= 1.0~) | baz + """ + ) + dctrl_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control", + content=dctrl_content, + ), + ] + ) + + migration = run_migrator( + migrator, + dctrl_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + msg = ( + "The following relationship fields can be removed from foo: Pre-Depends." + " (The content in them would be applied automatically.)" + ) + assert migration.anything_to_do + assert migration.warnings == [msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + +def test_detect_obsolete_substvars_remove_field_essential( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_obsolete_substvars + + dctrl_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-debputy, + dh-sequence-foo, + + Package: foo + Architecture: any + Description: ... + Essential: yes + # Obsolete because the package is essential + Pre-Depends: ${shlibs:Depends} + Depends: bar (>= 1.0~) | baz + """ + ) + dctrl_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control", + content=dctrl_content, + ), + ] + ) + + migration = run_migrator( + migrator, + dctrl_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + msg = ( + "The following relationship fields can be removed from foo: Pre-Depends." + " (The content in them would be applied automatically.)" + ) + assert migration.anything_to_do + assert migration.warnings == [msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + +def test_detect_obsolete_substvars_remove_field_non_essential( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_obsolete_substvars + + dctrl_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-debputy, + dh-sequence-foo, + + Package: foo + Architecture: any + Description: ... + # This is not obsolete since the package is not essential + Pre-Depends: ${shlibs:Depends} + Depends: bar (>= 1.0~) | baz + """ + ) + dctrl_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control", + content=dctrl_content, + ), + ] + ) + + migration = run_migrator( + migrator, + dctrl_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + +def test_detect_dh_addons( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, + accept_any_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_dh_addons + empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + + dctrl_no_addons_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13) + + Package: foo + Architecture: all + Description: ... + """ + ) + + dctrl_w_addons_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-debputy, + dh-sequence-foo, + + Package: foo + Architecture: all + Description: ... + """ + ) + + dctrl_w_migrateable_addons_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-debputy, + dh-sequence-numpy3, + + Package: foo + Architecture: all + Description: ... + """ + ) + + dctrl_no_addons_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control-without-addons", + content=dctrl_no_addons_content, + ), + ] + ) + dctrl_w_addons_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control-with-addons", + content=dctrl_w_addons_content, + ), + ] + ) + dctrl_w_migrateable_addons_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control-with-migrateable-addons", + content=dctrl_w_migrateable_addons_content, + ), + ] + ) + no_ctrl_msg = ( + "Cannot find debian/control. Detection of unsupported/missing dh-sequence addon" + " could not be performed. Please ensure the package will Build-Depend on dh-sequence-zz-debputy" + " and not rely on any other debhelper sequence addons except those debputy explicitly supports." + ) + missing_debputy_bd_msg = "Missing Build-Depends on dh-sequence-zz-debputy" + unsupported_sequence_msg = ( + 'The dh addon "foo" is not known to work with dh-debputy and might malfunction' + ) + + migration = run_migrator( + migrator, + empty_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + assert migration.anything_to_do + assert migration.warnings == [no_ctrl_msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + migration = run_migrator( + migrator, + dctrl_no_addons_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + assert migration.anything_to_do + assert migration.warnings == [missing_debputy_bd_msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + _assert_unsupported_feature( + migrator, + dctrl_w_addons_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + migration = run_migrator( + migrator, + dctrl_w_addons_fs, + empty_manifest_pkg_foo, + accept_any_migration_issues, + ) + + assert migration.anything_to_do + assert migration.warnings == [unsupported_sequence_msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + migration = run_migrator( + migrator, + dctrl_w_migrateable_addons_fs, + empty_manifest_pkg_foo, + accept_any_migration_issues, + ) + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert migration.required_plugins == ["numpy3"] + + +def test_detect_dh_addons_rrr( + empty_manifest_pkg_foo: HighLevelManifest, + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + migrator = detect_dh_addons_zz_debputy_rrr + empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY]) + + dctrl_no_addons_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13) + + Package: foo + Architecture: all + Description: ... + """ + ) + + dctrl_w_addons_content = textwrap.dedent( + """\ + Source: foo + Build-Depends: debhelper-compat (= 13), + dh-sequence-zz-debputy-rrr, + dh-sequence-foo, + + Package: foo + Architecture: all + Description: ... + """ + ) + + dctrl_no_addons_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control-without-addons", + content=dctrl_no_addons_content, + ), + ] + ) + dctrl_w_addons_fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + "control", + fs_path="/nowhere/debian/control-with-addons", + content=dctrl_w_addons_content, + ), + ] + ) + no_ctrl_msg = ( + "Cannot find debian/control. Detection of unsupported/missing dh-sequence addon" + " could not be performed. Please ensure the package will Build-Depend on dh-sequence-zz-debputy-rrr." + ) + missing_debputy_bd_msg = "Missing Build-Depends on dh-sequence-zz-debputy-rrr" + + migration = run_migrator( + migrator, + empty_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + migration_target=MIGRATION_TARGET_DH_DEBPUTY_RRR, + ) + assert migration.anything_to_do + assert migration.warnings == [no_ctrl_msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + migration = run_migrator( + migrator, + dctrl_no_addons_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + assert migration.anything_to_do + assert migration.warnings == [missing_debputy_bd_msg] + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + migration = run_migrator( + migrator, + dctrl_w_addons_fs, + empty_manifest_pkg_foo, + accept_no_migration_issues, + ) + + assert not migration.anything_to_do + assert not migration.warnings + assert not migration.remove_paths_on_success + assert not migration.rename_paths_on_success + assert not migration.required_plugins + + +def test_migrate_bash_completion_file_no_changes( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + compgen -A + """ + ) + dh_config_name = "bash-completion" + fs = build_virtual_file_system( + [ + DEBIAN_DIR_ENTRY, + virtual_path_def( + dh_config_name, + fs_path=f"/nowhere/debian/{dh_config_name}", + content=dh_config_content, + ), + ] + ) + migration = run_migrator( + migrate_bash_completion, + fs, + manifest_parser_pkg_foo_factory().build_manifest(), + accept_no_migration_issues, + ) + assert not migration.rename_paths_on_success + assert not migration.remove_paths_on_success + assert not migration.warnings + assert not migration.required_plugins + + +def test_migrate_bash_completion_file( + manifest_parser_pkg_foo_factory: Callable[[], YAMLManifestParser], + accept_no_migration_issues: AcceptableMigrationIssues, +) -> None: + dh_config_content = textwrap.dedent( + """\ + foo/* + bar baz + debian/bar-completion bar + debian/foo-completion foo + debian/*.cmpl + """ + ) + expected_manifest_content = textwrap.dedent( + """\ + manifest-version: '0.1' + installations: + - install: + sources: + - foo/* + - debian/*.cmpl + dest-dir: '{{path:BASH_COMPLETION_DIR}}' + - install: + source: bar + as: '{{path:BASH_COMPLETION_DIR}}/baz' + """ + ) + expected_renames = [ + ("debian/bar-completion", "debian/foo.bar.bash-completion"), + ("debian/foo-completion", "debian/foo.bash-completion"), + ] + _verify_migrator_generates_parsable_manifest( + migrate_bash_completion, + manifest_parser_pkg_foo_factory, + accept_no_migration_issues, + "bash-completion", + dh_config_content, + expected_manifest_content, + expected_renamed_paths=expected_renames, + ) |