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

pantsbuild / pants / 24604025132

18 Apr 2026 11:49AM UTC coverage: 92.478% (-0.4%) from 92.924%
24604025132

Pull #23268

github

web-flow
Merge c60f47029 into a92bc34b6
Pull Request #23268: perf: Remove python coroutine/trampoline overhead in awaits for ~22% faster `dependencies` goal

31 of 37 new or added lines in 4 files covered. (83.78%)

443 existing lines in 21 files now uncovered.

91210 of 98629 relevant lines covered (92.48%)

4.03 hits per line

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

80.99
/src/python/pants/core/goals/test_test.py
1
# Copyright 2018 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
from abc import abstractmethod
1✔
7
from collections.abc import Iterable
1✔
8
from dataclasses import dataclass
1✔
9
from functools import partial
1✔
10
from pathlib import Path
1✔
11
from textwrap import dedent
1✔
12
from typing import Any
1✔
13

14
import pytest
1✔
15
from _pytest.monkeypatch import MonkeyPatch
1✔
16

17
from pants.backend.python.goals import package_pex_binary
1✔
18
from pants.backend.python.target_types import PexBinary, PythonSourcesGeneratorTarget
1✔
19
from pants.backend.python.target_types_rules import rules as python_target_type_rules
1✔
20
from pants.backend.python.util_rules import pex_from_targets
1✔
21
from pants.core.environments.rules import ChosenLocalEnvironmentName
1✔
22
from pants.core.goals.test import (
1✔
23
    BuildPackageDependenciesRequest,
24
    BuiltPackageDependencies,
25
    ConsoleCoverageReport,
26
    CoverageData,
27
    CoverageDataCollection,
28
    CoverageReports,
29
    RuntimePackageDependenciesField,
30
    ShowOutput,
31
    Test,
32
    TestDebugRequest,
33
    TestFieldSet,
34
    TestRequest,
35
    TestResult,
36
    TestSubsystem,
37
    TestTimeoutField,
38
    _format_test_rerun_command,
39
    _format_test_summary,
40
    build_runtime_package_dependencies,
41
    run_tests,
42
)
43
from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem
1✔
44
from pants.core.util_rules.distdir import DistDir
1✔
45
from pants.core.util_rules.partitions import Partition, Partitions
1✔
46
from pants.engine.addresses import Address
1✔
47
from pants.engine.console import Console
1✔
48
from pants.engine.environment import EnvironmentName
1✔
49
from pants.engine.fs import EMPTY_DIGEST, EMPTY_FILE_DIGEST, FileDigest, Snapshot, Workspace
1✔
50
from pants.engine.internals.session import RunId
1✔
51
from pants.engine.platform import Platform
1✔
52
from pants.engine.process import (
1✔
53
    InteractiveProcess,
54
    InteractiveProcessResult,
55
    ProcessExecutionEnvironment,
56
    ProcessResultMetadata,
57
)
58
from pants.engine.target import (
1✔
59
    BoolField,
60
    Field,
61
    MultipleSourcesField,
62
    Target,
63
    TargetRootsToFieldSets,
64
    TargetRootsToFieldSetsRequest,
65
)
66
from pants.engine.unions import UnionMembership, UnionRule
1✔
67
from pants.option.option_types import SkipOption
1✔
68
from pants.option.subsystem import Subsystem
1✔
69
from pants.testutil.option_util import create_goal_subsystem, create_subsystem
1✔
70
from pants.testutil.python_rule_runner import PythonRuleRunner
1✔
71
from pants.testutil.rule_runner import QueryRule, mock_console, run_rule_with_mocks
1✔
72
from pants.util.logging import LogLevel
1✔
73

74

