• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 18812500213

26 Oct 2025 03:42AM UTC coverage: 80.284% (+0.005%) from 80.279%
18812500213

Pull #22804

github

web-flow
Merge 2a56fdb46 into 4834308dc
Pull Request #22804: test_shell_command: use correct default cache scope for a test's environment

29 of 31 new or added lines in 2 files covered. (93.55%)

1314 existing lines in 64 files now uncovered.

77900 of 97030 relevant lines covered (80.28%)

3.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/python/pants/backend/nfpm/rules_integration_test.py
1
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
3✔
5

6
from textwrap import dedent
3✔
7
from typing import Any, ContextManager, cast
3✔
8

9
import pytest
3✔
10
from _pytest.mark import ParameterSet
3✔
11

12
from pants.backend.nfpm.dependency_inference import rules as nfpm_dependency_inference_rules
3✔
13
from pants.backend.nfpm.field_sets import (
3✔
14
    NFPM_PACKAGE_FIELD_SET_TYPES,
15
    NfpmApkPackageFieldSet,
16
    NfpmArchlinuxPackageFieldSet,
17
    NfpmDebPackageFieldSet,
18
    NfpmPackageFieldSet,
19
    NfpmRpmPackageFieldSet,
20
)
21
from pants.backend.nfpm.rules import rules as nfpm_rules
3✔
22
from pants.backend.nfpm.subsystem import rules as nfpm_subsystem_rules
3✔
23
from pants.backend.nfpm.target_types import target_types as nfpm_target_types
3✔
24
from pants.backend.nfpm.target_types_rules import rules as nfpm_target_types_rules
3✔
25
from pants.core.goals.package import BuiltPackage
3✔
26
from pants.core.target_types import FilesGeneratorTarget, FileTarget
3✔
27
from pants.core.target_types import rules as core_target_type_rules
3✔
28
from pants.engine.internals.native_engine import Address
3✔
29
from pants.engine.internals.scheduler import ExecutionError
3✔
30
from pants.engine.target import Target
3✔
31
from pants.testutil.pytest_util import no_exception
3✔
32
from pants.testutil.rule_runner import QueryRule, RuleRunner
3✔
33

34

35
@pytest.fixture
3✔
36
def rule_runner() -> RuleRunner:
3✔
37
    rule_runner = RuleRunner(
3✔
38
        target_types=[
39
            FileTarget,
40
            FilesGeneratorTarget,
41
            *nfpm_target_types(),
42
        ],
43
        rules=[
44
            *core_target_type_rules(),
45
            *nfpm_subsystem_rules(),
46
            *nfpm_target_types_rules(),
47
            *nfpm_dependency_inference_rules(),
48
            *nfpm_rules(),
49
            *(
50
                QueryRule(BuiltPackage, [field_set_type])
51
                for field_set_type in NFPM_PACKAGE_FIELD_SET_TYPES
52
            ),
53
        ],
54
    )
55
    return rule_runner
3✔
56

57

58
def build_package(
3✔
59
    rule_runner: RuleRunner,
60
    binary_target: Target,
61
    field_set_type: type[NfpmPackageFieldSet],
62
) -> BuiltPackage:
63
    field_set = field_set_type.create(binary_target)
3✔
64
    result = rule_runner.request(BuiltPackage, [field_set])
3✔
65
    rule_runner.write_digest(result.digest)
3✔
66
    return result
3✔
67

68

69
def _assert_one_built_artifact(
3✔
70
    pkg_name: str, built_package: BuiltPackage, field_set_type: type[NfpmPackageFieldSet]
71
) -> None:
72
    assert len(built_package.artifacts) == 1
3✔
73
    artifact = built_package.artifacts[0]
3✔
74
    relpath = artifact.relpath or ""
3✔
75
    assert relpath.endswith(field_set_type.extension)
3✔
76
    assert relpath.startswith(f"{pkg_name}/{pkg_name}")
3✔
77

78

