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

pantsbuild / pants / 19091350721

05 Nov 2025 04:31AM UTC coverage: 92.574% (+12.3%) from 80.3%
19091350721

Pull #22816

github

web-flow
Merge 941954680 into 89462b7ef
Pull Request #22816: Update Pants internal Python to 3.14

23 of 24 new or added lines in 14 files covered. (95.83%)

128 existing lines in 8 files now uncovered.

89656 of 96848 relevant lines covered (92.57%)

3.73 hits per line

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

93.23
/src/python/pants/engine/internals/build_files_test.py
1
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
import logging
1✔
7
import re
1✔
8
from collections.abc import Mapping
1✔
9
from textwrap import dedent
1✔
10
from typing import Any
1✔
11

12
import pytest
1✔
13

14
from pants.build_graph.address import BuildFileAddressRequest, MaybeAddress, ResolveError
1✔
15
from pants.build_graph.build_file_aliases import BuildFileAliases
1✔
16
from pants.core.target_types import GenericTarget, ResourceTarget
1✔
17
from pants.engine.addresses import Address, AddressInput, BuildFileAddress
1✔
18
from pants.engine.env_vars import CompleteEnvironmentVars, EnvironmentVars
1✔
19
from pants.engine.fs import DigestContents, FileContent
1✔
20
from pants.engine.internals.build_files import (
1✔
21
    AddressFamilyDir,
22
    BUILDFileEnvVarExtractor,
23
    BuildFileOptions,
24
    BuildFileSyntaxError,
25
    OptionalAddressFamily,
26
    evaluate_preludes,
27
    parse_address_family,
28
)
29
from pants.engine.internals.defaults import BuildFileDefaults, ParametrizeDefault
1✔
30
from pants.engine.internals.dep_rules import MaybeBuildFileDependencyRulesImplementation
1✔
31
from pants.engine.internals.mapper import AddressFamily
1✔
32
from pants.engine.internals.parametrize import Parametrize
1✔
33
from pants.engine.internals.parser import BuildFilePreludeSymbols, BuildFileSymbolInfo, Parser
1✔
34
from pants.engine.internals.scheduler import ExecutionError
1✔
35
from pants.engine.internals.session import SessionValues
1✔
36
from pants.engine.internals.synthetic_targets import SyntheticAddressMap, SyntheticAddressMaps
1✔
37
from pants.engine.internals.target_adaptor import TargetAdaptor, TargetAdaptorRequest
1✔
38
from pants.engine.target import (
1✔
39
    Dependencies,
40
    MultipleSourcesField,
41
    OverridesField,
42
    RegisteredTargetTypes,
43
    SingleSourceField,
44
    StringField,
45
    Tags,
46
    Target,
47
    TargetFilesGenerator,
48
)
49
from pants.engine.unions import UnionMembership
1✔
50
from pants.init.bootstrap_scheduler import BootstrapStatus
1✔
51
from pants.testutil.pytest_util import assert_logged
1✔
52
from pants.testutil.rule_runner import QueryRule, RuleRunner, engine_error, run_rule_with_mocks
1✔
53
from pants.util.frozendict import FrozenDict
1✔
54
from pants.util.strutil import softwrap
1✔
55

56

57
def test_parse_address_family_empty() -> None:
1✔
58
    """Test that parsing an empty BUILD file results in an empty AddressFamily."""
59
    optional_af = run_rule_with_mocks(
1✔
60
        parse_address_family,
61
        rule_args=[
62
            AddressFamilyDir("/dev/null"),
63
            Parser(
64
                build_root="",
65
                registered_target_types=RegisteredTargetTypes({}),
66
                union_membership=UnionMembership.empty(),
67
                object_aliases=BuildFileAliases(),
68
                ignore_unrecognized_symbols=False,
69
            ),
70
            BootstrapStatus(in_progress=False),
71
            BuildFileOptions(("BUILD",)),
72
            BuildFilePreludeSymbols(FrozenDict(), ()),
73
            RegisteredTargetTypes({}),
74
            UnionMembership.empty(),
75
            MaybeBuildFileDependencyRulesImplementation(None),
76
            SessionValues({CompleteEnvironmentVars: CompleteEnvironmentVars({})}),
77
        ],
78
        mock_calls={
79
            "pants.engine.intrinsics.get_digest_contents": lambda __implicitly: DigestContents(
80
                [FileContent(path="/dev/null/BUILD", content=b"")]
81
            ),
82
            "pants.engine.internals.synthetic_targets.get_synthetic_address_maps": lambda _: SyntheticAddressMaps(),
83
            "pants.engine.internals.build_files.parse_address_family": lambda *_: OptionalAddressFamily(
84
                "/dev"
85
            ),
86
            "pants.core.util_rules.env_vars.environment_vars_subset": lambda _1,
87
            _2: EnvironmentVars({}),
88
        },
89
    )
90
    assert optional_af.path == "/dev/null"
1✔
91
    assert optional_af.address_family is not None
1✔
92
    af = optional_af.address_family
1✔
93
    assert af.namespace == "/dev/null"
1✔
94
    assert len(af.name_to_target_adaptors) == 0
1✔
95

96