75
def make_process_result_metadata(
1✔
76
    source: str,
77
    *,
78
    environment_name: str | None = None,
79
    docker_image: str | None = None,
80
    remote_execution: bool = False,
81
    total_elapsed_ms: int = 999,
82
    source_run_id: int = 0,
83
) -> ProcessResultMetadata:
84
    return ProcessResultMetadata(
1✔
85
        total_elapsed_ms,
86
        ProcessExecutionEnvironment(
87
            environment_name=environment_name,
88
            # TODO: None of the following are currently consumed in these tests.
89
            platform=Platform.create_for_localhost().value,
90
            docker_image=docker_image,
91
            remote_execution=remote_execution,
92
            remote_execution_extra_platform_properties=[],
93
            execute_in_workspace=False,
94
            keep_sandboxes="never",
95
        ),
96
        source,
97
        source_run_id,
98
    )
99

100

101
def make_test_result(
1✔
102
    addresses: Iterable[Address],
103
    exit_code: None | int,
104
    stdout_bytes: bytes = b"",
105
    stdout_digest: FileDigest = EMPTY_FILE_DIGEST,
106
    stderr_bytes: bytes = b"",
107
    stderr_digest: FileDigest = EMPTY_FILE_DIGEST,
108
    coverage_data: CoverageData | None = None,
109
    output_setting: ShowOutput = ShowOutput.NONE,
110
    result_metadata: None | ProcessResultMetadata = None,
111
) -> TestResult:
112
    """Create a TestResult with default values for most fields."""
113
    return TestResult(
1✔
114
        addresses=tuple(addresses),
115
        exit_code=exit_code,
116
        stdout_bytes=stdout_bytes,
117
        stdout_digest=stdout_digest,
118
        stderr_bytes=stderr_bytes,
119
        stderr_digest=stderr_digest,
120
        coverage_data=coverage_data,
121
        output_setting=output_setting,
122
        result_metadata=result_metadata,
123
    )
124

125

126
class MockMultipleSourcesField(MultipleSourcesField):
1✔
127
    pass
1✔
128

129

130
class MockTestTimeoutField(TestTimeoutField):
1✔
131
    pass
1✔
132

133

134
class MockSkipTestsField(BoolField):
1✔
135
    alias = "skip_test"
1✔
136
    default = False
1✔
137

138

139
class MockRequiredField(Field):
1✔
140
    alias = "required"
1✔
141
    required = True
1✔
142

143

144
class MockTarget(Target):
1✔
145
    alias = "mock_target"
1✔
146
    core_fields = (MockMultipleSourcesField, MockSkipTestsField, MockRequiredField)
1✔
147

148

149
@dataclass(frozen=True)
1✔
150
class MockCoverageData(CoverageData):
1✔
151
    addresses: Iterable[Address]
1✔
152

153

154
class MockCoverageDataCollection(CoverageDataCollection):
1✔
155
    element_type = MockCoverageData
1✔
156

157

158
@dataclass(frozen=True)
1✔
159
class MockTestFieldSet(TestFieldSet):
1✔
160
    required_fields = (MultipleSourcesField, MockRequiredField)
1✔
161
    sources: MultipleSourcesField
1✔
162
    required: MockRequiredField
1✔
163

164
    @classmethod
1✔
165
    def opt_out(cls, tgt: Target) -> bool:
1✔
166
        return tgt.get(MockSkipTestsField).value
1✔
167

168

169
class MockTestSubsystem(Subsystem):
1✔
170
    options_scope = "mock-test"
1✔
171
    help = "Not real"
1✔
172
    name = "Mock"
1✔
173
    skip = SkipOption("test")
1✔
174

175

176
class MockTestRequest(TestRequest):
1✔
177
    field_set_type = MockTestFieldSet
1✔
178
    tool_subsystem = MockTestSubsystem  # type: ignore[assignment]
1✔
179

180
    @staticmethod
1✔
181
    @abstractmethod
1✔
182
    def exit_code(_: Iterable[Address]) -> int:
1✔
183
        pass
×
184

185
    @staticmethod
1✔
186
    @abstractmethod
1✔
187
    def skipped(_: Iterable[Address]) -> bool:
1✔
188
        pass
×
189

190
    @classmethod
1✔
191
    def test_result(cls, field_sets: Iterable[MockTestFieldSet]) -> TestResult:
1✔
UNCOV
192
        addresses = tuple(field_set.address for field_set in field_sets)