79
_PKG_NAME = "pkg"
3✔
80
_PKG_VERSION = "3.2.1"
3✔
81

82
_TEST_CASES: tuple[ParameterSet, ...] = (
3✔
83
    # apk
84
    pytest.param(NfpmApkPackageFieldSet, {}, True, id="apk-minimal-metadata"),
85
    pytest.param(
86
        NfpmApkPackageFieldSet,
87
        # apk uses "maintainer" not "packager"
88
        {"packager": "Apk Maintainer <apk-maintainer@example.com>"},
89
        False,
90
        id="apk-invalid-field-packager",
91
    ),
92
    pytest.param(
93
        NfpmApkPackageFieldSet,
94
        {
95
            "homepage": "https://apk.example.com",
96
            "license": "Apache-2.0",
97
            "maintainer": "APK Maintainer <apk-maintainer@example.com>",
98
            "replaces": ["some-command"],
99
            "provides": [f"cmd:some-command={_PKG_VERSION}"],
100
            "depends": ["bash", "git=2.40.1-r0", "/bin/sh", "so:libcurl.so.4"],
101
            "scripts": {"postinstall": "postinstall.sh", "postupgrade": "apk-postupgrade.sh"},
102
        },
103
        True,
104
        id="apk-extra-metadata",
105
    ),
106
    # archlinux
107
    pytest.param(NfpmArchlinuxPackageFieldSet, {}, True, id="archlinux-minimal-metadata"),
108
    pytest.param(
109
        NfpmArchlinuxPackageFieldSet,
110
        # archlinux uses "packager" not "maintainer"
111
        {"maintainer": "Arch Maintainer <arch-maintainer@example.com>"},
112
        False,
113
        id="archlinux-invalid-field-maintainer",
114
    ),
115
    pytest.param(
116
        NfpmArchlinuxPackageFieldSet,
117
        {
118
            "homepage": "https://arch.example.com",
119
            "license": "Apache-2.0",
120
            "packager": "Arch Maintainer <arch-maintainer@example.com>",
121
            "replaces": ["obsolete-pkg"],
122
            "provides": ["foo", "bar=1.0.0", "libbaz.so=2"],
123
            "depends": ["bash", "git>=2.40.1"],
124
            "conflicts": ["conflicting-pkg"],
125
            "scripts": {"postinstall": "postinstall.sh", "postupgrade": "arch-postupgrade.sh"},
126
        },
127
        True,
128
        id="archlinux-extra-metadata",
129
    ),
130
    # deb
131
    pytest.param(NfpmDebPackageFieldSet, {}, False, id="deb-missing-maintainer-field"),
132
    pytest.param(
133
        NfpmDebPackageFieldSet,
134
        # deb uses "maintainer" not "packager"
135
        {"packager": "Deb Maintainer <deb-maintainer@example.com>"},
136
        False,
137
        id="deb-invalid-field-packager",
138
    ),
139
    pytest.param(
140
        NfpmDebPackageFieldSet,
141
        # maintainer is a required field
142
        {"maintainer": "Deb Maintainer <deb-maintainer@example.com>"},
143
        True,
144
        id="deb-minimal-metadata",
145
    ),
146
    pytest.param(
147
        NfpmDebPackageFieldSet,
148
        {
149
            "homepage": "https://deb.example.com",
150
            "license": "Apache-2.0",
151
            "maintainer": "deb-maintainer@example.com",
152
            "section": "education",
153
            "priority": "standard",  # defaults to optional
154
            "fields": {"XB-Custom": "custom control file field"},
155
            "triggers": {"interest_noawait": ["some-trigger"]},
156
            "replaces": ["partial-pkg", "replaced-pkg"],
157
            "provides": ["pkg"],
158
            "depends": ["git", "libc6 (>= 2.2.1)", "default-mta | mail-transport-agent"],
159
            "recommends": ["other-pkg"],
160
            "suggests": ["beneficial-other-pkg"],
161
            "conflicts": ["replaced-pkg"],
162
            "breaks": ["partial-pkg"],
163
            "compression": "none",  # defaults to gzip
164
            "scripts": {"postinstall": "postinstall.sh", "config": "deb-config.sh"},
165
        },
166
        True,
167
        id="deb-extra-metadata",
168
    ),
169
    # rpm
170
    pytest.param(NfpmRpmPackageFieldSet, {}, True, id="rpm-minimal-metadata"),
171
    pytest.param(
172
        NfpmRpmPackageFieldSet,
173
        # rpm uses "packager" not "maintainer"
174
        {"maintainer": "RPM Maintainer <rpm-maintainer@example.com>"},
175
        False,
176
        id="rpm-invalid-field-maintainer",
177
    ),
178
    pytest.param(
179
        NfpmRpmPackageFieldSet,
180
        {
181
            "homepage": "https://rpm.example.com",
182
            "license": "Apache-2.0",
183
            "packager": "RPM Maintainer <rpm-maintainer@example.com>",
184
            "vendor": "Example Organization",
185
            "prefixes": ["/usr", "/usr/local", "/opt/foobar"],
186
            "replaces": ["partial-pkg", "replaced-pkg"],
187
            "provides": ["pkg"],
188
            "depends": ["git", "libc6 (>= 2.2.1)", "default-mta | mail-transport-agent"],
189
            "recommends": ["other-pkg"],
190
            "suggests": ["beneficial-other-pkg"],
191
            "conflicts": ["replaced-pkg"],
192
            "compression": "zstd:fastest",  # defaults to gzip:-1
193
            "scripts": {"postinstall": "postinstall.sh", "verify": "rpm-verify.sh"},
194
            "ghost_contents": ["/var/log/pkg.log"],
195
        },
196
        True,
197
        id="rpm-extra-metadata",
198
    ),
199
)
200