97
def test_extend_synthetic_target() -> None:
1✔
98
    optional_af = run_rule_with_mocks(
1✔
99
        parse_address_family,
100
        rule_args=[
101
            AddressFamilyDir("/foo"),
102
            Parser(
103
                build_root="",
104
                registered_target_types=RegisteredTargetTypes({"resource": ResourceTarget}),
105
                union_membership=UnionMembership.empty(),
106
                object_aliases=BuildFileAliases(),
107
                ignore_unrecognized_symbols=False,
108
            ),
109
            BootstrapStatus(in_progress=False),
110
            BuildFileOptions(("BUILD",)),
111
            BuildFilePreludeSymbols(FrozenDict(), ()),
112
            RegisteredTargetTypes({"resource": ResourceTarget}),
113
            UnionMembership.empty(),
114
            MaybeBuildFileDependencyRulesImplementation(None),
115
            SessionValues({CompleteEnvironmentVars: CompleteEnvironmentVars({})}),
116
        ],
117
        mock_calls={
118
            "pants.engine.intrinsics.get_digest_contents": lambda __implicitly: DigestContents(
119
                [
120
                    FileContent(
121
                        path="/foo/BUILD.1", content=b"resource(name='aaa', description='a')"
122
                    ),
123
                    FileContent(
124
                        path="/foo/BUILD.2",
125
                        content=b"resource(name='bar', description='b', _extend_synthetic=True)",
126
                    ),
127
                ]
128
            ),
129
            "pants.engine.internals.synthetic_targets.get_synthetic_address_maps": lambda __implicitly: SyntheticAddressMaps(
130
                [
131
                    SyntheticAddressMap.create(
132
                        "/foo/synthetic1",
133
                        [
134
                            TargetAdaptor("resource", "xxx", "", description="x"),
135
                        ],
136
                    ),
137
                    SyntheticAddressMap.create(
138
                        "/foo/synthetic2",
139
                        [
140
                            TargetAdaptor("resource", "yyy", ""),
141
                            TargetAdaptor("resource", "bar", "", extend=42),
142
                        ],
143
                    ),
144
                ]
145
            ),
146
            "pants.engine.internals.build_files.parse_address_family": lambda __implicitly: OptionalAddressFamily(
147
                "/",
148
                address_family=AddressFamily.create(
149
                    "/",
150
                    [],
151
                    defaults=BuildFileDefaults(
152
                        FrozenDict({"resource": FrozenDict({"description": "q"})})
153
                    ),
154
                ),
155
            ),
156
            "pants.core.util_rules.env_vars.environment_vars_subset": lambda _1,
157
            _2: EnvironmentVars({}),
158
        },
159
    )
160
    assert optional_af.path == "/foo"
1✔
161
    assert optional_af.address_family is not None
1✔
162
    af = optional_af.address_family
1✔
163
    assert af.namespace == "/foo"
1✔
164

165
    path, tgt = af.name_to_target_adaptors["aaa"]
1✔
166
    assert path == "/foo/BUILD.1"
1✔
167
    assert tgt.kwargs == FrozenDict({"description": "a"})
1✔
168

169
    path, tgt = af.name_to_target_adaptors["xxx"]
1✔
170
    assert path == "/foo/synthetic1"
1✔
171
    assert tgt.kwargs == FrozenDict({"description": "x"})
1✔
172

173
    path, tgt = af.name_to_target_adaptors["yyy"]
1✔
174
    assert path == "/foo/synthetic2"
1✔
175
    assert tgt.kwargs == FrozenDict({"description": "q"})
1✔
176

177
    path, tgt = af.name_to_target_adaptors["bar"]
1✔
178
    assert path == "/foo/BUILD.2"
1✔
179
    assert tgt.kwargs == FrozenDict({"description": "b", "extend": 42})
1✔
180

181

182
def run_prelude_parsing_rule(prelude_content: str) -> BuildFilePreludeSymbols:
1✔
183
    symbols = run_rule_with_mocks(
1✔
184
        evaluate_preludes,
185
        rule_args=[
186
            BuildFileOptions((), prelude_globs=("prelude",)),
187
            Parser(
188
                build_root="",
189
                registered_target_types=RegisteredTargetTypes({"target": GenericTarget}),
190
                union_membership=UnionMembership.empty(),
191
                object_aliases=BuildFileAliases(),
192
                ignore_unrecognized_symbols=False,
193
            ),
194
        ],
195
        mock_calls={
196
            "pants.engine.intrinsics.get_digest_contents": lambda __implicitly: DigestContents(
197
                [FileContent(path="/dev/null/prelude", content=prelude_content.encode())]
198
            )
199
        },
200
    )
201
    return symbols
1✔
202

203

204
def test_prelude_parsing_good() -> None:
1✔
205
    prelude_content = dedent(
1✔
206
        """
207
        def bar():
208
            __defaults__(all=dict(ok=123))
209
            return build_file_dir()
210

211
        def foo():
212
            return 1
213
        """
214
    )
215
    result = run_prelude_parsing_rule(prelude_content)
1✔
216
    assert result.symbols["foo"]() == 1
1✔
217

218

219
def test_prelude_parsing_syntax_error() -> None:
1✔
220
    with pytest.raises(
1✔
221
        Exception, match="Error parsing prelude file /dev/null/prelude: name 'blah' is not defined"
222
    ):
223
        run_prelude_parsing_rule("blah")
1✔
224

225

226
def test_prelude_parsing_illegal_import() -> None:
1✔
227
    prelude_content = dedent(
1✔
228
        """\
229
        import os
230
        def make_target():
231
            python_sources()
232
        """
233
    )
234
    with pytest.raises(
1✔
235
        Exception,
236
        match="Import used in /dev/null/prelude at line 1\\. Import statements are banned",
237
    ):
238
        run_prelude_parsing_rule(prelude_content)
1✔
239

240

241
def test_prelude_check_filepath() -> None:
1✔
242
    prelude_content = dedent(
1✔
243
        """
244
        build_file_dir()
245
        """
246
    )
247
    with pytest.raises(
1✔
248
        Exception,
249
        match="The BUILD file symbol `build_file_dir` may only be used in BUILD files\\. If used",
250
    ):
251
        run_prelude_parsing_rule(prelude_content)
1✔
252

253

254
def test_prelude_check_defaults() -> None:
1✔
255
    prelude_content = dedent(
1✔
256
        """
257
        __defaults__(all=dict(bad=123))
258
        """
259
    )
260
    with pytest.raises(
1✔
261
        Exception,
262
        match="The BUILD file symbol `__defaults__` may only be used in BUILD files\\. If used",
263
    ):
264
        run_prelude_parsing_rule(prelude_content)
1✔
265

266

267
def test_prelude_check_env() -> None:
1✔
268
    prelude_content = dedent(
1✔
269
        """
270
        env("nope")
271
        """
272
    )
273
    with pytest.raises(
1✔
274
        Exception,
275
        match="The BUILD file symbol `env` may only be used in BUILD files\\. If used",
276
    ):
277
        run_prelude_parsing_rule(prelude_content)
1✔
278

279

280
def test_prelude_exceptions() -> None:
1✔
281
    prelude_content = dedent(
1✔
282
        """\
283
        def abort():
284
            raise ValueError
285
        """
286
    )
287
    result = run_prelude_parsing_rule(prelude_content)
1✔
288
    assert "ValueError" not in result.symbols
1✔
289
    with pytest.raises(ValueError):
1✔
290
        result.symbols["abort"]()
1✔
291

292