×
UNCOV
193
        return make_test_result(
×
194
            addresses,
195
            exit_code=cls.exit_code(addresses),
196
            coverage_data=MockCoverageData(addresses),
197
            output_setting=ShowOutput.ALL,
198
            result_metadata=None if cls.skipped(addresses) else make_process_result_metadata("ran"),
199
        )
200

201

202
class SuccessfulRequest(MockTestRequest):
1✔
203
    @staticmethod
1✔
204
    def exit_code(_: Iterable[Address]) -> int:
1✔
UNCOV
205
        return 0
×
206

207
    @staticmethod
1✔
208
    def skipped(_: Iterable[Address]) -> bool:
1✔
UNCOV
209
        return False
×
210

211

212
class ConditionallySucceedsRequest(MockTestRequest):
1✔
213
    @staticmethod
1✔
214
    def exit_code(addresses: Iterable[Address]) -> int:
1✔
UNCOV
215
        if any(address.target_name == "bad" for address in addresses):
×
UNCOV
216
            return 27
×
UNCOV
217
        return 0
×
218

219
    @staticmethod
1✔
220
    def skipped(addresses: Iterable[Address]) -> bool:
1✔
UNCOV
221
        return any(address.target_name == "skipped" for address in addresses)
×
222

223

224
def mock_partitioner(
1✔
225
    __implicitly: tuple,
226
) -> Partitions[MockTestFieldSet, Any]:
UNCOV
227
    request, typ = next(iter(__implicitly[0].items()))
×
UNCOV
228
    assert typ == TestRequest.PartitionRequest
×
UNCOV
229
    return Partitions(Partition((field_set,), None) for field_set in request.field_sets)
×
230

231

232
def mock_test_partition(__implicitly: tuple) -> TestResult:
1✔
UNCOV
233
    request, typ = next(iter(__implicitly[0].items()))
×
UNCOV
234
    assert typ == TestRequest.Batch
×
UNCOV
235
    request_subtype = {cls.Batch: cls for cls in MockTestRequest.__subclasses__()}[type(request)]
×
UNCOV
236
    return request_subtype.test_result(request.elements)
×
237

238

239
@pytest.fixture
1✔
240
def rule_runner() -> PythonRuleRunner:
1✔
241
    return PythonRuleRunner()
1✔
242

243

244
def make_target(address: Address | None = None, *, skip: bool = False) -> Target:
1✔
245
    if address is None:
1✔
246
        address = Address("", target_name="tests")
1✔
247
    return MockTarget({MockSkipTestsField.alias: skip, MockRequiredField.alias: "present"}, address)
1✔
248

249

250
def run_test_rule(
1✔
251
    rule_runner: PythonRuleRunner,
252
    *,
253
    request_type: type[TestRequest],
254
    targets: list[Target],
255
    debug: bool = False,
256
    use_coverage: bool = False,
257
    experimental_report_test_result_info: bool = False,
258
    report: bool = False,
259
    report_dir: str = TestSubsystem.default_report_path,
260
    output: ShowOutput = ShowOutput.ALL,
261
    valid_targets: bool = True,
262
    show_rerun_command: bool = False,
263
    run_id: RunId = RunId(999),
264
) -> tuple[int, str]:
265
    test_subsystem = create_goal_subsystem(
1✔
266
        TestSubsystem,
267
        debug=debug,
268
        debug_adapter=False,
269
        use_coverage=use_coverage,
270
        experimental_report_test_result_info=experimental_report_test_result_info,
271
        report=report,
272
        report_dir=report_dir,
273
        xml_dir=None,
274
        output=output,
275
        extra_env_vars=[],
276
        shard="",
277
        batch_size=1,
278
        show_rerun_command=show_rerun_command,
279
    )
280
    debug_adapter_subsystem = create_subsystem(
1✔
281
        DebugAdapterSubsystem,
282
        host="127.0.0.1",
283
        port="5678",
284
    )
285
    workspace = Workspace(rule_runner.scheduler, _enforce_effects=False)