201

202
@pytest.mark.parametrize("field_set_type,extra_metadata,valid_target", _TEST_CASES)
3✔
203
def test_generate_package_without_contents(
3✔
204
    rule_runner: RuleRunner,
205
    field_set_type: type[NfpmPackageFieldSet],
206
    extra_metadata: dict[str, Any],
207
    valid_target: bool,
208
) -> None:
UNCOV
209
    packager = field_set_type.packager
1✔
210
    # do not use scripts for this test.
UNCOV
211
    extra_metadata = {key: value for key, value in extra_metadata.items() if key != "scripts"}
1✔
UNCOV
212
    rule_runner.write_files(
1✔
213
        {
214
            "BUILD": dedent(
215
                f"""
216
                nfpm_{packager}_package(
217
                    name="{_PKG_NAME}",
218
                    package_name="{_PKG_NAME}",
219
                    version="{_PKG_VERSION}",
220
                    **{extra_metadata}
221
                )
222
                """
223
            ),
224
        }
225
    )
226

227
    # noinspection DuplicatedCode
UNCOV
228
    with cast(ContextManager, no_exception()) if valid_target else pytest.raises(ExecutionError):
1✔
UNCOV
229
        binary_tgt = rule_runner.get_target(Address("", target_name="pkg"))
1✔
UNCOV
230
    if not valid_target:
1✔
UNCOV
231
        return
1✔
232

UNCOV
233
    built_package = build_package(rule_runner, binary_tgt, field_set_type)
1✔
UNCOV
234
    _assert_one_built_artifact(_PKG_NAME, built_package, field_set_type)
1✔
235

236

237
@pytest.mark.platform_specific_behavior
3✔
238
@pytest.mark.parametrize("field_set_type,extra_metadata,valid_target", _TEST_CASES)
3✔
239
def test_generate_package_with_contents(
3✔
240
    rule_runner: RuleRunner,
241
    field_set_type: type[NfpmPackageFieldSet],
242
    extra_metadata: dict[str, Any],
243
    valid_target: bool,
244
) -> None:
245
    packager = field_set_type.packager
