summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/lint_tests/lint_tutil.py30
-rw-r--r--tests/lint_tests/test_lint_dctrl.py444
-rw-r--r--tests/lsp_tests/test_lsp_debputy_manifest_completer.py4
-rw-r--r--tests/test_debputy_plugin.py136
-rw-r--r--tests/test_declarative_parser.py9
-rw-r--r--tests/test_fs_metadata.py115
-rw-r--r--tests/test_install_rules.py39
-rw-r--r--tests/test_migrations.py13
-rw-r--r--tests/test_parser.py108
-rw-r--r--tests/test_style.py135
-rw-r--r--tests/test_substitute.py3
-rw-r--r--tests/test_utils.py20
12 files changed, 928 insertions, 128 deletions
diff --git a/tests/lint_tests/lint_tutil.py b/tests/lint_tests/lint_tutil.py
index 267f669..c16fde3 100644
--- a/tests/lint_tests/lint_tutil.py
+++ b/tests/lint_tests/lint_tutil.py
@@ -1,5 +1,5 @@
import collections
-from typing import List, Optional, Mapping, Any, Callable
+from typing import List, Optional, Mapping, Any, Callable, Sequence
import pytest
@@ -9,11 +9,14 @@ from debputy.linting.lint_util import (
LintStateImpl,
LintState,
)
-from debputy.lsp.style_prefs import StylePreferenceTable, EffectivePreference
+from debputy.lsp.maint_prefs import (
+ MaintainerPreferenceTable,
+ EffectiveFormattingPreference,
+)
from debputy.packages import DctrlParser
from debputy.plugin.api.feature_set import PluginProvidedFeatureSet
-from debputy.lsprotocol.types import Diagnostic, DiagnosticSeverity
+from debputy.lsprotocol.types import Diagnostic, DiagnosticSeverity, Range
try:
@@ -42,8 +45,8 @@ class LintWrapper:
self.path = path
self._dctrl_parser = dctrl_parser
self.source_root: Optional[VirtualPathBase] = None
- self.lint_style_preference_table = StylePreferenceTable({}, {})
- self.effective_preference: Optional[EffectivePreference] = None
+ self.lint_maint_preference_table = MaintainerPreferenceTable({}, {})
+ self.effective_preference: Optional[EffectiveFormattingPreference] = None
def __call__(self, lines: List[str]) -> Optional[List["Diagnostic"]]:
source_package = None
@@ -59,7 +62,7 @@ class LintWrapper:
debian_dir = source_root.get("debian") if source_root is not None else None
state = LintStateImpl(
self._debputy_plugin_feature_set,
- self.lint_style_preference_table,
+ self.lint_maint_preference_table,
source_root,
debian_dir,
self.path,
@@ -108,3 +111,18 @@ def group_diagnostics_by_severity(
by_severity[severity].append(diagnostic)
return by_severity
+
+
+def diag_range_to_text(lines: Sequence[str], range_: "Range") -> str:
+ parts = []
+ for line_no in range(range_.start.line, range_.end.line + 1):
+ line = lines[line_no]
+ chunk = line
+ if line_no == range_.start.line and line_no == range_.end.line:
+ chunk = line[range_.start.character : range_.end.character]
+ elif line_no == range_.start.line:
+ chunk = line[range_.start.character :]
+ elif line_no == range_.end.line:
+ chunk = line[: range_.end.character]
+ parts.append(chunk)
+ return "".join(parts)
diff --git a/tests/lint_tests/test_lint_dctrl.py b/tests/lint_tests/test_lint_dctrl.py
index 745c323..229acc1 100644
--- a/tests/lint_tests/test_lint_dctrl.py
+++ b/tests/lint_tests/test_lint_dctrl.py
@@ -13,6 +13,7 @@ from lint_tests.lint_tutil import (
group_diagnostics_by_severity,
requires_levenshtein,
LintWrapper,
+ diag_range_to_text,
)
from debputy.lsprotocol.types import Diagnostic, DiagnosticSeverity
@@ -732,7 +733,63 @@ def test_dctrl_lint_ambiguous_pkgfile(line_linter: LintWrapper) -> None:
# FIXME: This relies on "cwd" being a valid debian directory using debhelper. Fix and
# remove the `build_time_only` restriction
- line_linter.source_root = build_virtual_file_system(["./debian/bar.install"])
+ line_linter.source_root = build_virtual_file_system(
+ [
+ virtual_path_def(".", fs_path="."),
+ "./debian/bar.service",
+ ]
+ )
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+
+ msg = (
+ 'Possible typo in "./debian/bar.service". Consider renaming the file to "debian/foo.service"'
+ ' (or maybe "debian/foo.bar.service") if it is intended for foo'
+ )
+ assert issue.message == msg
+ assert f"{issue.range}" == "7:0-8:0"
+ assert issue.severity == DiagnosticSeverity.Warning
+ diag_data = issue.data
+ assert isinstance(diag_data, dict)
+ assert diag_data.get("report_for_related_file") in (
+ "./debian/bar.service",
+ "debian/bar.service",
+ )
+
+
+@build_time_only
+def test_dctrl_lint_ambiguous_pkgfile_no_name_segment(line_linter: LintWrapper) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13), dh-sequence-zz-debputy,
+
+ Package: foo
+ Architecture: all
+ Depends: bar, baz
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ # FIXME: This relies on "cwd" being a valid debian directory using debhelper. Fix and
+ # remove the `build_time_only` restriction
+ line_linter.source_root = build_virtual_file_system(
+ [
+ virtual_path_def(".", fs_path="."),
+ "./debian/bar.alternatives",
+ ]
+ )
diagnostics = line_linter(lines)
print(diagnostics)
@@ -740,8 +797,8 @@ def test_dctrl_lint_ambiguous_pkgfile(line_linter: LintWrapper) -> None:
issue = diagnostics[0]
msg = (
- 'Possible typo in "./debian/bar.install". Consider renaming the file to "debian/foo.bar.install"'
- ' or "debian/foo.install if it is intended for foo'
+ 'Possible typo in "./debian/bar.alternatives". Consider renaming the file to "debian/foo.alternatives"'
+ " if it is intended for foo"
)
assert issue.message == msg
assert f"{issue.range}" == "7:0-8:0"
@@ -749,8 +806,8 @@ def test_dctrl_lint_ambiguous_pkgfile(line_linter: LintWrapper) -> None:
diag_data = issue.data
assert isinstance(diag_data, dict)
assert diag_data.get("report_for_related_file") in (
- "./debian/bar.install",
- "debian/bar.install",
+ "./debian/bar.alternatives",
+ "debian/bar.alternatives",
)
@@ -779,7 +836,12 @@ def test_dctrl_lint_stem_typo_pkgfile(line_linter: LintWrapper) -> None:
# FIXME: This relies on "cwd" being a valid debian directory using debhelper. Fix and
# remove the `build_time_only` restriction
- line_linter.source_root = build_virtual_file_system(["./debian/foo.intsall"])
+ line_linter.source_root = build_virtual_file_system(
+ [
+ virtual_path_def(".", fs_path="."),
+ "./debian/foo.intsall",
+ ]
+ )
diagnostics = line_linter(lines)
print(diagnostics)
@@ -827,6 +889,7 @@ def test_dctrl_lint_stem_inactive_pkgfile_fp(line_linter: LintWrapper) -> None:
# load the `zz-debputy` sequence.
line_linter.source_root = build_virtual_file_system(
[
+ virtual_path_def(".", fs_path="."),
"./debian/foo.install",
virtual_path_def(
"./debian/rules",
@@ -846,3 +909,372 @@ def test_dctrl_lint_stem_inactive_pkgfile_fp(line_linter: LintWrapper) -> None:
print(diagnostics)
# We should not emit diagnostics when the package is not using dh!
assert not diagnostics
+
+
+@requires_levenshtein
+@build_time_only
+def test_dctrl_lint_stem_typo_pkgfile_ignored_exts_or_files(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ Depends: bar, baz
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ # FIXME: This relies on "cwd" being a valid debian directory using debhelper. Fix and
+ # remove the `build_time_only` restriction
+ line_linter.source_root = build_virtual_file_system(
+ [
+ virtual_path_def(".", fs_path="."),
+ "debian/salsa-ci.yml",
+ "debian/gbp.conf",
+ "debian/foo.conf",
+ "debian/foo.sh",
+ "debian/foo.yml",
+ # One wrong one to ensure the test works.
+ "debian/foo.intsall",
+ ]
+ )
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+
+ msg = 'The file "./debian/foo.intsall" is likely a typo of "./debian/foo.install"'
+ assert issue.message == msg
+ assert f"{issue.range}" == "7:0-8:0"
+ assert issue.severity == DiagnosticSeverity.Warning
+ diag_data = issue.data
+ assert isinstance(diag_data, dict)
+ assert diag_data.get("report_for_related_file") in (
+ "./debian/foo.intsall",
+ "debian/foo.intsall",
+ )
+
+
+def test_dctrl_lint_dep_field_missing_sep(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ Depends: bar, baz
+ # Missing separator between baz and libfubar1
+ libfubar1,
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+ msg = (
+ "Trailing data after a relationship that might be a second relationship."
+ " Is a separator missing before this part?"
+ )
+ problem_text = diag_range_to_text(lines, issue.range)
+ assert issue.message == msg
+ assert problem_text == "libfubar1"
+ assert f"{issue.range}" == "11:1-11:10"
+ assert issue.severity == DiagnosticSeverity.Error
+
+
+def test_dctrl_lint_dep_field_missing_sep_or_syntax_error(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ Depends: bar, baz
+ # Missing separator between baz and libfubar1
+ _libfubar1,
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+ msg = "Parse error of the relationship. Either a syntax error or a missing separator somewhere."
+ problem_text = diag_range_to_text(lines, issue.range)
+ assert issue.message == msg
+ assert problem_text == "_libfubar1"
+ assert f"{issue.range}" == "11:1-11:11"
+ assert issue.severity == DiagnosticSeverity.Error
+
+
+def test_dctrl_lint_dep_field_completely_busted(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ Depends: bar, baz, _asd
+ # This is just busted
+ _libfubar1,
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+ msg = 'Could not parse "_asd _libfubar1" as a dependency relation.'
+ problem_text = diag_range_to_text(lines, issue.range)
+ expected_problem_text = "\n".join((" _asd", "# This is just busted", " _libfubar1"))
+ assert issue.message == msg
+ assert problem_text == expected_problem_text
+ assert f"{issue.range}" == "9:18-11:11"
+ assert issue.severity == DiagnosticSeverity.Error
+
+
+def test_dctrl_lint_dep_field_completely_busted_first_line(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ # A wild field comment appeared!
+ Depends: _bar,
+ asd,
+ # This is fine (but the _bar part is not)
+ libfubar1,
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+ msg = 'Could not parse "_bar" as a dependency relation.'
+ problem_text = diag_range_to_text(lines, issue.range)
+ assert issue.message == msg
+ assert problem_text == " _bar"
+ assert f"{issue.range}" == "10:8-10:13"
+ assert issue.severity == DiagnosticSeverity.Error
+
+
+def test_dctrl_lint_dep_field_restricted_operator(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ # Some random field comment
+ Provides: bar (>= 2),
+ bar
+ # Inline comment to spice up things
+ (<= 1),
+ # This one is valid
+ fubar (= 2),
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 2
+ first_issue, second_issue = diagnostics
+
+ msg = 'The version operator ">=" is not allowed in Provides'
+ problem_text = diag_range_to_text(lines, first_issue.range)
+ assert first_issue.message == msg
+ assert problem_text == ">="
+ assert f"{first_issue.range}" == "10:15-10:17"
+ assert first_issue.severity == DiagnosticSeverity.Error
+
+ msg = 'The version operator "<=" is not allowed in Provides'
+ problem_text = diag_range_to_text(lines, second_issue.range)
+ assert second_issue.message == msg
+ assert problem_text == "<="
+ assert f"{second_issue.range}" == "13:2-13:4"
+ assert second_issue.severity == DiagnosticSeverity.Error
+
+
+def test_dctrl_lint_dep_field_restricted_or_relations(
+ line_linter: LintWrapper,
+) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: foo
+ Section: devel
+ Priority: optional
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Maintainer: Jane Developer <jane@example.com>
+ Build-Depends: debhelper-compat (= 13)
+
+ Package: foo
+ Architecture: all
+ Depends: pkg-a
+ | pkg-b
+ # What goes in Depends do not always work in Provides
+ Provides: foo-a
+ | foo-b
+ Description: some short synopsis
+ A very interesting description
+ with a valid synopsis
+ .
+ Just so be clear, this is for a test.
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ print(diagnostics)
+ assert diagnostics and len(diagnostics) == 1
+ issue = diagnostics[0]
+
+ msg = 'The field Provides does not support "|" (OR) in relations.'
+ problem_text = diag_range_to_text(lines, issue.range)
+ assert issue.message == msg
+ assert problem_text == "|"
+ assert f"{issue.range}" == "13:1-13:2"
+ assert issue.severity == DiagnosticSeverity.Error
+
+
+def test_dctrl_duplicate_key(line_linter: LintWrapper) -> None:
+ lines = textwrap.dedent(
+ f"""\
+ Source: jquery-tablesorter
+ Section: javascript
+ Priority: optional
+ Maintainer: Debian Javascript Maintainers <pkg-javascript-devel@lists.alioth.de\
+ bian.org>
+ Uploaders: Paul Gevers <elbrus@debian.org>
+ Build-Depends:
+ debhelper-compat (=13),
+ grunt,
+ libjs-qunit,
+ node-grunt-contrib-clean,
+ node-grunt-contrib-copy,
+ node-grunt-contrib-uglify,
+ node-grunt-contrib-concat,
+ Standards-Version: {CURRENT_STANDARDS_VERSION}
+ Homepage: https://github.com/Mottie/tablesorter
+ Vcs-Git: https://salsa.debian.org/js-team/jquery-tablesorter.git
+ Vcs-Browser: https://salsa.debian.org/js-team/jquery-tablesorter
+ Rules-Requires-Root: no
+
+ Package: libjs-jquery-tablesorter
+ Architecture: all
+ Multi-Arch: foreign
+ Depends:
+ ${{misc:Depends}},
+ libjs-jquery,
+ libjs-jquery-metadata,
+ Recommends: javascript-common
+ Multi-Arch: foreign
+ Description: jQuery flexible client-side table sorting plugin
+ Tablesorter is a jQuery plugin for turning a standard HTML table with THEAD
+ and TBODY tags into a sortable table without page refreshes. Tablesorter can
+ successfully parse and sort many types of data including linked data in a
+ cell. It has many useful features including:
+ .
+ * Multi-column alphanumeric sorting and filtering.
+ * Multi-tbody sorting
+ * Supports Bootstrap v2-4.
+ * Parsers for sorting text, alphanumeric text, URIs, integers, currency,
+ floats, IP addresses, dates (ISO, long and short formats) and time.
+ Add your own easily.
+ * Inline editing
+ * Support for ROWSPAN and COLSPAN on TH elements.
+ * Support secondary "hidden" sorting (e.g., maintain alphabetical sort when
+ sorting on other criteria).
+ * Extensibility via widget system.
+ * Cross-browser: IE 6.0+, FF 2+, Safari 2.0+, Opera 9.0+, Chrome 5.0+.
+
+ """
+ ).splitlines(keepends=True)
+
+ diagnostics = line_linter(lines)
+ assert len(diagnostics) == 1
+
+ issue = diagnostics[0]
+
+ msg = (
+ "The Multi-Arch field name was used multiple times in this stanza."
+ " Please ensure the field is only used once per stanza. Note that Multi-Arch and"
+ " X[BCS]-Multi-Arch are considered the same field."
+ )
+ assert issue.message == msg
+ assert f"{issue.range}" == "27:0-27:10"
+ assert issue.severity == DiagnosticSeverity.Error
diff --git a/tests/lsp_tests/test_lsp_debputy_manifest_completer.py b/tests/lsp_tests/test_lsp_debputy_manifest_completer.py
index f052164..92056ed 100644
--- a/tests/lsp_tests/test_lsp_debputy_manifest_completer.py
+++ b/tests/lsp_tests/test_lsp_debputy_manifest_completer.py
@@ -200,7 +200,7 @@ def test_basic_debputy_completer_manifest_variable_value(
)
assert isinstance(completions, list)
keywords = {m.label for m in completions}
- assert "0.1" in keywords
+ assert "'0.1'" in keywords
cursor_pos = put_doc_with_cursor(
ls,
@@ -219,7 +219,7 @@ def test_basic_debputy_completer_manifest_variable_value(
)
assert isinstance(completions, list)
keywords = {m.label for m in completions}
- assert "0.1" in keywords
+ assert "'0.1'" in keywords
def test_basic_debputy_completer_install_rule_dispatch_key(
diff --git a/tests/test_debputy_plugin.py b/tests/test_debputy_plugin.py
index dc60597..8a4cc59 100644
--- a/tests/test_debputy_plugin.py
+++ b/tests/test_debputy_plugin.py
@@ -19,6 +19,16 @@ from debputy.plugin.api.test_api import (
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.build_system_rules import (
+ AutoconfBuildSystemRule,
+ MakefileBuildSystemRule,
+ PerlBuildBuildSystemRule,
+ PerlMakeMakerBuildSystemRule,
+ QmakeBuildSystemRule,
+ Qmake6BuildSystemRule,
+ CMakeBuildSystemRule,
+ MesonBuildSystemRule,
+)
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
@@ -1252,3 +1262,129 @@ def test_auto_depends_solink() -> None:
context=context_too_many_matches,
)
assert "misc:Depends" not in sodep_metadata.substvars
+
+
+@pytest.mark.parametrize(
+ "filename,expected,mode,content",
+ [
+ ("configure.ac", True, 0o0644, None),
+ ("configure.in", True, 0o0644, "AC_INIT"),
+ ("configure.in", True, 0o0644, "AC_PREREQ"),
+ ("configure.in", False, 0o0644, "None of the above"),
+ ("configure", True, 0o0755, "GNU Autoconf"),
+ ("configure", False, 0o0644, "GNU Autoconf"),
+ ("configure", False, 0o0755, "No magic keyword"),
+ ("random-file", False, 0o0644, "No configure at all"),
+ ],
+)
+def test_auto_detect_autoconf_build_system(
+ filename: str,
+ expected: bool,
+ mode: int,
+ content: Optional[str],
+) -> None:
+ fs_root = build_virtual_file_system(
+ [virtual_path_def(filename, mode=mode, content=content)]
+ )
+ detected = AutoconfBuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected == expected
+
+
+@pytest.mark.parametrize(
+ "filename,expected",
+ [
+ ("GNUmakefile", True),
+ ("Makefile", True),
+ ("makefile", True),
+ ("random-file", False),
+ ],
+)
+def test_auto_detect_make_build_system(
+ filename: str,
+ expected: bool,
+) -> None:
+ fs_root = build_virtual_file_system([filename])
+ detected = MakefileBuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected == expected
+
+
+@pytest.mark.parametrize(
+ "filename,expected",
+ [
+ ("Build.PL", True),
+ ("random-file", False),
+ ],
+)
+def test_auto_detect_perl_build_build_system(
+ filename: str,
+ expected: bool,
+) -> None:
+ fs_root = build_virtual_file_system([filename])
+ detected = PerlBuildBuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected == expected
+
+
+@pytest.mark.parametrize(
+ "filename,expected",
+ [
+ ("Makefile.PL", True),
+ ("random-file", False),
+ ],
+)
+def test_auto_detect_perl_makemaker_build_system(
+ filename: str,
+ expected: bool,
+) -> None:
+ fs_root = build_virtual_file_system([filename])
+ detected = PerlMakeMakerBuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected == expected
+
+
+@pytest.mark.parametrize(
+ "filename,expected",
+ [
+ ("foo.pro", True),
+ ("random-file", False),
+ ],
+)
+def test_auto_detect_qmake_build_systems(
+ filename: str,
+ expected: bool,
+) -> None:
+ fs_root = build_virtual_file_system([filename])
+ detected_qmake = QmakeBuildSystemRule.auto_detect_build_system(fs_root)
+ detected_qmake6 = Qmake6BuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected_qmake == expected
+ assert detected_qmake6 == expected
+
+
+@pytest.mark.parametrize(
+ "filename,expected",
+ [
+ ("CMakeLists.txt", True),
+ ("random-file", False),
+ ],
+)
+def test_auto_detect_cmake_build_systems(
+ filename: str,
+ expected: bool,
+) -> None:
+ fs_root = build_virtual_file_system([filename])
+ detected = CMakeBuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected == expected
+
+
+@pytest.mark.parametrize(
+ "filename,expected",
+ [
+ ("meson.build", True),
+ ("random-file", False),
+ ],
+)
+def test_auto_detect_meson_build_systems(
+ filename: str,
+ expected: bool,
+) -> None:
+ fs_root = build_virtual_file_system([filename])
+ detected = MesonBuildSystemRule.auto_detect_build_system(fs_root)
+ assert detected == expected
diff --git a/tests/test_declarative_parser.py b/tests/test_declarative_parser.py
index 26291dd..d52f1c3 100644
--- a/tests/test_declarative_parser.py
+++ b/tests/test_declarative_parser.py
@@ -10,11 +10,12 @@ from typing import (
import pytest
from debputy.highlevel_manifest import PackageTransformationDefinition
-from debputy.manifest_parser.base_types import DebputyParsedContent, TypeMapping
-from debputy.manifest_parser.declarative_parser import (
- DebputyParseHint,
- ParserGenerator,
+from debputy.manifest_parser.tagging_types import (
+ DebputyParsedContent,
+ TypeMapping,
)
+from debputy.manifest_parser.parse_hints import DebputyParseHint
+from debputy.manifest_parser.declarative_parser import ParserGenerator
from debputy.manifest_parser.mapper_code import type_mapper_str2package
from debputy.manifest_parser.parser_data import ParserContextData
from debputy.manifest_parser.util import AttributePath
diff --git a/tests/test_fs_metadata.py b/tests/test_fs_metadata.py
index 7dd3d55..3cbbb03 100644
--- a/tests/test_fs_metadata.py
+++ b/tests/test_fs_metadata.py
@@ -132,7 +132,9 @@ def test_mtime_clamp_and_builtin_dir_mode(
verify_paths(intermediate_manifest, path_defs)
-def test_transformations_create_symlink(manifest_parser_pkg_foo):
+def test_transformations_create_symlink(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -178,7 +180,9 @@ def test_transformations_create_symlink(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformations_create_symlink_replace_success(manifest_parser_pkg_foo):
+def test_transformations_create_symlink_replace_success(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -230,8 +234,10 @@ def test_transformations_create_symlink_replace_success(manifest_parser_pkg_foo)
],
)
def test_transformations_create_symlink_replace_failure(
- manifest_parser_pkg_foo, replacement_rule, reason
-):
+ manifest_parser_pkg_foo: YAMLManifestParser,
+ replacement_rule: str,
+ reason: str,
+) -> None:
content = textwrap.dedent(
f"""\
manifest-version: '0.1'
@@ -257,14 +263,15 @@ def test_transformations_create_symlink_replace_failure(
f"Refusing to replace ./usr/share/foo with a symlink; {reason} and the active"
f" replacement-rule was {replacement_rule}. You can set the replacement-rule to"
' "discard-existing", if you are not interested in the contents of ./usr/share/foo. This error'
- " was triggered by packages.foo.transformations[0].create-symlink <Search for: usr/share/foo>."
+ # Ideally, this would be reported for line 5.
+ " was triggered by packages.foo.transformations[0].create-symlink [Line 6 column 18]."
)
assert e_info.value.args[0] == msg
def test_transformations_create_symlink_replace_with_explicit_remove(
- manifest_parser_pkg_foo,
-):
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -299,8 +306,8 @@ def test_transformations_create_symlink_replace_with_explicit_remove(
def test_transformations_create_symlink_replace_with_replacement_rule(
- manifest_parser_pkg_foo,
-):
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -335,7 +342,9 @@ def test_transformations_create_symlink_replace_with_replacement_rule(
verify_paths(intermediate_manifest, expected_results)
-def test_transformations_path_metadata(manifest_parser_pkg_foo):
+def test_transformations_path_metadata(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -382,7 +391,9 @@ def test_transformations_path_metadata(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformations_directories(manifest_parser_pkg_foo):
+def test_transformations_directories(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -453,7 +464,9 @@ def test_transformations_directories(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformation_remove(manifest_parser_pkg_foo):
+def test_transformation_remove(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -489,7 +502,9 @@ def test_transformation_remove(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformation_remove_keep_empty(manifest_parser_pkg_foo):
+def test_transformation_remove_keep_empty(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -529,7 +544,9 @@ def test_transformation_remove_keep_empty(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformation_remove_glob(manifest_parser_pkg_foo):
+def test_transformation_remove_glob(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -581,7 +598,9 @@ def test_transformation_remove_glob(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformation_remove_no_match(manifest_parser_pkg_foo):
+def test_transformation_remove_no_match(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -612,13 +631,15 @@ def test_transformation_remove_no_match(manifest_parser_pkg_foo):
manifest.apply_to_binary_staging_directory("foo", fs_root, claim_mtime_to)
expected = (
'The match rule "./some/non-existing-path" in transformation'
- ' "packages.foo.transformations[0].remove <Search for: some/non-existing-path>" did not match any paths. Either'
+ ' "packages.foo.transformations[0].remove [Line 5 column 18]" did not match any paths. Either'
" the definition is redundant (and can be omitted) or the match rule is incorrect."
)
assert expected == e_info.value.args[0]
-def test_transformation_move_basic(manifest_parser_pkg_foo):
+def test_transformation_move_basic(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -682,7 +703,9 @@ def test_transformation_move_basic(manifest_parser_pkg_foo):
verify_paths(intermediate_manifest, expected_results)
-def test_transformation_move_no_match(manifest_parser_pkg_foo):
+def test_transformation_move_no_match(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
content = textwrap.dedent(
"""\
manifest-version: '0.1'
@@ -715,13 +738,15 @@ def test_transformation_move_no_match(manifest_parser_pkg_foo):
manifest.apply_to_binary_staging_directory("foo", fs_root, claim_mtime_to)
expected = (
'The match rule "./some/non-existing-path" in transformation'
- ' "packages.foo.transformations[0].move <Search for: some/non-existing-path>" did not match any paths. Either'
+ ' "packages.foo.transformations[0].move [Line 6 column 12]" did not match any paths. Either'
" the definition is redundant (and can be omitted) or the match rule is incorrect."
)
assert expected == e_info.value.args[0]
-def test_builtin_mode_normalization(manifest_parser_pkg_foo):
+def test_builtin_mode_normalization_shell_scripts(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
manifest = manifest_parser_pkg_foo.build_manifest()
claim_mtime_to = 255
sh_script_content = "#!/bin/sh"
@@ -773,3 +798,53 @@ def test_builtin_mode_normalization(manifest_parser_pkg_foo):
print(intermediate_manifest)
verify_paths(intermediate_manifest, expected_results)
+
+
+def test_builtin_mode_normalization(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
+ manifest = manifest_parser_pkg_foo.build_manifest()
+ claim_mtime_to = 255
+
+ paths = [
+ virtual_path_def("usr/", mode=0o755, mtime=10, fs_path="/nowhere/usr"),
+ virtual_path_def(
+ "usr/share/", mode=0o755, mtime=10, fs_path="/nowhere/usr/share"
+ ),
+ virtual_path_def(
+ "usr/share/perl5/", mode=0o755, mtime=10, fs_path="/nowhere/usr/share/perl5"
+ ),
+ virtual_path_def(
+ "usr/share/perl5/Foo.pm",
+ # #1076346
+ mode=0o444,
+ mtime=10,
+ fs_path="/nowhere/Foo.pm",
+ ),
+ virtual_path_def(
+ "usr/share/perl5/Bar.pm",
+ mode=0o755,
+ mtime=10,
+ fs_path="/nowhere/Bar.pm",
+ ),
+ ]
+
+ fs_root = build_virtual_fs(paths, read_write_fs=True)
+ assert [p.name for p in manifest.all_packages] == ["foo"]
+
+ expected_results = [
+ ("usr/", Expected(mode=0o755, mtime=10)),
+ ("usr/share/", Expected(mode=0o755, mtime=10)),
+ ("usr/share/perl5/", Expected(mode=0o755, mtime=10)),
+ ("usr/share/perl5/Bar.pm", Expected(mode=0o644, mtime=10)),
+ ("usr/share/perl5/Foo.pm", Expected(mode=0o644, mtime=10)),
+ ]
+ assert [p.name for p in manifest.all_packages] == ["foo"]
+
+ intermediate_manifest = manifest.apply_to_binary_staging_directory(
+ "foo", fs_root, claim_mtime_to
+ )
+
+ print(intermediate_manifest)
+
+ verify_paths(intermediate_manifest, expected_results)
diff --git a/tests/test_install_rules.py b/tests/test_install_rules.py
index a361864..ecc49fd 100644
--- a/tests/test_install_rules.py
+++ b/tests/test_install_rules.py
@@ -9,6 +9,7 @@ from debputy.installations import (
SearchDir,
)
from debputy.plugin.api import virtual_path_def
+from debputy.plugin.api.spec import INTEGRATION_MODE_DH_DEBPUTY
from debputy.plugin.api.test_api import build_virtual_file_system
@@ -118,13 +119,14 @@ def test_install_rules(manifest_parser_pkg_foo) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -207,12 +209,13 @@ def test_multi_dest_install_rules(manifest_parser_pkg_foo) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_source_root_dir, all_pkgs),
],
[],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -300,13 +303,14 @@ def test_install_rules_with_glob(manifest_parser_pkg_foo) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -365,13 +369,14 @@ def test_install_rules_auto_discard_rules_dir(manifest_parser_pkg_foo) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -424,13 +429,14 @@ def test_install_rules_auto_discard_rules_glob(manifest_parser_pkg_foo) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -490,13 +496,14 @@ def test_install_rules_auto_discard_rules_overruled_by_explicit_install_rule(
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -559,13 +566,14 @@ def test_install_rules_install_as_with_var(manifest_parser_pkg_foo) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
@@ -610,17 +618,18 @@ def test_install_rules_no_matches(manifest_parser_pkg_foo) -> None:
with pytest.raises(NoMatchForInstallPatternError) as e_info:
manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_dir, all_pkgs),
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_dir],
- )
+ ),
)
expected_msg = (
"There were no matches for build/private-arch-tool in /nowhere/debian/tmp, /nowhere"
- " (definition: installations[0].install <Search for: build/private-arch-tool>)."
+ " (definition: installations[0].install [Line 5 column 6])."
" Match rule: ./build/private-arch-tool (the exact path / no globbing)"
)
assert e_info.value.message == expected_msg
@@ -719,6 +728,7 @@ def test_install_rules_per_package_search_dirs(manifest_parser_pkg_foo_w_udeb) -
all_udeb_pkgs = frozenset({p for p in all_pkgs if p.is_udeb})
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_tmp_deb_dir, all_deb_pkgs),
@@ -726,7 +736,7 @@ def test_install_rules_per_package_search_dirs(manifest_parser_pkg_foo_w_udeb) -
SearchDir(debian_source_root_dir, all_pkgs),
],
[debian_tmp_deb_dir],
- )
+ ),
)
for pkg, ptype in [
("foo", "deb"),
@@ -820,12 +830,13 @@ def test_install_rules_multi_into(manifest_parser_pkg_foo_w_udeb) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_source_root_dir, all_pkgs),
],
[],
- )
+ ),
)
for pkg in ["foo", "foo-udeb"]:
assert pkg in result
@@ -939,6 +950,7 @@ def test_auto_install_d_pkg(manifest_parser_pkg_foo_w_udeb) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_source_root_dir, all_pkgs),
@@ -948,7 +960,7 @@ def test_auto_install_d_pkg(manifest_parser_pkg_foo_w_udeb) -> None:
"foo": debian_foo_dir,
"foo-udeb": debian_foo_udeb_dir,
},
- )
+ ),
)
for pkg in ["foo", "foo-udeb"]:
assert pkg in result
@@ -1024,12 +1036,13 @@ def test_install_doc_rules_ignore_udeb(manifest_parser_pkg_foo_w_udeb) -> None:
all_pkgs = frozenset(manifest.all_packages)
result = manifest.perform_installations(
+ INTEGRATION_MODE_DH_DEBPUTY,
install_request_context=InstallSearchDirContext(
[
SearchDir(debian_source_root_dir, all_pkgs),
],
[],
- )
+ ),
)
assert "foo" in result
foo_fs_root = result["foo"].fs_root
diff --git a/tests/test_migrations.py b/tests/test_migrations.py
index f53c716..0704c60 100644
--- a/tests/test_migrations.py
+++ b/tests/test_migrations.py
@@ -4,6 +4,7 @@ from typing import Callable, Optional, List, Tuple, Sequence
import pytest
+from debputy.commands.debputy_cmd.output import no_fancy_output
from debputy.dh_migration.migrators import Migrator
from debputy.dh_migration.migrators_impl import (
migrate_tmpfile,
@@ -15,7 +16,7 @@ from debputy.dh_migration.migrators_impl import (
migrate_install_file,
migrate_maintscript,
migrate_links_files,
- detect_dh_addons,
+ detect_dh_addons_with_zz_integration,
migrate_not_installed_file,
migrate_installman_file,
migrate_bash_completion,
@@ -99,7 +100,7 @@ def run_migrator(
*,
migration_target=INTEGRATION_MODE_DH_DEBPUTY,
) -> FeatureMigration:
- feature_migration = FeatureMigration(migrator.__name__)
+ feature_migration = FeatureMigration(migrator.__name__, no_fancy_output())
migrator(
path,
manifest,
@@ -1308,7 +1309,7 @@ def test_detect_obsolete_substvars(
)
msg = (
"The following relationship substitution variables can be removed from foo:"
- " ${misc:Depends}, ${shlibs:Depends}, ${so:Depends}"
+ " ${misc:Depends}, ${shlibs:Depends}, ${so:Depends} (Note: https://bugs.debian.org/1067653)"
)
assert migration.anything_to_do
assert migration.warnings == [msg]
@@ -1356,7 +1357,7 @@ def test_detect_obsolete_substvars_remove_field(
)
msg = (
"The following relationship fields can be removed from foo: Pre-Depends."
- " (The content in them would be applied automatically.)"
+ " (The content in them would be applied automatically. Note: https://bugs.debian.org/1067653)"
)
assert migration.anything_to_do
assert migration.warnings == [msg]
@@ -1406,7 +1407,7 @@ def test_detect_obsolete_substvars_remove_field_essential(
)
msg = (
"The following relationship fields can be removed from foo: Pre-Depends."
- " (The content in them would be applied automatically.)"
+ " (The content in them would be applied automatically. Note: https://bugs.debian.org/1067653)"
)
assert migration.anything_to_do
assert migration.warnings == [msg]
@@ -1465,7 +1466,7 @@ def test_detect_dh_addons(
accept_no_migration_issues: AcceptableMigrationIssues,
accept_any_migration_issues: AcceptableMigrationIssues,
) -> None:
- migrator = detect_dh_addons
+ migrator = detect_dh_addons_with_zz_integration
empty_fs = build_virtual_file_system([DEBIAN_DIR_ENTRY])
dctrl_no_addons_content = textwrap.dedent(
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 4aee024..0b1ed56 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -124,7 +124,10 @@ def test_parsing_variables_reserved(manifest_parser_pkg_foo, varname):
with pytest.raises(ManifestParseException) as e_info:
manifest_parser_pkg_foo.parse_manifest(fd=content)
- msg = f'The variable "{varname}" is already reserved/defined. Error triggered by definitions.variables.{varname}.'
+ msg = (
+ f'The variable "{varname}" is already reserved/defined.'
+ f" Error triggered by definitions.variables.{varname} [Line 4 column 4]."
+ )
assert normalize_doc_link(e_info.value.args[0]) == msg
@@ -163,7 +166,7 @@ def test_parsing_variables_unused(manifest_parser_pkg_foo):
msg = (
'The variable "UNUSED" is unused. Either use it or remove it.'
- " The variable was declared at definitions.variables.UNUSED."
+ " The variable was declared at definitions.variables.UNUSED [Line 4 column 4]."
)
assert normalize_doc_link(e_info.value.args[0]) == msg
@@ -181,7 +184,7 @@ def test_parsing_package_foo_empty(manifest_parser_pkg_foo):
manifest_parser_pkg_foo.parse_manifest(fd=content)
msg = (
- "The attribute packages.foo must be a non-empty mapping. Please see"
+ "The attribute packages.foo [Line 3 column 4] must be a non-empty mapping. Please see"
" {{DEBPUTY_DOC_ROOT_DIR}}/MANIFEST-FORMAT.md#binary-package-rules for the documentation."
)
assert normalize_doc_link(e_info.value.args[0]) == msg
@@ -238,8 +241,8 @@ def test_create_symlinks_missing_path(manifest_parser_pkg_foo):
manifest_parser_pkg_foo.parse_manifest(fd=content)
msg = (
- "The following keys were required but not present at packages.foo.transformations[0].create-symlink: 'path'"
- " (Documentation: "
+ "The following keys were required but not present at packages.foo.transformations[0].create-symlink"
+ " [Line 5 column 12]: 'path' (Documentation: "
"{{DEBPUTY_DOC_ROOT_DIR}}/MANIFEST-FORMAT.md#create-symlinks-transformation-rule-create-symlink)"
)
assert normalize_doc_link(e_info.value.args[0]) == msg
@@ -263,7 +266,7 @@ def test_create_symlinks_unknown_replacement_rule(manifest_parser_pkg_foo):
manifest_parser_pkg_foo.parse_manifest(fd=content)
msg = (
- 'The attribute "packages.foo.transformations[0].create-symlink.replacement-rule <Search for: usr/share/foo>"'
+ 'The attribute "packages.foo.transformations[0].create-symlink.replacement-rule [Line 8 column 32]"'
" did not have a valid structure/type: Value (golf) must be one of the following literal values:"
' "error-if-exists", "error-if-directory", "abort-on-non-empty-directory", "discard-existing"'
)
@@ -286,8 +289,8 @@ def test_create_symlinks_missing_target(manifest_parser_pkg_foo):
manifest_parser_pkg_foo.parse_manifest(fd=content)
msg = (
- "The following keys were required but not present at packages.foo.transformations[0].create-symlink: 'target'"
- " (Documentation: "
+ "The following keys were required but not present at packages.foo.transformations[0].create-symlink"
+ " [Line 5 column 12]: 'target' (Documentation: "
"{{DEBPUTY_DOC_ROOT_DIR}}/MANIFEST-FORMAT.md#create-symlinks-transformation-rule-create-symlink)"
)
assert normalize_doc_link(e_info.value.args[0]) == msg
@@ -310,7 +313,7 @@ def test_create_symlinks_not_normalized_path(manifest_parser_pkg_foo):
manifest_parser_pkg_foo.parse_manifest(fd=content)
expected = (
- 'The path "../bar" provided in packages.foo.transformations[0].create-symlink.path <Search for: ../bar>'
+ 'The path "../bar" provided in packages.foo.transformations[0].create-symlink.path [Line 6 column 20]'
' should be relative to the root of the package and not use any ".." or "." segments.'
)
assert e_info.value.args[0] == expected
@@ -332,7 +335,7 @@ def test_unresolvable_subst_in_source_context(manifest_parser_pkg_foo):
expected = (
"The variable {{PACKAGE}} is not available while processing installations[0].install.as"
- " <Search for: foo.sh>."
+ " [Line 5 column 7]."
)
assert e_info.value.args[0] == expected
@@ -397,7 +400,7 @@ def test_yaml_octal_mode_int(manifest_parser_pkg_foo):
manifest_parser_pkg_foo.parse_manifest(fd=content)
msg = (
- 'The attribute "packages.foo.transformations[0].path-metadata.mode <Search for: usr/share/bar>" did not'
+ 'The attribute "packages.foo.transformations[0].path-metadata.mode [Line 7 column 20]" did not'
" have a valid structure/type: The attribute must be a FileSystemMode (string)"
)
@@ -486,3 +489,86 @@ def test_yaml_clean_after_removal_unsafe_path(
else:
with pytest.raises(ManifestParseException) as e_info:
manifest_parser_pkg_foo.parse_manifest(fd=content)
+
+
+def test_yaml_build_environment_default(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
+ content = textwrap.dedent(
+ (
+ """\
+
+ manifest-version: '0.1'
+ default-build-environment:
+ set:
+ FOO: "bar"
+ builds:
+ # FIXME: we should not require an empty dict here
+ - autoconf: {}
+ """
+ )
+ )
+ manifest = manifest_parser_pkg_foo.parse_manifest(fd=content)
+ envs = manifest.build_environments
+ assert not envs.environments
+ base_env = {}
+ envs.default_environment.update_env(base_env)
+ assert "FOO" in base_env
+ build_rule = manifest.build_rules[0]
+ assert build_rule.environment is envs.default_environment
+
+
+def test_yaml_build_environments_no_default(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
+ content = textwrap.dedent(
+ (
+ f"""\
+
+ manifest-version: '0.1'
+ build-environments:
+ - name: custom-env
+ set:
+ FOO: "bar"
+ builds:
+ - autoconf:
+ environment: custom-env
+ """
+ )
+ )
+ manifest = manifest_parser_pkg_foo.parse_manifest(fd=content)
+ envs = manifest.build_environments
+ assert "custom-env" in envs.environments
+ custom_env = envs.environments["custom-env"]
+ assert envs.default_environment is None
+ base_env = {}
+ custom_env.update_env(base_env)
+ assert "FOO" in base_env
+ build_rule = manifest.build_rules[0]
+ assert build_rule.environment is custom_env
+
+
+def test_yaml_build_environments_no_default_error(
+ manifest_parser_pkg_foo: YAMLManifestParser,
+) -> None:
+ content = textwrap.dedent(
+ (
+ """\
+
+ manifest-version: '0.1'
+ build-environments:
+ - name: custom-env
+ set:
+ FOO: "bar"
+ builds:
+ # FIXME: we should not require an empty dict here
+ - autoconf: {}
+ """
+ )
+ )
+ with pytest.raises(ManifestParseException) as e_info:
+ manifest_parser_pkg_foo.parse_manifest(fd=content)
+
+ expected_msg = "The following named environments were never referenced: custom-env"
+ msg = e_info.value.args[0]
+ assert msg == expected_msg
diff --git a/tests/test_style.py b/tests/test_style.py
index ef6ddc4..8f5b6ca 100644
--- a/tests/test_style.py
+++ b/tests/test_style.py
@@ -4,51 +4,57 @@ import pytest
from debian.deb822 import Deb822
from debputy.yaml.compat import CommentedMap
-from debputy.lsp.style_prefs import (
- StylePreferenceTable,
- determine_effective_style,
- EffectivePreference,
+from debputy.lsp.maint_prefs import (
+ MaintainerPreferenceTable,
+ determine_effective_preference,
+ EffectiveFormattingPreference,
_WAS_DEFAULTS,
)
from debputy.packages import SourcePackage
def test_load_styles() -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
assert "niels@thykier.net" in styles.maintainer_preferences
- nt_style = styles.maintainer_preferences["niels@thykier.net"]
+ nt_maint_pref = styles.maintainer_preferences["niels@thykier.net"]
# Note this is data dependent; if it fails because the style changes, update the test
- assert nt_style.canonical_name == "Niels Thykier"
- assert not nt_style.is_packaging_team
- assert nt_style.formatting_deb822_normalize_field_content
- assert nt_style.formatting_deb822_short_indent
- assert nt_style.formatting_deb822_always_wrap
- assert nt_style.formatting_deb822_trailing_separator
- assert nt_style.formatting_deb822_max_line_length == 79
- assert not nt_style.formatting_deb822_normalize_stanza_order
+ assert nt_maint_pref.canonical_name == "Niels Thykier"
+ assert not nt_maint_pref.is_packaging_team
+ black_style = styles.named_styles["black"]
+ nt_style = nt_maint_pref.formatting
+ assert nt_style is not None
+ assert black_style == black_style
- # TODO: Not implemented yet
- assert not nt_style.formatting_deb822_normalize_field_order
+
+def test_load_no_styles() -> None:
+ styles = MaintainerPreferenceTable.load_preferences()
+ assert "packages@qa.debian.org" in styles.maintainer_preferences
+ qa_maint_pref = styles.maintainer_preferences["packages@qa.debian.org"]
+ assert qa_maint_pref.canonical_name == "Debian QA Group"
+ assert qa_maint_pref.is_packaging_team
+ # Orphaned packages do not have a predefined style, since Debian (nor Debian QA) have
+ # one well-defined style.
+ assert qa_maint_pref.formatting is None
def test_load_named_styles() -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
assert "black" in styles.named_styles
black_style = styles.named_styles["black"]
# Note this is data dependent; if it fails because the style changes, update the test
- assert black_style.formatting_deb822_normalize_field_content
- assert black_style.formatting_deb822_short_indent
- assert black_style.formatting_deb822_always_wrap
- assert black_style.formatting_deb822_trailing_separator
- assert black_style.formatting_deb822_max_line_length == 79
- assert not black_style.formatting_deb822_normalize_stanza_order
+ assert black_style.deb822_normalize_field_content
+ assert black_style.deb822_short_indent
+ assert black_style.deb822_always_wrap
+ assert black_style.deb822_trailing_separator
+ assert black_style.deb822_max_line_length == 79
+ assert not black_style.deb822_normalize_stanza_order
# TODO: Not implemented yet
- assert not black_style.formatting_deb822_normalize_field_order
+ assert not black_style.deb822_normalize_field_order
def test_compat_styles() -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
# Data dependent; if it breaks, provide a stubbed style preference table
assert "niels@thykier.net" in styles.maintainer_preferences
@@ -56,11 +62,11 @@ def test_compat_styles() -> None:
assert "random-package@packages.debian.org" not in styles.maintainer_preferences
assert "random@example.org" not in styles.maintainer_preferences
- nt_pref = styles.maintainer_preferences["niels@thykier.net"].as_effective_pref()
- zeha_pref = styles.maintainer_preferences["zeha@debian.org"].as_effective_pref()
+ nt_style = styles.maintainer_preferences["niels@thykier.net"].formatting
+ zeha_style = styles.maintainer_preferences["zeha@debian.org"].formatting
# Data dependency
- assert nt_pref == zeha_pref
+ assert nt_style == zeha_style
fields = Deb822(
{
@@ -71,30 +77,33 @@ def test_compat_styles() -> None:
)
src = SourcePackage(fields)
- effective_style, _ = determine_effective_style(styles, src, None)
- assert effective_style == nt_pref
+ effective_style, tool, _ = determine_effective_preference(styles, src, None)
+ assert effective_style == nt_style
+ assert tool == "debputy reformat"
fields["Uploaders"] = (
"Niels Thykier <niels@thykier.net>, Chris Hofstaedtler <zeha@debian.org>"
)
src = SourcePackage(fields)
- effective_style, _ = determine_effective_style(styles, src, None)
- assert effective_style == nt_pref
- assert effective_style == zeha_pref
+ effective_style, tool, _ = determine_effective_preference(styles, src, None)
+ assert effective_style == nt_style
+ assert effective_style == zeha_style
+ assert tool == "debputy reformat"
fields["Uploaders"] = (
"Niels Thykier <niels@thykier.net>, Chris Hofstaedtler <zeha@debian.org>, Random Developer <random@example.org>"
)
src = SourcePackage(fields)
- effective_style, _ = determine_effective_style(styles, src, None)
+ effective_style, tool, _ = determine_effective_preference(styles, src, None)
assert effective_style is None
+ assert tool is None
@pytest.mark.xfail
def test_compat_styles_team_maint() -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
fields = Deb822(
{
"Package": "foo",
@@ -108,12 +117,13 @@ def test_compat_styles_team_maint() -> None:
assert "random@example.org" not in styles.maintainer_preferences
team_style = styles.maintainer_preferences["team@lists.debian.org"]
assert team_style.is_packaging_team
- effective_style, _ = determine_effective_style(styles, src, None)
- assert effective_style == team_style.as_effective_pref()
+ effective_style, tool, _ = determine_effective_preference(styles, src, None)
+ assert effective_style == team_style.formatting
+ assert tool is None
def test_x_style() -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
fields = Deb822(
{
"Package": "foo",
@@ -125,12 +135,13 @@ def test_x_style() -> None:
assert "random@example.org" not in styles.maintainer_preferences
assert "black" in styles.named_styles
black_style = styles.named_styles["black"]
- effective_style, _ = determine_effective_style(styles, src, None)
+ effective_style, tool, _ = determine_effective_preference(styles, src, None)
assert effective_style == black_style
+ assert tool == "debputy reformat"
def test_was_from_salsa_ci_style() -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
fields = Deb822(
{
"Package": "foo",
@@ -139,20 +150,23 @@ def test_was_from_salsa_ci_style() -> None:
)
src = SourcePackage(fields)
assert "random@example.org" not in styles.maintainer_preferences
- effective_style, _ = determine_effective_style(styles, src, None)
+ effective_style, tool, _ = determine_effective_preference(styles, src, None)
assert effective_style is None
+ assert tool is None
salsa_ci = CommentedMap(
{"variables": CommentedMap({"SALSA_CI_DISABLE_WRAP_AND_SORT": "yes"})}
)
- effective_style, _ = determine_effective_style(styles, src, salsa_ci)
+ effective_style, tool, _ = determine_effective_preference(styles, src, salsa_ci)
assert effective_style is None
+ assert tool is None
salsa_ci = CommentedMap(
{"variables": CommentedMap({"SALSA_CI_DISABLE_WRAP_AND_SORT": "no"})}
)
- effective_style, _ = determine_effective_style(styles, src, salsa_ci)
- was_style = EffectivePreference(**_WAS_DEFAULTS)
+ effective_style, tool, _ = determine_effective_preference(styles, src, salsa_ci)
+ was_style = EffectiveFormattingPreference(**_WAS_DEFAULTS)
assert effective_style == was_style
+ assert tool == "wrap-and-sort"
@pytest.mark.parametrize(
@@ -161,45 +175,46 @@ def test_was_from_salsa_ci_style() -> None:
(
"-a",
{
- "formatting_deb822_always_wrap": True,
+ "deb822_always_wrap": True,
},
),
(
"-sa",
{
- "formatting_deb822_always_wrap": True,
- "formatting_deb822_short_indent": True,
+ "deb822_always_wrap": True,
+ "deb822_short_indent": True,
},
),
(
"-sa --keep-first",
{
- "formatting_deb822_always_wrap": True,
- "formatting_deb822_short_indent": True,
+ "deb822_always_wrap": True,
+ "deb822_short_indent": True,
},
),
(
"-sab --keep-first",
{
- "formatting_deb822_always_wrap": True,
- "formatting_deb822_short_indent": True,
- "formatting_deb822_normalize_stanza_order": True,
+ "deb822_always_wrap": True,
+ "deb822_short_indent": True,
+ "deb822_normalize_stanza_order": True,
},
),
(
"-sab --no-keep-first",
{
- "formatting_deb822_always_wrap": True,
- "formatting_deb822_short_indent": True,
- "formatting_deb822_normalize_stanza_order": False,
+ "deb822_always_wrap": True,
+ "deb822_short_indent": True,
+ "deb822_normalize_stanza_order": False,
},
),
],
)
def test_was_from_salsa_ci_style_args(
- was_args: str, style_delta: Optional[Mapping[str, Any]]
+ was_args: str,
+ style_delta: Optional[Mapping[str, Any]],
) -> None:
- styles = StylePreferenceTable.load_styles()
+ styles = MaintainerPreferenceTable.load_preferences()
fields = Deb822(
{
"Package": "foo",
@@ -218,12 +233,14 @@ def test_was_from_salsa_ci_style_args(
)
}
)
- effective_style, _ = determine_effective_style(styles, src, salsa_ci)
+ effective_style, tool, _ = determine_effective_preference(styles, src, salsa_ci)
if style_delta is None:
assert effective_style is None
+ assert tool is None
else:
- was_style = EffectivePreference(**_WAS_DEFAULTS).replace(
+ was_style = EffectiveFormattingPreference(**_WAS_DEFAULTS).replace(
**style_delta,
)
assert effective_style == was_style
+ assert tool == f"wrap-and-sort {was_args}".strip()
diff --git a/tests/test_substitute.py b/tests/test_substitute.py
index a83cc7f..81eb2e0 100644
--- a/tests/test_substitute.py
+++ b/tests/test_substitute.py
@@ -1,6 +1,7 @@
import pytest
from debputy.architecture_support import faked_arch_table
+from debputy.commands.debputy_cmd.output import no_fancy_output
from debputy.dh_migration.models import (
DHMigrationSubstitution,
AcceptableMigrationIssues,
@@ -55,7 +56,7 @@ def test_substitution_match(debputy_plugin_feature_set, value, expected) -> None
def test_migrate_substitution() -> None:
- feature_migration = FeatureMigration("test migration")
+ feature_migration = FeatureMigration("test migration", no_fancy_output())
subst = DHMigrationSubstitution(
MOCK_DPKG_ARCH_TABLE,
AcceptableMigrationIssues(frozenset()),
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..e1dc87e
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,20 @@
+from typing import Sequence, Union
+
+import pytest
+
+from debputy.util import escape_shell
+
+
+@pytest.mark.parametrize(
+ "arg,expected",
+ [
+ ("foo bar", '"foo bar"'),
+ ("a'b", r"""a\'b"""),
+ ("foo=bar and baz", 'foo="bar and baz"'),
+ ("--foo=bar and baz", '--foo="bar and baz"'),
+ ("--foo with spaces=bar and baz", '"--foo with spaces=bar and baz"'),
+ ],
+)
+def test_symlink_normalization(arg: Union[str, Sequence[str]], expected: str) -> None:
+ actual = escape_shell(arg) if isinstance(arg, str) else escape_shell(*arg)
+ assert actual == expected