1✔
286
    union_membership = UnionMembership.from_rules(
1✔
287
        {
288
            UnionRule(TestFieldSet, MockTestFieldSet),
289
            UnionRule(TestRequest, request_type),
290
            UnionRule(TestRequest.PartitionRequest, request_type.PartitionRequest),
291
            UnionRule(TestRequest.Batch, request_type.Batch),
292
            UnionRule(CoverageDataCollection, MockCoverageDataCollection),
293
        }
294
    )
295

296
    def mock_find_valid_field_sets(
1✔
297
        _: TargetRootsToFieldSetsRequest,
298
    ) -> TargetRootsToFieldSets:
299
        if not valid_targets:
1✔
300
            return TargetRootsToFieldSets({})
1✔
301
        return TargetRootsToFieldSets(
1✔
302
            {
303
                tgt: [request_type.field_set_type.create(tgt)]
304
                for tgt in targets
305
                if request_type.field_set_type.is_applicable(tgt)
306
            }
307
        )
308

309
    def mock_debug_request(
1✔
310
        __implicitly: tuple,
311
    ) -> TestDebugRequest:
UNCOV
312
        return TestDebugRequest(InteractiveProcess(["/bin/example"], input_digest=EMPTY_DIGEST))
×
313

314
    def mock_coverage_report_generation(
1✔
315
        __implicitly: tuple,
316
    ) -> CoverageReports:
UNCOV
317
        coverage_data_collection, typ = next(iter(__implicitly[0].items()))
×
UNCOV
318
        assert typ == CoverageDataCollection
×
UNCOV
319
        addresses = ", ".join(
×
320
            address.spec
321
            for coverage_data in coverage_data_collection
322
            for address in coverage_data.addresses
323
        )
UNCOV
324
        console_report = ConsoleCoverageReport(
×
325
            coverage_insufficient=False, report=f"Ran coverage on {addresses}"
326
        )
UNCOV
327
        return CoverageReports(reports=(console_report,))
×
328

329
    with mock_console(rule_runner.options_bootstrapper) as (console, stdio_reader):
1✔
330
        result: Test = run_rule_with_mocks(
1✔
331
            run_tests,
332
            rule_args=[
333
                console,
334
                test_subsystem,
335
                debug_adapter_subsystem,
336
                workspace,
337
                union_membership,
338
                DistDir(relpath=Path("dist")),
339
                run_id,
340
                ChosenLocalEnvironmentName(EnvironmentName(None)),
341
            ],
342
            mock_calls={
343
                "pants.core.goals.test.partition_tests": mock_partitioner,
344
                "pants.core.environments.rules.resolve_single_environment_name": lambda _a: EnvironmentName(
345
                    None
346
                ),
347
                "pants.core.goals.test.test_batch_to_debug_request": mock_debug_request,
348
                "pants.core.goals.test.test_batch_to_debug_adapter_request": mock_debug_request,
349
                "pants.core.goals.test.run_test_batch": mock_test_partition,
350
                "pants.core.goals.test.create_coverage_report": mock_coverage_report_generation,
351
                "pants.engine.internals.specs_rules.find_valid_field_sets_for_target_roots": mock_find_valid_field_sets,
352
                "pants.engine.intrinsics.merge_digests": lambda _: EMPTY_DIGEST,
353
                "pants.engine.intrinsics._interactive_process": lambda _p,
354
                _e: InteractiveProcessResult(0),
355
            },
356
            union_membership=union_membership,
357
            # We don't want temporary warnings to interfere with our expected output.
358
            show_warnings=False,
359
        )
360
        assert not stdio_reader.get_stdout()
1✔
361
        return result.exit_code, stdio_reader.get_stderr()
1✔
362

363

364
def test_invalid_target_noops(rule_runner: PythonRuleRunner) -> None:
1✔
365
    exit_code, stderr = run_test_rule(
1✔
366
        rule_runner,
367
        request_type=SuccessfulRequest,
368
        targets=[make_target()],
369
        valid_targets=False,
370
    )
UNCOV
371
    assert exit_code == 0
×
UNCOV
372
    assert stderr.strip() == ""
×
373

374

