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

pantsbuild / pants / 18812500213

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

Pull #22804

github

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

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

1314 existing lines in 64 files now uncovered.

77900 of 97030 relevant lines covered (80.28%)

3.35 hits per line

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

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

4
from __future__ import annotations
12✔
5

6
import fnmatch
12✔
7
import re
12✔
8
from collections.abc import Iterator, Sequence
12✔
9
from dataclasses import dataclass
12✔
10

11
from pants.util.frozendict import FrozenDict
12✔
12
from pants.util.ordered_set import FrozenOrderedSet
12✔
13

14
name_value_re = re.compile(r"([A-Za-z_]\w*)=(.*)")
12✔
15
shorthand_re = re.compile(r"([A-Za-z_]\w*)")
12✔
16

17
EXTRA_ENV_VARS_USAGE_HELP = """\
12✔
18
Entries are strings in the form `ENV_VAR=value` to use explicitly; or just
19
`ENV_VAR` to copy the value of a variable in Pants's own environment.
20
`fnmatch` globs like `ENV_VAR_PREFIXED_*` can be used to copy multiple environment variables.
21
"""
22

23

24
class CompleteEnvironmentVars(FrozenDict):
12✔
25
    """CompleteEnvironmentVars contains all environment variables from the current Pants process.
26

27
    NB: Consumers should almost always prefer to consume the `EnvironmentVars` type, which is
28
    filtered to a relevant subset of the environment.
29
    """
30

31
    def get_subset(
12✔
32
        self, requested: Sequence[str], *, allowed: Sequence[str] | None = None
33
    ) -> FrozenDict[str, str]:
34
        """Extract a subset of named env vars.
35

36
        Given a list of extra environment variable specifiers as strings, filter the contents of
37
        the Pants environment to only those variables.
38

39
        Each variable can be specified either as a name or as a name=value pair.
40
        In the former case, the value for that name is taken from this env. In the latter
41
        case the specified value overrides the value in this env.
42

43
        If `allowed` is specified, the requested variable names must be in that list, or an error
44
        will be raised.
45
        """
UNCOV
46
        allowed_set = None if allowed is None else set(allowed)
1✔
UNCOV
47
        env_var_subset: dict[str, str] = {}
1✔
48

UNCOV
49
        def check_and_set(name: str, value: str | None):
1✔
UNCOV
50
            if allowed_set is not None and name not in allowed_set:
1✔
51
                raise ValueError(
×
52
                    f"{name} is not in the list of variable names that are allowed to be set. "
53
                    f"Must be one of {','.join(sorted(allowed_set))}."
54
                )
UNCOV
55
            if value is not None:
1✔
UNCOV
56
                env_var_subset[name] = value
1✔
57

UNCOV
58
        for env_var in requested:
1✔
UNCOV
59
            name_value_match = name_value_re.match(env_var)
1✔
UNCOV
60
            if name_value_match:
1✔
UNCOV
61
                check_and_set(name_value_match[1], name_value_match[2])
1✔
UNCOV
62
            elif shorthand_re.match(env_var):
1✔
UNCOV
63
                for name, value in self.get_or_match(env_var):
1✔
UNCOV
64
                    check_and_set(name, value)
1✔
65
            else:
UNCOV
66
                raise ValueError(
1✔
67
                    f"An invalid variable was requested via the --test-extra-env-var "
68
                    f"mechanism: {env_var}"
69
                )
70

UNCOV
71
        return FrozenDict(env_var_subset)
1✔
72

73
    def get_or_match(self, name_or_pattern: str) -> Iterator[tuple[str, str]]:
12✔
74
        """Get the value of an envvar if it has an exact match, otherwise all fnmatches.
75

76
        Although fnmatch could also handle direct matches, it is significantly slower (roughly 2000
77
        times).
78
        """
UNCOV
79
        if value := self.get(name_or_pattern):
1✔
UNCOV
80
            yield name_or_pattern, value
1✔
UNCOV
81
            return  # do not check fnmatches if we have an exact match
1✔
82

83
        # fnmatch.filter looks tempting,
84
        # but we'd need to iterate once for the filtering the keys and again for getting the values
UNCOV
85
        for k, v in self.items():
1✔
86
            # we use fnmatchcase to avoid normalising the case with `os.path.normcase` on Windows systems
UNCOV
87
            if fnmatch.fnmatchcase(k, name_or_pattern):
1✔
UNCOV
88
                yield k, v
1✔
89

90

91
@dataclass(frozen=True)
12✔
92
class EnvironmentVarsRequest:
12✔
93
    """Requests a subset of the variables set in the environment.
94

95
    Requesting only the relevant subset of the environment reduces invalidation caused by unrelated
96
    changes.
97
    """
98

99
    requested: FrozenOrderedSet[str]
12✔
100
    allowed: FrozenOrderedSet[str] | None
12✔
101

102
    def __init__(self, requested: Sequence[str], allowed: Sequence[str] | None = None):
12✔
103
        object.__setattr__(self, "requested", FrozenOrderedSet(requested))
3✔
104
        object.__setattr__(self, "allowed", None if allowed is None else FrozenOrderedSet(allowed))
3✔
105

106

107
class EnvironmentVars(FrozenDict[str, str]):
12✔
108
    """A subset of the variables set in the environment.
109

110
    Accesses to `os.environ` cannot be accurately tracked, so @rules that need access to the
111
    environment should use APIs from this module instead.
112

113
    Wherever possible, the `EnvironmentVars` type should be consumed rather than the
114
    `CompleteEnvironmentVars`, as it represents a filtered/relevant subset of the environment, rather
115
    than the entire unfiltered environment.
116
    """
117

118

119
class PathEnvironmentVariable(FrozenOrderedSet):
12✔
120
    """The PATH environment variable entries, split on `os.pathsep`."""
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