293
def test_prelude_references_builtin_symbols() -> None:
1✔
294
    prelude_content = dedent(
1✔
295
        """\
296
        def make_a_target():
297
            # Can't call it outside of the context of a BUILD file, less we get internal errors
298
            target
299
        """
300
    )
301
    result = run_prelude_parsing_rule(prelude_content)
1✔
302
    # In the real world, this would define the target (note it doesn't need to return, as BUILD files
303
    # don't). In the test we're just ensuring we don't get a `NameError`
304
    result.symbols["make_a_target"]()
1✔
305

306

307
def test_prelude_type_hint_code() -> None:
1✔
308
    # Issue 18435
309
    prelude_content = dedent(
1✔
310
        """\
311
        def ecr_docker_image(
312
            *,
313
            name: Optional[str] = None,
314
            dependencies: Optional[List[str]] = None,
315
            image_tags: Optional[List[str]] = None,
316
            git_tag_prefix: Optional[str] = None,
317
            latest_tag_prefix: Optional[str] = None,
318
            buildcache_tag: str = "buildcache",
319
            image_labels: Optional[Mapping[str, str]] = None,
320
            tags: Optional[List[str]] = None,
321
            extra_build_args: Optional[List[str]] = None,
322
            source: Optional[str] = None,
323
            target_stage: Optional[str] = None,
324
            instructions: Optional[List[str]] = None,
325
            repository: Optional[str] = None,
326
            context_root: Optional[str] = None,
327
            push_in_pants_ci: bool = True,
328
            push_latest: bool = False,
329
        ) -> int:
330
            return 42
331
        """
332
    )
333
    result = run_prelude_parsing_rule(prelude_content)
1✔
334
    ecr_docker_image = result.info["ecr_docker_image"]
1✔
335
    assert ecr_docker_image.signature in (
1✔
336
        (
337
            "(*,"
338
            " name: Optional[str] = None,"
339
            " dependencies: Optional[List[str]] = None,"
340
            " image_tags: Optional[List[str]] = None,"
341
            " git_tag_prefix: Optional[str] = None,"
342
            " latest_tag_prefix: Optional[str] = None,"
343
            " buildcache_tag: str = 'buildcache',"
344
            " image_labels: Optional[Mapping[str, str]] = None,"
345
            " tags: Optional[List[str]] = None,"
346
            " extra_build_args: Optional[List[str]] = None,"
347
            " source: Optional[str] = None,"
348
            " target_stage: Optional[str] = None,"
349
            " instructions: Optional[List[str]] = None,"
350
            " repository: Optional[str] = None,"
351
            " context_root: Optional[str] = None,"
352
            " push_in_pants_ci: bool = True,"
353
            " push_latest: bool = False"
354
            ") -> int"
355
        ),
356
        (
357
            "(*,"
358
            " name: Union[str, NoneType] = None,"
359
            " dependencies: Union[List[str], NoneType] = None,"
360
            " image_tags: Union[List[str], NoneType] = None,"
361
            " git_tag_prefix: Union[str, NoneType] = None,"
362
            " latest_tag_prefix: Union[str, NoneType] = None,"
363
            " buildcache_tag: str = 'buildcache',"
364
            " image_labels: Union[Mapping[str, str], NoneType] = None,"
365
            " tags: Union[List[str], NoneType] = None,"
366
            " extra_build_args: Union[List[str], NoneType] = None,"
367
            " source: Union[str, NoneType] = None,"
368
            " target_stage: Union[str, NoneType] = None,"
369
            " instructions: Union[List[str], NoneType] = None,"
370
            " repository: Union[str, NoneType] = None,"
371
            " context_root: Union[str, NoneType] = None,"
372
            " push_in_pants_ci: bool = True,"
373
            " push_latest: bool = False"
374
            ") -> int"
375
        ),
376
    )
UNCOV
377
    assert 42 == ecr_docker_image.value()
×
378

379

380
def test_prelude_docstring_on_function() -> None:
1✔
381
    macro_docstring = "This is the doc-string for `macro_func`."
1✔
382
    prelude_content = dedent(
1✔
383
        f"""
384
        def macro_func(arg: int) -> str:
385
            '''{macro_docstring}'''
386
            pass
387
        """
388
    )
389
    result = run_prelude_parsing_rule(prelude_content)
1✔
390
    info = result.info["macro_func"]
1✔
391
    assert BuildFileSymbolInfo("macro_func", result.symbols["macro_func"]) == info
1✔
392
    assert macro_docstring == info.help
1✔
393
    assert "(arg: int) -> str" == info.signature
1✔
394
    assert {"macro_func"} == set(result.info)
1✔
395

396

397
def test_prelude_docstring_on_constant() -> None:
1✔
398
    macro_docstring = """This is the doc-string for `MACRO_CONST`.
1✔
399

400
    Use weird indentations.
401

402
    On purpose.
403
    """
404
    prelude_content = dedent(
1✔
405
        f"""
406
        Number = NewType("Number", int)
407
        MACRO_CONST: Annotated[str, Doc({macro_docstring!r})] = "value"
408
        MULTI_HINTS: Annotated[Number, "unrelated", Doc("this is it"), 24] = 42
409
        ANON: str = "undocumented"
410
        _PRIVATE: int = 42
411
        untyped = True
412
        """
413
    )
414
    result = run_prelude_parsing_rule(prelude_content)
1✔
UNCOV
415
    assert {"MACRO_CONST", "ANON", "Number", "MULTI_HINTS", "_PRIVATE", "untyped"} == set(
×
416
        result.info
417
    )
418

UNCOV
419
    info = result.info["MACRO_CONST"]
×
UNCOV
420
    assert info.value == "value"
×
UNCOV
421
    assert info.help == softwrap(macro_docstring)
×
UNCOV
422
    assert info.signature == ": str"
×
UNCOV
423
    assert info.hide_from_help is False
×
424

UNCOV
425
    multi = result.info["MULTI_HINTS"]
×
UNCOV
426
    assert multi.value == 42
×
UNCOV
427
    assert multi.help == "this is it"
×
UNCOV
428
    assert multi.signature == ": Number"
×
UNCOV
429
    assert multi.hide_from_help is False
×
430

UNCOV
431
    anon = result.info["ANON"]
×
UNCOV
432
    assert anon.value == "undocumented"
×
UNCOV
433
    assert anon.help is None