375
def test_skipped_target_noops(rule_runner: PythonRuleRunner) -> None:
1✔
376
    exit_code, stderr = run_test_rule(
1✔
377
        rule_runner,
378
        request_type=ConditionallySucceedsRequest,
379
        targets=[make_target(Address("", target_name="bad"), skip=True)],
380
    )
UNCOV
381
    assert exit_code == 0
×
UNCOV
382
    assert stderr.strip() == ""
×
383

384

385
@pytest.mark.parametrize(
1✔
386
    ("show_rerun_command", "expected_stderr"),
387
    [
388
        (
389
            False,
390
            # the summary is for humans, so we test it literally, to make sure the formatting is good
391
            dedent(
392
                """\
393

394
                ✓ //:good succeeded in 1.00s (memoized).
395
                ✕ //:bad failed in 1.00s (memoized).
396
                """
397
            ),
398
        ),
399
        (
400
            True,
401
            dedent(
402
                """\
403

404
                ✓ //:good succeeded in 1.00s (memoized).
405
                ✕ //:bad failed in 1.00s (memoized).
406

407
                To rerun the failing tests, use:
408

409
                    pants test //:bad
410
                """
411
            ),
412
        ),
413
    ],
414
)
415
def test_summary(
1✔
416
    rule_runner: PythonRuleRunner, show_rerun_command: bool, expected_stderr: str
417
) -> None:
418
    good_address = Address("", target_name="good")
1✔
419
    bad_address = Address("", target_name="bad")
1✔
420
    skipped_address = Address("", target_name="skipped")
1✔
421

422
    exit_code, stderr = run_test_rule(
1✔
423
        rule_runner,
424
        request_type=ConditionallySucceedsRequest,
425
        targets=[make_target(good_address), make_target(bad_address), make_target(skipped_address)],
426
        show_rerun_command=show_rerun_command,
427
    )
UNCOV
428
    assert exit_code == ConditionallySucceedsRequest.exit_code((bad_address,))
×
UNCOV
429
    assert stderr == expected_stderr
×
430

431

432
def _assert_test_summary(
1✔
433
    expected: str,
434
    *,
435
    exit_code: int | None,
436
    run_id: int,
437
    result_metadata: ProcessResultMetadata | None,
438
) -> None:
439
    assert expected == _format_test_summary(
1✔
440
        make_test_result(
441
            [Address(spec_path="", target_name="dummy_address")],
442
            exit_code=exit_code,
443
            result_metadata=result_metadata,
444
            output_setting=ShowOutput.FAILED,
445
        ),
446
        RunId(run_id),
447
        Console(use_colors=False),
448
    )
449

450

451
def test_format_summary_remote(rule_runner: PythonRuleRunner) -> None:
1✔
452
    _assert_test_summary(
1✔
453
        "✓ //:dummy_address succeeded in 0.05s (ran in remote environment `ubuntu`).",
454
        exit_code=0,
455
        run_id=0,
456
        result_metadata=make_process_result_metadata(
457
            "ran", environment_name="ubuntu", remote_execution=True, total_elapsed_ms=50
458
        ),
459
    )
460

461

462
def test_format_summary_local(rule_runner: PythonRuleRunner) -> None:
1✔
463
    _assert_test_summary(
1✔
464
        "✓ //:dummy_address succeeded in 0.05s.",
465
        exit_code=0,
466
        run_id=0,
467
        result_metadata=make_process_result_metadata(
468
            "ran", environment_name=None, total_elapsed_ms=50
469
        ),
470
    )
471

472

473
def test_format_summary_memoized(rule_runner: PythonRuleRunner) -> None:
1✔
474
    _assert_test_summary(
1✔
475
        "✓ //:dummy_address succeeded in 0.05s (memoized).",
476
        exit_code=0,
477
        run_id=1234,
478
        result_metadata=make_process_result_metadata("ran", total_elapsed_ms=50),
479
    )
480

481

482
def test_format_summary_memoized_remote(rule_runner: PythonRuleRunner) -> None:
1✔
483
    _assert_test_summary(
1✔
484
        "✓ //:dummy_address succeeded in 0.05s (memoized for remote environment `ubuntu`).",
485
        exit_code=0,
486
        run_id=1234,
487
        result_metadata=make_process_result_metadata(
488
            "ran", environment_name="ubuntu", remote_execution=True, total_elapsed_ms=50
489
        ),
490
    )