3✔
246
    scripts = extra_metadata.get("scripts", {})
3✔
247
    rule_runner.write_files(
3✔
248
        {
249
            "BUILD": dedent(
250
                f"""
251
                nfpm_{packager}_package(
252
                    name="{_PKG_NAME}",
253
                    package_name="{_PKG_NAME}",
254
                    version="{_PKG_VERSION}",
255
                    dependencies=[
256
                        "contents:files",
257
                        "contents:file",
258
                        "contents:symlinks",
259
                        "contents:symlink",
260
                        "contents:dirs",
261
                        "contents:dir",
262
                    ],
263
                    **{extra_metadata}
264
                )
265
                if {bool(scripts)}:
266
                    files(
267
                        name="scripts",
268
                        sources={list(scripts.values())},
269
                    )
270
                """
271
            ),
272
            **dict.fromkeys(scripts.values(), ""),
273
            "contents/BUILD": dedent(
274
                f"""
275
                file(
276
                    name="sandbox_file",
277
                    source="sandbox-file.txt",
278
                )
279
                nfpm_content_files(
280
                    name="files",
281
                    files=[
282
                        ("sandbox-file.txt", "/usr/share/{_PKG_NAME}/{_PKG_NAME}.{_PKG_VERSION}/installed-file.txt"),
283
                        ("sandbox-file.txt", "/etc/{_PKG_NAME}/installed-file.txt"),
284
                    ],
285
                    dependencies=[":sandbox_file"],
286
                    overrides={{
287
                        "/etc/{_PKG_NAME}/installed-file.txt": dict(
288
                            content_type="config",
289
                            file_mode="rw-------",  # same as 0o600 and "600"
290
                            file_group="root",
291
                        ),
292
                    }},
293
                    content_type="doc",
294
                    file_owner="root",
295
                    file_group="{_PKG_NAME}",
296
                    file_mode="644",  # same as 0o644 and "rw-r--r--"
297
                )
298
                nfpm_content_file(
299
                    name="file",
300
                    source="some-executable",
301
                    dst="/usr/bin/some-executable",
302
                    file_mode=0o755,  # same as "755" and "rwxr-xr-x"
303
                )
304
                nfpm_content_symlinks(
305
                    name="symlinks",
306
                    symlinks=(
307
                        ("some-executable", "/usr/bin/new-relative-symlinked-exe"),
308
                        ("/usr/bin/some-executable", "/usr/bin/new-absolute-symlinked-exe"),
309
                    ),
310
                    overrides={{
311
                        "/usr/bin/new-relative-symlinked-exe": dict(file_group="special-group"),
312
                    }},
313
                )
314
                nfpm_content_symlink(
315
                    name="symlink",
316
                    src="/usr/bin/some-executable",
317
                    dst="/usr/sbin/sbin-executable",
318
                )
319
                nfpm_content_dirs(
320
                    name="dirs",
321
                    dirs=["/usr/share/{_PKG_NAME}"],
322
                    overrides={{
323
                        "/usr/share/{_PKG_NAME}": dict(file_group="special-group"),
324
                    }},
325
                )
326
                nfpm_content_dir(
327
                    name="dir",
328
                    dst="/etc/{_PKG_NAME}",
329
                    file_mode=0o700,
330
                )
331
                """
332
            ),
333
            "contents/sandbox-file.txt": "",
334
            "contents/some-executable": "",
335
        }
336
    )
337

338
    # noinspection DuplicatedCode
339
    with cast(ContextManager, no_exception()) if valid_target else pytest.raises(ExecutionError):
3✔
340
        binary_tgt = rule_runner.get_target(Address("", target_name=_PKG_NAME))
3✔
341
    if not valid_target:
3✔
342
        return
3✔
343

344
    built_package = build_package(rule_runner, binary_tgt, field_set_type)
3✔
345
    _assert_one_built_artifact(_PKG_NAME, built_package, field_set_type)
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc