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

pantsbuild / pants / 25443604553

06 May 2026 03:05PM UTC coverage: 92.879% (-0.04%) from 92.915%
25443604553

push

github

web-flow
[pants_ng] Scaffolding for a pants_ng mode. (#23319)

In this mode the command line is parsed as an
NG invocation, and dispatched appropriately.

Of course at the moment there are no
implementations to dispatch to. That will follow.

This does expose a new option, `pants_ng` to users. 
There is a big warning not to set it, but we're not trying
to hide that we're working on a new thing, so I am
comfortable with this.

25 of 76 new or added lines in 9 files covered. (32.89%)

1294 existing lines in 76 files now uncovered.

92234 of 99306 relevant lines covered (92.88%)

4.05 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