491

492

493
@pytest.mark.parametrize(
1✔
494
    ("results", "expected"),
495
    [
496
        pytest.param([], None, id="no_results"),
497
        pytest.param(
498
            [make_test_result([Address("", target_name="t1")], exit_code=0)], None, id="one_success"
499
        ),
500
        pytest.param(
501
            [make_test_result([Address("", target_name="t2")], exit_code=None)],
502
            None,
503
            id="one_no_run",
504
        ),
505
        pytest.param(
506
            [make_test_result([Address("", target_name="t3")], exit_code=1)],
507
            "To rerun the failing tests, use:\n\n    pants test //:t3",
508
            id="one_failure",
509
        ),
510
        pytest.param(
511
            [
512
                make_test_result([Address("", target_name="t1")], exit_code=0),
513
                make_test_result([Address("", target_name="t2")], exit_code=None),
514
                make_test_result([Address("", target_name="t3")], exit_code=1),
515
            ],
516
            "To rerun the failing tests, use:\n\n    pants test //:t3",
517
            id="one_of_each",
518
        ),
519
        pytest.param(
520
            [
521
                make_test_result([Address("path/to", target_name="t1")], exit_code=1),
522
                make_test_result([Address("another/path", target_name="t2")], exit_code=2),
523
                make_test_result([Address("", target_name="t3")], exit_code=3),
524
            ],
525
            "To rerun the failing tests, use:\n\n    pants test //:t3 another/path:t2 path/to:t1",
526
            id="multiple_failures",
527
        ),
528
        pytest.param(
529
            [
530
                make_test_result(
531
                    [
532
                        Address(
533
                            "path with spaces",
534
                            target_name="$*",
535
                            parameters=dict(key="value"),
536
                            generated_name="gn",
537
                        )
538
                    ],
539
                    exit_code=1,
540
                )
541
            ],
542
            "To rerun the failing tests, use:\n\n    pants test 'path with spaces:$*#gn@key=value'",
543
            id="special_characters_require_quoting",
544
        ),
545
    ],
546
)
547
def test_format_rerun_command(results: list[TestResult], expected: None | str) -> None:
1✔
548
    assert expected == _format_test_rerun_command(results)
1✔
549

550

551
def test_debug_target(rule_runner: PythonRuleRunner, monkeypatch: MonkeyPatch) -> None:
1✔
552
    def noop():
1✔
UNCOV
553
        pass
×
554

555
    monkeypatch.setattr("pants.engine.intrinsics.task_side_effected", noop)
1✔
556
    exit_code, _ = run_test_rule(
1✔
557
        rule_runner,
558
        request_type=SuccessfulRequest,
559
        targets=[make_target()],
560
        debug=True,
561
    )
UNCOV
562
    assert exit_code == 0
×
563

564

565
def test_report(rule_runner: PythonRuleRunner) -> None:
1✔
566
    addr1 = Address("", target_name="t1")
1✔
567
    addr2 = Address("", target_name="t2")
1✔
568
    exit_code, stderr = run_test_rule(
1✔
569
        rule_runner,
570
        request_type=SuccessfulRequest,
571
        targets=[make_target(addr1), make_target(addr2)],
572
        report=True,
573
    )
UNCOV
574
    assert exit_code == 0
×
UNCOV
575
    assert "Wrote test reports to dist/test/reports" in stderr
×
576

577

578
def test_report_dir(rule_runner: PythonRuleRunner) -> None:
1✔
579
    report_dir = "dist/test-results"
1✔
580
    addr1 = Address("", target_name="t1")
1✔
581
    addr2 = Address("", target_name="t2")
1✔
582
    exit_code, stderr = run_test_rule(
1✔
583
        rule_runner,
584
        request_type=SuccessfulRequest,
585
        targets=[make_target(addr1), make_target(addr2)],
586
        report=True,
587
        report_dir=report_dir,
588
    )