×
UNCOV
434
    assert anon.signature == ": str"
×
UNCOV
435
    assert anon.hide_from_help is False
×
436

UNCOV
437
    private = result.info["_PRIVATE"]
×
UNCOV
438
    assert private.value == 42
×
UNCOV
439
    assert private.help is None
×
UNCOV
440
    assert private.signature == ": int"
×
UNCOV
441
    assert private.hide_from_help is True
×
442

443

444
def test_prelude_reference_env_vars() -> None:
1✔
445
    prelude_content = dedent(
1✔
446
        """
447
        def macro():
448
            env("MY_ENV")
449
        """
450
    )
451
    result = run_prelude_parsing_rule(prelude_content)
1✔
452
    assert ("MY_ENV",) == result.referenced_env_vars
1✔
453

454

455
class ResolveField(StringField):
1✔
456
    alias = "resolve"
1✔
457

458

459
class MockDepsField(Dependencies):
1✔
460
    pass
1✔
461

462

463
class MockMultipleSourcesField(MultipleSourcesField):
1✔
464
    default = ("*.mock",)
1✔
465

466

467
class MockTgt(Target):
1✔
468
    alias = "mock_tgt"
1✔
469
    core_fields = (MockDepsField, MockMultipleSourcesField, Tags, ResolveField)
1✔
470

471

472
class MockSingleSourceField(SingleSourceField):
1✔
473
    pass
1✔
474

475

476
class MockGeneratedTarget(Target):
1✔
477
    alias = "generated"
1✔
478
    core_fields = (MockDepsField, Tags, MockSingleSourceField, ResolveField)
1✔
479

480

481
class MockTargetGenerator(TargetFilesGenerator):
1✔
482
    alias = "generator"
1✔
483
    core_fields = (MockMultipleSourcesField, OverridesField)
1✔
484
    generated_target_cls = MockGeneratedTarget
1✔
485
    copied_fields = ()
1✔
486
    moved_fields = (MockDepsField, Tags, ResolveField)
1✔
487

488

489
def test_resolve_address() -> None:
1✔
490
    rule_runner = RuleRunner(
1✔
491
        rules=[QueryRule(Address, [AddressInput]), QueryRule(MaybeAddress, [AddressInput])]
492
    )
493
    rule_runner.write_files({"a/b/c.txt": "", "f.txt": ""})
1✔
494

495
    def assert_is_expected(address_input: AddressInput, expected: Address) -> None:
1✔
496
        assert rule_runner.request(Address, [address_input]) == expected
1✔
497

498
    assert_is_expected(
1✔
499
        AddressInput.parse("a/b/c.txt", description_of_origin="tests"),
500
        Address("a/b", target_name=None, relative_file_path="c.txt"),
501
    )
502
    assert_is_expected(
1✔
503
        AddressInput.parse("a/b", description_of_origin="tests"),
504
        Address("a/b", target_name=None, relative_file_path=None),
505
    )
506

507
    assert_is_expected(
1✔
508
        AddressInput.parse("a/b:c", description_of_origin="tests"),
509
        Address("a/b", target_name="c"),
510
    )
511
    assert_is_expected(
1✔
512
        AddressInput.parse("a/b/c.txt:c", description_of_origin="tests"),
513
        Address("a/b", relative_file_path="c.txt", target_name="c"),
514
    )
515

516
    # Top-level addresses will not have a path_component, unless they are a file address.
517
    assert_is_expected(
1✔
518
        AddressInput.parse("f.txt:original", description_of_origin="tests"),
519
        Address("", relative_file_path="f.txt", target_name="original"),
520
    )
521
    assert_is_expected(
1✔
522
        AddressInput.parse("//:t", description_of_origin="tests"),
523
        Address("", target_name="t"),
524
    )
525

526
    bad_address_input = AddressInput.parse("a/b/fake", description_of_origin="tests")
1✔
527
    expected_err = "'a/b/fake' does not exist on disk"
1✔
528
    with engine_error(ResolveError, contains=expected_err):
1✔
529
        rule_runner.request(Address, [bad_address_input])
1✔
530
    maybe_addr = rule_runner.request(MaybeAddress, [bad_address_input])
1✔
531
    assert isinstance(maybe_addr.val, ResolveError)
1✔
532
    assert expected_err in str(maybe_addr.val)
1✔
533

534

535
@pytest.fixture
1✔
536
def target_adaptor_rule_runner() -> RuleRunner:
1✔
537
    return RuleRunner(
1✔
538
        rules=[QueryRule(TargetAdaptor, (TargetAdaptorRequest,))],
539
        target_types=[MockTgt, MockGeneratedTarget, MockTargetGenerator],
540
        objects={"parametrize": Parametrize},
541
    )
542

543

