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

pantsbuild / pants / 20632486505

01 Jan 2026 04:21AM UTC coverage: 43.231% (-37.1%) from 80.281%
20632486505

Pull #22962

github

web-flow
Merge 08d5c63b0 into f52ab6675
Pull Request #22962: Bump the gha-deps group across 1 directory with 6 updates

26122 of 60424 relevant lines covered (43.23%)

0.86 hits per line

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

27.78
/src/python/pants/backend/go/conftest.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
import os
2✔
5
import subprocess
2✔
6
from functools import lru_cache
2✔
7

8
import pytest
2✔
9

10

11
@lru_cache(None)
2✔
12
def _go_present() -> bool:
2✔
13
    try:
2✔
14
        subprocess.run(
2✔
15
            ["go", "version"], check=False, env={"PATH": os.getenv("PATH") or ""}
16
        ).returncode
17
    except (subprocess.CalledProcessError, FileNotFoundError):
×
18
        return False
×
19
    return True
2✔
20

21

22
def _parse_go_version(version_string: str) -> tuple[int, int] | None:
2✔
23
    """Parse 'go1.24.3' output to (1, 24)."""
24
    if not version_string.startswith("go"):
×
25
        return None
×
26

27
    # Strip "go" prefix and split on "."
28
    version_parts = version_string[2:].split(".")
×
29

30
    if len(version_parts) < 2:
×
31
        return None
×
32

33
    try:
×
34
        major = int(version_parts[0])
×
35
        minor = int(version_parts[1])
×
36
        return (major, minor)
×
37
    except (ValueError, IndexError):
×
38
        return None
×
39

40

41
@lru_cache(None)
2✔
42
def _discover_go_binaries() -> list[tuple[str, tuple[int, int]]]:
2✔
43
    """Discover all Go binaries in PATH and return sorted by version (newest first)."""
44
    path_env = os.getenv("PATH", "")
×
45
    if not path_env:
×
46
        return []
×
47

48
    go_binaries = []
×
49
    seen_paths = set()
×
50

51
    for path_dir in path_env.split(os.pathsep):
×
52
        if not path_dir or not os.path.isdir(path_dir):
×
53
            continue
×
54

55
        go_binary = os.path.join(path_dir, "go")
×
56
        if os.path.isfile(go_binary) and os.access(go_binary, os.X_OK):
×
57
            # Resolve symlinks to avoid duplicates.
58
            real_path = os.path.realpath(go_binary)
×
59
            if real_path in seen_paths:
×
60
                continue
×
61
            seen_paths.add(real_path)
×
62

63
            try:
×
64
                result = subprocess.run(
×
65
                    [go_binary, "env", "GOVERSION"],
66
                    capture_output=True,
67
                    text=True,
68
                    timeout=5,
69
                    check=False,
70
                )
71
                if result.returncode == 0:
×
72
                    version = _parse_go_version(result.stdout.strip())
×
73
                    if version:
×
74
                        go_binaries.append((go_binary, version))
×
75
            except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
×
76
                continue
×
77

78
    # Sort by version (newest first).
79
    go_binaries.sort(key=lambda x: x[1], reverse=True)
×
80
    return go_binaries
×
81

82

83
def pytest_generate_tests(metafunc):
2✔
84
    """Parametrize tests that require specific Go versions."""
85
    # Check if the test has the require_go_version_max marker.
86
    marker = metafunc.definition.get_closest_marker("require_go_version_max")
2✔
87
    if not marker:
2✔
88
        return
2✔
89

90
    # Extract the maximum version from the marker.
91
    if len(marker.args) < 2:
×
92
        pytest.fail(
×
93
            f"require_go_version_max marker requires 2 args (major, minor), got {marker.args}"
94
        )
95
        return
×
96

97
    max_major, max_minor = marker.args[0], marker.args[1]
×
98
    max_version = (max_major, max_minor)
×
99

100
    # Discover available Go binaries
101
    available_go = _discover_go_binaries()
×
102

103
    # Filter for compatible versions (<= max_version)
104
    compatible_go = [(path, version) for path, version in available_go if version <= max_version]
×
105

106
    # Parametrize the test if it accepts a `go_binary_path` fixture.
107
    if "go_binary_path" in metafunc.fixturenames:
×
108
        if compatible_go:
×
109
            # Use the newest compatible version.
110
            best_go_path, best_version = compatible_go[0]
×
111
            metafunc.parametrize(
×
112
                "go_binary_path",
113
                [best_go_path],
114
                ids=[f"go{best_version[0]}.{best_version[1]}"],
115
            )
116
        else:
117
            # Parametrize with None and mark to skip, so the test shows as skipped rather than uncollected.
118
            available_versions = [f"{v[0]}.{v[1]}" for _, v in available_go]
×
119
            skip_msg = (
×
120
                f"Test requires Go <= {max_major}.{max_minor}, but only found: "
121
                f"{', '.join(available_versions) if available_versions else 'none'}"
122
            )
123
            metafunc.parametrize(
×
124
                "go_binary_path",
125
                [pytest.param(None, marks=pytest.mark.skip(reason=skip_msg))],
126
                ids=["no-compatible-go"],
127
            )
128

129

130
def pytest_configure(config):
2✔
131
    """Register custom markers."""
132
    config.addinivalue_line(
2✔
133
        "markers",
134
        "require_go_version_max(major, minor): mark test to require Go version <= specified",
135
    )
136

137

138
def pytest_runtest_setup(item: pytest.Item) -> None:
2✔
139
    if not _go_present():
2✔
140
        pytest.skip(reason="`go` not present on PATH")
×
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