UNCOV
589
    assert exit_code == 0
×
UNCOV
590
    assert f"Wrote test reports to {report_dir}" in stderr
×
591

592

593
def test_coverage(rule_runner: PythonRuleRunner) -> None:
1✔
594
    addr1 = Address("", target_name="t1")
1✔
595
    addr2 = Address("", target_name="t2")
1✔
596
    exit_code, stderr = run_test_rule(
1✔
597
        rule_runner,
598
        request_type=SuccessfulRequest,
599
        targets=[make_target(addr1), make_target(addr2)],
600
        use_coverage=True,
601
    )
UNCOV
602
    assert exit_code == 0
×
UNCOV
603
    assert stderr.strip().endswith(f"Ran coverage on {addr1.spec}, {addr2.spec}")
×
604

605

606
def sort_results() -> None:
1✔
607
    def create_test_result(exit_code: int | None, addresses: Iterable[Address]) -> TestResult:
×
608
        return TestResult(
×
609
            exit_code=exit_code,
610
            addresses=tuple(addresses),
611
            stdout_bytes=b"",
612
            stdout_digest=EMPTY_FILE_DIGEST,
613
            stderr_bytes=b"",
614
            stderr_digest=EMPTY_FILE_DIGEST,
615
            output_setting=ShowOutput.ALL,
616
            result_metadata=None,
617
        )
618

619
    skip1 = create_test_result(
×
620
        exit_code=None,
621
        addresses=(Address("t1"),),
622
    )
623
    skip2 = create_test_result(
×
624
        exit_code=None,
625
        addresses=(Address("t2"),),
626
    )
627
    success1 = create_test_result(
×
628
        exit_code=0,
629
        addresses=(Address("t1"),),
630
    )
631
    success2 = create_test_result(
×
632
        exit_code=0,
633
        addresses=(Address("t2"),),
634
    )
635
    fail1 = create_test_result(
×
636
        exit_code=1,
637
        addresses=(Address("t1"),),
638
    )
639
    fail2 = create_test_result(
×
640
        exit_code=1,
641
        addresses=(Address("t2"),),
642
    )
643
    assert sorted([fail2, success2, skip2, fail1, success1, skip1]) == [
×
644
        skip1,
645
        skip2,
646
        success1,
647
        success2,
648
        fail1,
649
        fail2,
650
    ]
651

652

653
def assert_streaming_output(
1✔
654
    *,
655
    exit_code: int | None,
656
    stdout: str = "stdout",
657
    stderr: str = "stderr",
658
    output_setting: ShowOutput = ShowOutput.ALL,
659
    expected_level: LogLevel,
660
    expected_message: str,
661
    result_metadata: ProcessResultMetadata = make_process_result_metadata("dummy"),
662
) -> None:
663
    result = make_test_result(
1✔
664
        addresses=(Address("demo_test"),),
665
        exit_code=exit_code,
666
        stdout_bytes=stdout.encode(),
667
        stderr_bytes=stderr.encode(),
668
        output_setting=output_setting,
669
        result_metadata=result_metadata,
670
    )
671
    assert result.level() == expected_level
1✔
672
    assert result.message() == expected_message
1✔
673

674

675
def test_streaming_output_no_tests() -> None:
1✔
676
    assert_streaming_output(
1✔
677
        exit_code=None,
678
        stdout="",
679
        stderr="",
680
        expected_level=LogLevel.DEBUG,
681
        expected_message="no tests found.",
682
    )
683

684

685
def test_streaming_output_success() -> None:
1✔
686
    assert_success_streamed = partial(
1✔
687
        assert_streaming_output, exit_code=0, expected_level=LogLevel.INFO
688
    )
689
    assert_success_streamed(
1✔
690
        expected_message=dedent(
691
            """\
692
            succeeded.
693
            stdout
694
            stderr
695

696
            """
697
        ),
698
    )
699
    assert_success_streamed(output_setting=ShowOutput.FAILED, expected_message="succeeded.")
1✔
700
    assert_success_streamed(output_setting=ShowOutput.NONE, expected_message="succeeded.")