544
def test_target_adaptor_parsed_correctly(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
545
    target_adaptor_rule_runner.write_files(
1✔
546
        {
547
            "helloworld/dir/BUILD": dedent(
548
                """\
549
                mock_tgt(
550
                    fake_field=42,
551
                    dependencies=[
552
                        # Because we don't follow dependencies or even parse dependencies, this
553
                        # self-cycle should be fine.
554
                        ":dir",
555
                        ":sibling",
556
                        "helloworld/util",
557
                        "helloworld/util:tests",
558
                    ],
559
                    build_file_dir=f"build file's dir is: {build_file_dir()}"
560
                )
561

562
                mock_tgt(name='t2')
563
                """
564
            )
565
        }
566
    )
567
    target_adaptor = target_adaptor_rule_runner.request(
1✔
568
        TargetAdaptor,
569
        [TargetAdaptorRequest(Address("helloworld/dir"), description_of_origin="tests")],
570
    )
571
    assert target_adaptor.name is None
1✔
572
    assert target_adaptor.type_alias == "mock_tgt"
1✔
573
    assert target_adaptor.kwargs["dependencies"] == (
1✔
574
        ":dir",
575
        ":sibling",
576
        "helloworld/util",
577
        "helloworld/util:tests",
578
    )
579
    # NB: TargetAdaptors do not validate what fields are valid. The Target API should error
580
    # when encountering this, but it's fine at this stage.
581
    assert target_adaptor.kwargs["fake_field"] == 42
1✔
582
    assert target_adaptor.kwargs["build_file_dir"] == "build file's dir is: helloworld/dir"
1✔
583

584
    target_adaptor = target_adaptor_rule_runner.request(
1✔
585
        TargetAdaptor,
586
        [
587
            TargetAdaptorRequest(
588
                Address("helloworld/dir", target_name="t2"), description_of_origin="tests"
589
            )
590
        ],
591
    )
592
    assert target_adaptor.name == "t2"
1✔
593
    assert target_adaptor.type_alias == "mock_tgt"
1✔
594

595

596
def test_target_adaptor_defaults_applied(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
597
    target_adaptor_rule_runner.write_files(
1✔
598
        {
599
            "helloworld/dir/BUILD": dedent(
600
                """\
601
                __defaults__({mock_tgt: dict(resolve="mock")}, all=dict(tags=["24"]))
602
                mock_tgt(tags=["42"])
603
                mock_tgt(name='t2')
604
                """
605
            )
606
        }
607
    )
608
    target_adaptor = target_adaptor_rule_runner.request(
1✔
609
        TargetAdaptor,
610
        [TargetAdaptorRequest(Address("helloworld/dir"), description_of_origin="tests")],
611
    )
612
    assert target_adaptor.name is None
1✔
613
    assert target_adaptor.kwargs["resolve"] == "mock"
1✔
614
    assert target_adaptor.kwargs["tags"] == ("42",)
1✔
615

616
    target_adaptor = target_adaptor_rule_runner.request(
1✔
617
        TargetAdaptor,
618
        [
619
            TargetAdaptorRequest(
620
                Address("helloworld/dir", target_name="t2"), description_of_origin="tests"
621
            )
622
        ],
623
    )
624
    assert target_adaptor.name == "t2"
1✔
625
    assert target_adaptor.kwargs["resolve"] == "mock"
1✔
626

627
    # The defaults are not frozen until after the BUILD file have been fully parsed, so this is a
628
    # list rather than a tuple at this time.
629
    assert target_adaptor.kwargs["tags"] == ("24",)
1✔
630

631

632
def test_generated_target_defaults(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
633
    target_adaptor_rule_runner.write_files(
1✔
634
        {
635
            "BUILD": dedent(
636
                """\
637
                __defaults__({generated: dict(resolve="mock")}, all=dict(tags=["24"]))
638
                generated(name="explicit", tags=["42"], source="e.txt")
639
                generator(name='gen', sources=["g*.txt"])
640
                """
641
            ),
642
            "e.txt": "",
643
            "g1.txt": "",
644
            "g2.txt": "",
645
        }
646
    )
647

648
    explicit_target = target_adaptor_rule_runner.get_target(Address("", target_name="explicit"))
1✔
649
    assert explicit_target.address.target_name == "explicit"
1✔
650
    assert explicit_target.get(ResolveField).value == "mock"
1✔
651
    assert explicit_target.get(Tags).value == ("42",)
1✔
652

653
    implicit_target = target_adaptor_rule_runner.get_target(
1✔
654
        Address("", target_name="gen", relative_file_path="g1.txt")
655
    )
656
    assert str(implicit_target.address) == "//g1.txt:gen"
1✔
657
    assert implicit_target.get(ResolveField).value == "mock"
1✔
658
    assert implicit_target.get(Tags).value == ("24",)
1✔
659

660

661
def test_inherit_defaults(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
662
    target_adaptor_rule_runner.write_files(
1✔
663
        {
664
            "BUILD": """__defaults__(all=dict(tags=["root"]))""",
665
            "helloworld/dir/BUILD": dedent(
666
                """\
667
                __defaults__({mock_tgt: dict(resolve="mock")}, extend=True)
668
                mock_tgt()
669
                """
670
            ),
671
        }
672
    )
673
    target_adaptor = target_adaptor_rule_runner.request(
1✔
674
        TargetAdaptor,
675
        [TargetAdaptorRequest(Address("helloworld/dir"), description_of_origin="tests")],
676
    )
677
    assert target_adaptor.name is None
1✔
678
    assert target_adaptor.kwargs["resolve"] == "mock"
1✔
679

680
    # The defaults originates from a parent BUILD file, and as such has been frozen.
681
    assert target_adaptor.kwargs["tags"] == ("root",)
1✔
682

683

684
def test_parametrize_defaults(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
685
    target_adaptor_rule_runner.write_files(
1✔
686
        {
687
            "BUILD": dedent(
688
                """\
689
                __defaults__(
690
                  all=dict(
691
                    tags=parametrize(a=["a", "root"], b=["non-root", "b"])
692
                  )
693
                )
694
                """
695
            ),
696
            "helloworld/dir/BUILD": "mock_tgt()",
697
        }
698
    )
699
    target_adaptor = target_adaptor_rule_runner.request(
1✔
700
        TargetAdaptor,
701
        [TargetAdaptorRequest(Address("helloworld/dir"), description_of_origin="tests")],
702
    )
703
    assert target_adaptor.kwargs["tags"] == ParametrizeDefault(a=("a", "root"), b=("non-root", "b"))
1✔
704

705

706
def test_parametrized_groups(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
707
    def _determenistic_parametrize_group_keys(value: Mapping[str, Any]) -> dict[str, Any]:
1✔
708
        # The `parametrize` object uses a unique generated field name when splatted onto a target
709
        # (in order to provide a helpful error message in case of non-unique group names), but the
710
        # part up until `:` is determenistic on the group name, which we need to exploit in the
711
        # tests using parametrize groups.
712
        return {key.rsplit(":", 1)[0]: val for key, val in value.items()}
1✔
713

714
    target_adaptor_rule_runner.write_files(
1✔
715
        {
716
            "hello/BUILD": dedent(
717
                """\
718
                mock_tgt(
719
                  description="desc for a and b",
720
                  **parametrize("a", tags=["opt-a"], resolve="lock-a"),
721
                  **parametrize("b", tags=["opt-b"], resolve="lock-b"),
722
                )
723
                """
724
            ),
725
        }
726
    )
727

728
    target_adaptor = target_adaptor_rule_runner.request(
1✔
729
        TargetAdaptor,
730
        [TargetAdaptorRequest(Address("hello"), description_of_origin="tests")],
731
    )
732
    assert _determenistic_parametrize_group_keys(
1✔
733
        target_adaptor.kwargs
734
    ) == _determenistic_parametrize_group_keys(
735
        dict(
736
            description="desc for a and b",
737
            **Parametrize("a", tags=["opt-a"], resolve="lock-a"),
738
            **Parametrize("b", tags=["opt-b"], resolve="lock-b"),
739
        )
740
    )
741

742

743
def test_default_parametrized_groups(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
744
    target_adaptor_rule_runner.write_files(
1✔
745
        {
746
            "hello/BUILD": dedent(
747
                """\
748
                __defaults__({mock_tgt: dict(**parametrize("a", tags=["from default"]))})
749
                mock_tgt(
750
                  tags=["from target"],
751
                  **parametrize("a"),
752
                  **parametrize("b", tags=["from b"]),
753
                )
754
                """
755
            ),
756
        }
757
    )
758
    address = Address("hello")
1✔
759
    target_adaptor = target_adaptor_rule_runner.request(
1✔
760
        TargetAdaptor,
761
        [TargetAdaptorRequest(address, description_of_origin="tests")],
762
    )
763
    targets = tuple(Parametrize.expand(address, target_adaptor.kwargs))
1✔
764
    assert targets == (
1✔
765
        (address.parametrize(dict(parametrize="a")), dict(tags=("from target",))),
766
        (address.parametrize(dict(parametrize="b")), dict(tags=("from b",))),
767
    )
768

769

770
def test_default_parametrized_groups_with_parametrizations(
1✔
771
    target_adaptor_rule_runner: RuleRunner,
772
) -> None:
773
    target_adaptor_rule_runner.write_files(
1✔
774
        {
775
            "src/BUILD": dedent(
776
                """
777
                __defaults__({
778
                  mock_tgt: dict(
779
                    **parametrize(
780
                      "py310-compat",
781
                      resolve="service-a",
782
                      tags=[
783
                        "CPython == 3.9.*",
784
                        "CPython == 3.10.*",
785
                      ]
786
                    ),
787
                    **parametrize(
788
                      "py39-compat",
789
                      resolve=parametrize(
790
                        "service-b",
791
                        "service-c",
792
                        "service-d",
793
                      ),
794
                      tags=[
795
                        "CPython == 3.9.*",
796
                      ]
797
                    )
798
                  )
799
                })
800
                mock_tgt()
801
                """
802
            ),
803
        }
804
    )
805
    address = Address("src")
1✔
806
    target_adaptor = target_adaptor_rule_runner.request(
1✔
807
        TargetAdaptor,
808
        [TargetAdaptorRequest(address, description_of_origin="tests")],
809
    )
810
    targets = tuple(Parametrize.expand(address, target_adaptor.kwargs))
1✔
811
    assert targets == (
1✔
812
        (
813
            address.parametrize(dict(parametrize="py310-compat")),
814
            dict(
815
                tags=("CPython == 3.9.*", "CPython == 3.10.*"),
816
                resolve="service-a",
817
            ),
818
        ),
819
        (
820
            address.parametrize(dict(parametrize="py39-compat", resolve="service-b")),
821
            dict(tags=("CPython == 3.9.*",), resolve="service-b"),
822
        ),
823
        (
824
            address.parametrize(dict(parametrize="py39-compat", resolve="service-c")),
825
            dict(tags=("CPython == 3.9.*",), resolve="service-c"),
826
        ),
827
        (
828
            address.parametrize(dict(parametrize="py39-compat", resolve="service-d")),
829
            dict(tags=("CPython == 3.9.*",), resolve="service-d"),
830
        ),
831
    )
832

833

834
def test_augment_target_field_defaults(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
835
    target_adaptor_rule_runner.write_files(
1✔
836
        {
837
            "BUILD": dedent(
838
                """
839
                __defaults__(all=dict(tags=["default-tag"]))
840
                mock_tgt(
841
                  sources=["*.added", *mock_tgt.sources.default],
842
                  tags=["custom-tag", *mock_tgt.tags.default],
843
                )
844
                """
845
            ),
846
        },
847
    )
848
    target_adaptor = target_adaptor_rule_runner.request(
1✔
849
        TargetAdaptor,
850
        [TargetAdaptorRequest(Address(""), description_of_origin="tests")],
851
    )
852
    assert target_adaptor.kwargs["sources"] == ("*.added", "*.mock")
1✔
853
    assert target_adaptor.kwargs["tags"] == ("custom-tag", "default-tag")
1✔
854

855

856
def test_target_adaptor_not_found(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
857
    with pytest.raises(ExecutionError) as exc:
1✔
858
        target_adaptor_rule_runner.request(
1✔
859
            TargetAdaptor,
860
            [TargetAdaptorRequest(Address("helloworld"), description_of_origin="tests")],
861
        )
862
    assert "Directory \\'helloworld\\' does not contain any BUILD files" in str(exc)
1✔
863

864
    target_adaptor_rule_runner.write_files({"helloworld/BUILD": "mock_tgt(name='other_tgt')"})
1✔
865
    expected_rx_str = re.escape(
1✔
866
        "The target name ':helloworld' is not defined in the directory helloworld"
867
    )
868
    with pytest.raises(ExecutionError, match=expected_rx_str):
1✔
869
        target_adaptor_rule_runner.request(
1✔
870
            TargetAdaptor,
871
            [TargetAdaptorRequest(Address("helloworld"), description_of_origin="tests")],
872
        )
873

874

875
def test_build_file_address() -> None:
1✔
876
    rule_runner = RuleRunner(
1✔
877
        rules=[QueryRule(BuildFileAddress, [BuildFileAddressRequest])], target_types=[MockTgt]
878
    )
879
    rule_runner.write_files({"helloworld/BUILD.ext": "mock_tgt()"})
1✔
880

881
    def assert_bfa_resolved(address: Address) -> None:
1✔
882
        expected_bfa = BuildFileAddress(address, "helloworld/BUILD.ext")
1✔
883
        bfa = rule_runner.request(
1✔
884
            BuildFileAddress, [BuildFileAddressRequest(address, description_of_origin="tests")]
885
        )
886
        assert bfa == expected_bfa
1✔
887

888
    assert_bfa_resolved(Address("helloworld"))
1✔
889
    # Generated targets should use their target generator's BUILD file.
890
    assert_bfa_resolved(Address("helloworld", generated_name="f.txt"))
1✔
891
    assert_bfa_resolved(Address("helloworld", relative_file_path="f.txt"))
1✔
892

893

894
def test_build_files_share_globals() -> None:
1✔
895
    """Test that a macro in a prelude can reference another macro in another prelude.
896

897
    At some point a change was made to separate the globals/locals dict (unintentional) which has
898
    the unintended side effect of having the `__globals__` of a macro not contain references to
899
    every other symbol in every other prelude.
900
    """
901

902
    symbols = run_rule_with_mocks(
1✔
903
        evaluate_preludes,
904
        rule_args=[
905
            BuildFileOptions((), prelude_globs=("prelude",)),
906
            Parser(
907
                build_root="",
908
                registered_target_types=RegisteredTargetTypes({}),
909
                union_membership=UnionMembership.empty(),
910
                object_aliases=BuildFileAliases(),
911
                ignore_unrecognized_symbols=False,
912
            ),
913
        ],
914
        mock_calls={
915
            "pants.engine.intrinsics.get_digest_contents": lambda _: DigestContents(
916
                [
917
                    FileContent(
918
                        path="/dev/null/prelude1",
919
                        content=dedent(
920
                            """\
921
                                def hello():
922
                                    pass
923
                                """
924
                        ).encode(),
925
                    ),
926
                    FileContent(
927
                        path="/dev/null/prelude2",
928
                        content=dedent(
929
                            """\
930
                                def world():
931
                                    pass
932
                                """
933
                        ).encode(),
934
                    ),
935
                ]
936
            ),
937
        },
938
    )
939
    assert symbols.symbols["hello"].__globals__ is symbols.symbols["world"].__globals__
1✔
940
    assert "world" in symbols.symbols["hello"].__globals__
1✔
941
    assert "hello" in symbols.symbols["world"].__globals__
1✔
942

943

944
def test_macro_undefined_symbol_bootstrap() -> None:
1✔
945
    # Tests that an undefined symbol in a macro is ignored while bootstrapping. Ignoring undeclared
946
    # symbols during parsing is insufficient, because we would need to re-evaluate the preludes after
947
    # adding each additional undefined symbol to scope.
948
    rule_runner = RuleRunner(
1✔
949
        rules=[QueryRule(AddressFamily, [AddressFamilyDir])],
950
        is_bootstrap=True,
951
    )
952
    rule_runner.set_options(
1✔
953
        args=("--build-file-prelude-globs=prelude.py",),
954
    )
955
    rule_runner.write_files(
1✔
956
        {
957
            "prelude.py": dedent(
958
                """
959
                def uses_undefined():
960
                    return this_is_undefined()
961
                """
962
            ),
963
            "BUILD": dedent(
964
                """
965
                uses_undefined()
966
                """
967
            ),
968
        }
969
    )
970

971
    # Parse the root BUILD file.
972
    address_family = rule_runner.request(AddressFamily, [AddressFamilyDir("")])
1✔
973
    assert not address_family.name_to_target_adaptors
1✔
974

975

976
def test_default_plugin_field_bootstrap() -> None:
1✔
977
    # Tests that an unknown field in `__defaults__` is ignored while bootstrapping.
978
    rule_runner = RuleRunner(
1✔
979
        rules=[QueryRule(AddressFamily, [AddressFamilyDir])],
980
        target_types=[MockTgt],
981
        is_bootstrap=True,
982
    )
983
    rule_runner.write_files(
1✔
984
        {
985
            "BUILD": dedent(
986
                """
987
                __defaults__({mock_tgt: dict(presumably_plugin_field="default", tags=["ok"])})
988
                """
989
            ),
990
        }
991
    )
992

993
    # Parse the root BUILD file.
994
    address_family = rule_runner.request(AddressFamily, [AddressFamilyDir("")])
1✔
995
    assert dict(tags=("ok",)) == dict(address_family.defaults["mock_tgt"])
1✔
996

997

998
def test_environment_target_macro_field_value() -> None:
1✔
999
    rule_runner = RuleRunner(
1✔
1000
        rules=[QueryRule(AddressFamily, [AddressFamilyDir])],
1001
        target_types=[MockTgt],
1002
        is_bootstrap=True,
1003
    )
1004
    rule_runner.set_options(
1✔
1005
        args=("--build-file-prelude-globs=prelude.py",),
1006
    )
1007
    rule_runner.write_files(
1✔
1008
        {
1009
            "prelude.py": dedent(
1010
                """
1011
                def tags():
1012
                    return ["foo", "bar"]
1013
                """
1014
            ),
1015
            "BUILD": dedent(
1016
                """
1017
                mock_tgt(name="tgt", tags=tags())
1018
                """
1019
            ),
1020
        }
1021
    )
1022

1023
    # Parse the root BUILD file.
1024
    address_family = rule_runner.request(AddressFamily, [AddressFamilyDir("")])
1✔
1025
    tgt = address_family.name_to_target_adaptors["tgt"][1]
1✔
1026
    # We're pretending that field values returned from a called macro function doesn't exist during
1027
    # bootstrap. This is to allow the semi-dubios use of macro calls for environment target field
1028
    # values that are not required, and depending on how they are used, it may work to only have
1029
    # those field values set during normal lookup.
1030
    assert not tgt.kwargs
1✔
1031
    assert tgt == TargetAdaptor("mock_tgt", "tgt", "BUILD:2")
1✔
1032

1033

1034
def test_build_file_env_vars(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
1035
    target_adaptor_rule_runner.write_files(
1✔
1036
        {
1037
            "BUILD": dedent(
1038
                """
1039
                mock_tgt(
1040
                  description=env("MOCK_DESC"),
1041
                  tags=[
1042
                    env("DEF", "default"),
1043
                    env("TAG", "default"),
1044
                  ]
1045
                )
1046
                """
1047
            ),
1048
        },
1049
    )
1050
    target_adaptor_rule_runner.set_options([], env={"MOCK_DESC": "from env", "TAG": "tag"})
1✔
1051
    target_adaptor = target_adaptor_rule_runner.request(
1✔
1052
        TargetAdaptor,
1053
        [TargetAdaptorRequest(Address(""), description_of_origin="tests")],
1054
    )
1055
    assert target_adaptor.kwargs["description"] == "from env"
1✔
1056
    assert target_adaptor.kwargs["tags"] == ("default", "tag")
1✔
1057

1058

1059
def test_prelude_env_vars(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
1060
    target_adaptor_rule_runner.write_files(
1✔
1061
        {
1062
            "prelude.py": dedent(
1063
                """
1064
                def macro_val():
1065
                    return env("MACRO_ENV")
1066
                """
1067
            ),
1068
            "BUILD": dedent(
1069
                """
1070
                mock_tgt(
1071
                  description=macro_val(),
1072
                )
1073
                """
1074
            ),
1075
        },
1076
    )
1077
    target_adaptor_rule_runner.set_options(
1✔
1078
        args=("--build-file-prelude-globs=prelude.py",),
1079
        env={"MACRO_ENV": "from env"},
1080
    )
1081
    target_adaptor = target_adaptor_rule_runner.request(
1✔
1082
        TargetAdaptor,
1083
        [TargetAdaptorRequest(Address(""), description_of_origin="tests")],
1084
    )
1085
    assert target_adaptor.kwargs["description"] == "from env"
1✔
1086

1087

1088
def test_invalid_build_file_env_vars(caplog, target_adaptor_rule_runner: RuleRunner) -> None:
1✔
1089
    target_adaptor_rule_runner.write_files(
1✔
1090
        {
1091
            "src/bad/BUILD": dedent(
1092
                """
1093
                DOES_NOT_WORK = "var_name1"
1094
                DO_THIS_INSTEAD = env("var_name2")
1095

1096
                mock_tgt(description=env(DOES_NOT_WORK), tags=[DO_THIS_INSTEAD])
1097
                """
1098
            ),
1099
        },
1100
    )
1101
    target_adaptor_rule_runner.set_options(
1✔
1102
        [], env={"var_name1": "desc from env", "var_name2": "tag-from-env"}
1103
    )
1104
    target_adaptor = target_adaptor_rule_runner.request(
1✔
1105
        TargetAdaptor,
1106
        [TargetAdaptorRequest(Address("src/bad"), description_of_origin="tests")],
1107
    )
1108
    assert target_adaptor.kwargs["description"] is None
1✔
1109
    assert target_adaptor.kwargs["tags"] == ("tag-from-env",)
1✔
1110
    assert_logged(
1✔
1111
        caplog,
1112
        [
1113
            (
1114
                logging.WARNING,
1115
                softwrap(
1116
                    """
1117
                    src/bad/BUILD:5: Only constant string values as variable name to `env()` is
1118
                    currently supported. This `env()` call will always result in the default value
1119
                    only.
1120
                    """
1121
                ),
1122
            ),
1123
        ],
1124
    )
1125

1126

1127
def test_build_file_parse_error(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
1128
    target_adaptor_rule_runner.write_files(
1✔
1129
        {
1130
            "src/bad/BUILD": dedent(
1131
                """\
1132
                mock_tgt(
1133
                  name="foo"
1134
                  tags=[]
1135
                )
1136
                """
1137
            ),
1138
        },
1139
    )
1140
    with pytest.raises(ExecutionError, match='File "src/bad/BUILD", line 2'):
1✔
1141
        target_adaptor_rule_runner.request(
1✔
1142
            TargetAdaptor,
1143
            [
1144
                TargetAdaptorRequest(
1145
                    Address("src/bad", target_name="foo"), description_of_origin="test"
1146
                )
1147
            ],
1148
        )
1149

1150

1151
def test_build_file_description_of_origin(target_adaptor_rule_runner: RuleRunner) -> None:
1✔
1152
    target_adaptor_rule_runner.write_files(
1✔
1153
        {
1154
            "src/BUILD": dedent(
1155
                """\
1156
                # Define a target..
1157
                mock_tgt(name="foo")
1158
                """
1159
            ),
1160
        },
1161
    )
1162
    target_adaptor = target_adaptor_rule_runner.request(
1✔
1163
        TargetAdaptor,
1164
        [TargetAdaptorRequest(Address("src", target_name="foo"), description_of_origin="test")],
1165
    )
1166
    assert "src/BUILD:2" == target_adaptor.description_of_origin
1✔
1167

1168

1169
@pytest.mark.parametrize(
1✔
1170
    "filename, contents, expect_failure, expected_message",
1171
    [
1172
        ("BUILD", "data()", False, None),
1173
        (
1174
            "BUILD.qq",
1175
            "data()qq",
1176
            True,
1177
            "Error parsing BUILD file BUILD.qq:1: invalid syntax\n  data()qq\n        ^",
1178
        ),
1179
        (
1180
            "foo/BUILD",
1181
            "data()\nqwe asd",
1182
            True,
1183
            "Error parsing BUILD file foo/BUILD:2: invalid syntax\n  qwe asd\n      ^",
1184
        ),
1185
    ],
1186
)
1187
def test_build_file_syntax_error(filename, contents, expect_failure, expected_message):
1✔
1188
    class MockFileContent:
1✔
1189
        def __init__(self, path, content):
1✔
1190
            self.path = path
1✔
1191
            self.content = content
1✔
1192

1193
    if expect_failure:
1✔
1194
        with pytest.raises(BuildFileSyntaxError) as e:
1✔
1195
            BUILDFileEnvVarExtractor.get_env_vars(MockFileContent(filename, contents))
1✔
1196

1197
        formatted = str(e.value)
1✔
1198

1199
        assert formatted == expected_message
1✔
1200

1201
    else:
1202
        BUILDFileEnvVarExtractor.get_env_vars(MockFileContent(filename, contents))
1✔
1203

1204

1205
def test_build_file_duplicate_declared_names() -> None:
1✔
1206
    rule_runner = RuleRunner(
1✔
1207
        rules=[QueryRule(AddressFamily, [AddressFamilyDir])],
1208
        target_types=[MockTgt],
1209
    )
1210
    rule_runner.write_files(
1✔
1211
        {
1212
            "src/BUILD.foo": dedent(
1213
                """\
1214
                # Define a target.
1215
                mock_tgt(name="foo")
1216
                """
1217
            ),
1218
            "src/BUILD.bar": dedent(
1219
                """\
1220
                # Define a target..
1221
                mock_tgt(name="foo")
1222
                """
1223
            ),
1224
        },
1225
    )
1226
    with pytest.raises(
1✔
1227
        ExecutionError,
1228
        match="A target already exists at `src/BUILD.bar` with name `foo` and target type `mock_tgt`",
1229
    ):
1230
        _ = rule_runner.request(AddressFamily, [AddressFamilyDir("src")])
1✔
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

© 2025 Coveralls, Inc