1✔
701

702

703
def test_streaming_output_failure() -> None:
1✔
704
    assert_failure_streamed = partial(
1✔
705
        assert_streaming_output, exit_code=1, expected_level=LogLevel.ERROR
706
    )
707
    message = dedent(
1✔
708
        """\
709
        failed (exit code 1).
710
        stdout
711
        stderr
712

713
        """
714
    )
715
    assert_failure_streamed(expected_message=message)
1✔
716
    assert_failure_streamed(output_setting=ShowOutput.FAILED, expected_message=message)
1✔
717
    assert_failure_streamed(
1✔
718
        output_setting=ShowOutput.NONE, expected_message="failed (exit code 1)."
719
    )
720

721

722
def test_runtime_package_dependencies() -> None:
1✔
723
    rule_runner = PythonRuleRunner(
1✔
724
        rules=[
725
            build_runtime_package_dependencies,
726
            *pex_from_targets.rules(),
727
            *package_pex_binary.rules(),
728
            *python_target_type_rules(),
729
            QueryRule(BuiltPackageDependencies, [BuildPackageDependenciesRequest]),
730
        ],
731
        target_types=[PythonSourcesGeneratorTarget, PexBinary],
732
    )
733
    rule_runner.set_options(args=[], env_inherit={"PATH", "PYENV_ROOT", "HOME"})
1✔
734

735
    rule_runner.write_files(
1✔
736
        {
737
            "src/py/main.py": "",
738
            "src/py/BUILD": dedent(
739
                """\
740
                python_sources()
741
                pex_binary(name='main', entry_point='main.py')
742
                """
743
            ),
744
        }
745
    )
746
    # Include an irrelevant target that cannot be built with `./pants package`.
747
    input_field = RuntimePackageDependenciesField(["src/py", "src/py:main"], Address("fake"))
1✔
748
    result = rule_runner.request(
1✔
749
        BuiltPackageDependencies, [BuildPackageDependenciesRequest(input_field)]
750
    )
751
    assert len(result) == 1
1✔
752
    built_package = result[0]
1✔
753
    snapshot = rule_runner.request(Snapshot, [built_package.digest])
1✔
754
    assert snapshot.files == ("src.py/main.pex",)
1✔
755

756

757
def test_timeout_calculation() -> None:
1✔
758
    def assert_timeout_calculated(
1✔
759
        *,
760
        field_value: int | None,
761
        expected: int | None,
762
        global_default: int | None = None,
763
        global_max: int | None = None,
764
        timeouts_enabled: bool = True,
765
    ) -> None:
766
        field = MockTestTimeoutField(field_value, Address("", target_name="tests"))
1✔
767
        test_subsystem = create_subsystem(
1✔
768
            TestSubsystem,
769
            timeouts=timeouts_enabled,
770
            timeout_default=global_default,
771
            timeout_maximum=global_max,
772
        )
773
        assert field.calculate_from_global_options(test_subsystem) == expected
1✔
774

775
    assert_timeout_calculated(field_value=10, expected=10)
1✔
776
    assert_timeout_calculated(field_value=20, global_max=10, expected=10)
1✔
777
    assert_timeout_calculated(field_value=None, global_default=20, expected=20)
1✔
778
    assert_timeout_calculated(field_value=None, expected=None)
1✔
779
    assert_timeout_calculated(field_value=None, global_default=20, global_max=10, expected=10)
1✔
780
    assert_timeout_calculated(field_value=10, timeouts_enabled=False, expected=None)
1✔
781

782

783
def test_non_utf8_output() -> None:
1✔
784
    test_result = make_test_result(
1✔
785
        [],
786
        exit_code=1,  # "test error" so stdout/stderr are output in message
787
        stdout_bytes=b"\x80\xbf",  # invalid UTF-8 as required by the test
788
        stderr_bytes=b"\x80\xbf",  # invalid UTF-8 as required by the test
789
        output_setting=ShowOutput.ALL,
790
    )
791
    assert test_result.message() == "failed (exit code 1).\n��\n��\n\n"
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

© 2026 Coveralls, Inc