Skip to content

Commit

Permalink
Get rid of remaining uses of init_subsystem.
Browse files Browse the repository at this point in the history
[ci skip-rust-tests]
  • Loading branch information
benjyw committed Jul 22, 2020
1 parent e4b6476 commit 84e0b11
Show file tree
Hide file tree
Showing 13 changed files with 68 additions and 200 deletions.
44 changes: 19 additions & 25 deletions src/python/pants/backend/python/rules/pex_test.py
Expand Up @@ -31,45 +31,41 @@
from pants.testutil.engine.util import create_subsystem
from pants.testutil.external_tool_test_base import ExternalToolTestBase
from pants.testutil.option.util import create_options_bootstrapper
from pants.testutil.subsystem.util import init_subsystem
from pants.util.frozendict import FrozenDict
from pants.util.strutil import create_path_env_var


def test_merge_interpreter_constraints() -> None:
def assert_merged(*, input: List[List[str]], expected: List[str]) -> None:
assert PexInterpreterConstraints.merge_constraint_sets(input) == expected
def assert_merged(*, inp: List[List[str]], expected: List[str]) -> None:
assert PexInterpreterConstraints.merge_constraint_sets(inp) == expected

# Multiple constraint sets get merged so that they are ANDed.
# A & B => A & B
assert_merged(
input=[["CPython==2.7.*"], ["CPython==3.6.*"]], expected=["CPython==2.7.*,==3.6.*"]
)
assert_merged(inp=[["CPython==2.7.*"], ["CPython==3.6.*"]], expected=["CPython==2.7.*,==3.6.*"])

# Multiple constraints within a single constraint set are kept separate so that they are ORed.
# A | B => A | B
assert_merged(
input=[["CPython==2.7.*", "CPython==3.6.*"]], expected=["CPython==2.7.*", "CPython==3.6.*"]
inp=[["CPython==2.7.*", "CPython==3.6.*"]], expected=["CPython==2.7.*", "CPython==3.6.*"]
)

# Input constraints already were ANDed.
# A => A
assert_merged(input=[["CPython>=2.7,<3"]], expected=["CPython>=2.7,<3"])
assert_merged(inp=[["CPython>=2.7,<3"]], expected=["CPython>=2.7,<3"])

# Both AND and OR.
# (A | B) & C => (A & B) | (B & C)
assert_merged(
input=[["CPython>=2.7,<3", "CPython>=3.5"], ["CPython==3.6.*"]],
inp=[["CPython>=2.7,<3", "CPython>=3.5"], ["CPython==3.6.*"]],
expected=["CPython>=2.7,<3,==3.6.*", "CPython>=3.5,==3.6.*"],
)
# A & B & (C | D) => (A & B & C) | (A & B & D)
assert_merged(
input=[["CPython==2.7.*"], ["CPython==3.6.*"], ["CPython==3.7.*", "CPython==3.8.*"]],
inp=[["CPython==2.7.*"], ["CPython==3.6.*"], ["CPython==3.7.*", "CPython==3.8.*"]],
expected=["CPython==2.7.*,==3.6.*,==3.7.*", "CPython==2.7.*,==3.6.*,==3.8.*"],
)
# (A | B) & (C | D) => (A & C) | (A & D) | (B & C) | (B & D)
assert_merged(
input=[["CPython>=2.7,<3", "CPython>=3.5"], ["CPython==3.6.*", "CPython==3.7.*"]],
inp=[["CPython>=2.7,<3", "CPython>=3.5"], ["CPython==3.6.*", "CPython==3.7.*"]],
expected=[
"CPython>=2.7,<3,==3.6.*",
"CPython>=2.7,<3,==3.7.*",
Expand All @@ -80,7 +76,7 @@ def assert_merged(*, input: List[List[str]], expected: List[str]) -> None:
# A & (B | C | D) & (E | F) & G =>
# (A & B & E & G) | (A & B & F & G) | (A & C & E & G) | (A & C & F & G) | (A & D & E & G) | (A & D & F & G)
assert_merged(
input=[
inp=[
["CPython==3.6.5"],
["CPython==2.7.14", "CPython==2.7.15", "CPython==2.7.16"],
["CPython>=3.6", "CPython==3.5.10"],
Expand All @@ -102,13 +98,13 @@ def assert_merged(*, input: List[List[str]], expected: List[str]) -> None:
# But, we first deduplicate each constraint_set. (A | B) & (A | B) can be rewritten as
# X & X => X.
assert_merged(
input=[["CPython==2.7.*", "CPython==3.6.*"], ["CPython==2.7.*", "CPython==3.6.*"]],
inp=[["CPython==2.7.*", "CPython==3.6.*"], ["CPython==2.7.*", "CPython==3.6.*"]],
expected=["CPython==2.7.*", "CPython==3.6.*"],
)
# (A | B) & C & (A | B) => (A & C) | (B & C). Alternatively, this can be rewritten as
# X & Y & X => X & Y.
assert_merged(
input=[
inp=[
["CPython>=2.7,<3", "CPython>=3.5"],
["CPython==3.6.*"],
["CPython>=3.5", "CPython>=2.7,<3"],
Expand All @@ -117,21 +113,19 @@ def assert_merged(*, input: List[List[str]], expected: List[str]) -> None:
)

# No specifiers
assert_merged(input=[["CPython"]], expected=["CPython"])
assert_merged(input=[["CPython"], ["CPython==3.7.*"]], expected=["CPython==3.7.*"])
assert_merged(inp=[["CPython"]], expected=["CPython"])
assert_merged(inp=[["CPython"], ["CPython==3.7.*"]], expected=["CPython==3.7.*"])

# No interpreter is shorthand for CPython, which is how Pex behaves
assert_merged(input=[[">=3.5"], ["CPython==3.7.*"]], expected=["CPython>=3.5,==3.7.*"])
assert_merged(inp=[[">=3.5"], ["CPython==3.7.*"]], expected=["CPython>=3.5,==3.7.*"])

# Different Python interpreters, which are guaranteed to fail when ANDed but are safe when ORed.
with pytest.raises(ValueError):
PexInterpreterConstraints.merge_constraint_sets([["CPython==3.7.*"], ["PyPy==43.0"]])
assert_merged(
input=[["CPython==3.7.*", "PyPy==43.0"]], expected=["CPython==3.7.*", "PyPy==43.0"]
)
assert_merged(inp=[["CPython==3.7.*", "PyPy==43.0"]], expected=["CPython==3.7.*", "PyPy==43.0"])

# Ensure we can handle empty input.
assert_merged(input=[], expected=[])
assert_merged(inp=[], expected=[])


@dataclass(frozen=True)
Expand Down Expand Up @@ -285,9 +279,9 @@ def test_pex_execution(self) -> None:
assert "main.py" in pex_files
assert "subdir/sub.py" in pex_files

init_subsystem(PythonSetup)
python_setup = PythonSetup.global_instance()
env = {"PATH": create_path_env_var(python_setup.interpreter_search_paths)}
# We reasonably expect there to be a python interpreter on the test-running
# process's path.
env = {"PATH": os.getenv("PATH", "")}

process = Process(
argv=("python", "test.pex"),
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/backend/python/rules/run_setup_py.py
Expand Up @@ -314,11 +314,10 @@ async def run_setup_pys(
exported_targets = list(FrozenOrderedSet(owners))

py2 = is_python2(
(
python_setup.compatibilities_or_constraints(
target_with_origin.target.get(PythonInterpreterCompatibility).value
for target_with_origin in targets_with_origins
),
python_setup,
)
)
chroots = await MultiGet(
Get(SetupPyChroot, SetupPyChrootRequest(exported_target, py2))
Expand Down
14 changes: 3 additions & 11 deletions src/python/pants/backend/python/rules/run_setup_py_test.py
Expand Up @@ -45,7 +45,6 @@
from pants.python.python_requirement import PythonRequirement
from pants.source.source_root import SourceRootConfig
from pants.testutil.option.util import create_options_bootstrapper
from pants.testutil.subsystem.util import init_subsystem
from pants.testutil.test_base import TestBase

_namespace_decl = "__import__('pkg_resources').declare_namespace(__name__)"
Expand All @@ -66,10 +65,6 @@ def tgt(self, addr: str) -> Target:
return self.request_single_product(WrappedTarget, Params(Address.parse(addr))).target


def init_source_root():
init_subsystem(SourceRootConfig, options={"source": {"root_patterns": ["src/python"]}})


class TestGenerateChroot(TestSetupPyBase):
@classmethod
def rules(cls):
Expand Down Expand Up @@ -112,7 +107,6 @@ def assert_error(self, addr: str, exc_cls: Type[Exception]):
assert type(ex.wrapped_exceptions[0]) == exc_cls

def test_generate_chroot(self) -> None:
init_source_root()
self.create_file(
"src/python/foo/bar/baz/BUILD",
"python_library(provides=setup_py(name='baz', version='1.1.1'))",
Expand Down Expand Up @@ -176,7 +170,6 @@ def test_generate_chroot(self) -> None:
)

def test_invalid_binary(self) -> None:
init_source_root()
self.create_file(
"src/python/invalid_binary/BUILD",
textwrap.dedent(
Expand Down Expand Up @@ -229,7 +222,7 @@ def assert_sources(
SetupPySources,
Params(
SetupPySourcesRequest(Targets([self.tgt(addr) for addr in addrs]), py2=False),
SourceRootConfig.global_instance(),
create_options_bootstrapper(args=["--source-root-patterns=src/python"]),
),
)
chroot_snapshot = self.request_single_product(Snapshot, Params(srcs.digest))
Expand All @@ -240,7 +233,6 @@ def assert_sources(
assert expected_package_data == dict(srcs.package_data)

def test_get_sources(self) -> None:
init_source_root()
self.create_file(
"src/python/foo/bar/baz/BUILD",
textwrap.dedent(
Expand Down Expand Up @@ -411,7 +403,8 @@ def assert_ancestor_init_py(
ancestor_init_py_files = self.request_single_product(
AncestorInitPyFiles,
Params(
Targets([self.tgt(addr) for addr in addrs]), SourceRootConfig.global_instance(),
Targets([self.tgt(addr) for addr in addrs]),
create_options_bootstrapper(args=["--source-root-patterns=src/python"]),
),
)
snapshots = [
Expand All @@ -423,7 +416,6 @@ def assert_ancestor_init_py(
assert sorted(expected_init_pys) == sorted(init_py_files_found)

def test_get_ancestor_init_py(self) -> None:
init_source_root()
# NB: src/python/foo/bar/baz/qux/__init__.py is a target's source.
self.create_file("src/python/foo/bar/baz/qux/BUILD", "python_library()")
self.create_file("src/python/foo/bar/baz/qux/qux.py", "")
Expand Down
12 changes: 4 additions & 8 deletions src/python/pants/backend/python/rules/util.py
Expand Up @@ -4,7 +4,7 @@
import io
import os
from collections import abc, defaultdict
from typing import Dict, Iterable, List, Optional, Set, Tuple, cast
from typing import Dict, Iterable, List, Set, Tuple, cast

from pkg_resources import Requirement

Expand All @@ -13,7 +13,6 @@
from pants.core.util_rules.strip_source_roots import SourceRootStrippedSources
from pants.engine.fs import DigestContents
from pants.engine.target import Target
from pants.python.python_setup import PythonSetup
from pants.source.source_root import SourceRootError
from pants.util.strutil import ensure_text

Expand Down Expand Up @@ -216,15 +215,12 @@ def has_args(call_node: ast.Call, required_arg_ids: Tuple[str, ...]) -> bool:
return False


def is_python2(
compatibilities: Iterable[Optional[Iterable[str]]], python_setup: PythonSetup
) -> bool:
def is_python2(compatibilities_or_constraints: Iterable[str]) -> bool:
"""Checks if we should assume python2 code."""

def iter_reqs():
for compatibility in compatibilities:
for constraint in python_setup.compatibility_or_constraints(compatibility):
yield parse_interpreter_constraint(constraint)
for constraint in compatibilities_or_constraints:
yield parse_interpreter_constraint(constraint)

for req in iter_reqs():
for python_27_ver in range(0, 18): # The last python 2.7 version was 2.7.18.
Expand Down
58 changes: 17 additions & 41 deletions src/python/pants/backend/python/rules/util_test.py
Expand Up @@ -8,10 +8,6 @@
distutils_repr,
is_python2,
)
from pants.option.ranked_value import Rank, RankedValue
from pants.python.python_setup import PythonSetup
from pants.subsystem.subsystem import Subsystem
from pants.testutil.subsystem.util import init_subsystem

testdata = {
"foo": "bar",
Expand Down Expand Up @@ -80,49 +76,29 @@ def test_does_not_declare_pkg_resources_namespace_package(python_src: str) -> No


@pytest.mark.parametrize(
["constraints", "compatibilities"],
"constraints",
[
([], [["CPython>=2.7,<3"]]),
(["CPython>=2.7,<3"], [None]),
(["CPython>=2.7,<3"], [["CPython>=2.7,<3"], ["CPython>=3.6"]]),
(["CPython>=2.7.13"], [None]),
(["CPython>=2.7.13,<2.7.16"], [None]),
(["CPython>=2.7.13,!=2.7.16"], [None]),
(["PyPy>=2.7,<3"], [None]),
["CPython>=2.7,<3"],
["CPython>=2.7,<3", "CPython>=3.6"],
["CPython>=2.7.13"],
["CPython>=2.7.13,<2.7.16"],
["CPython>=2.7.13,!=2.7.16"],
["PyPy>=2.7,<3"],
],
)
def test_is_python2(constraints, compatibilities):
Subsystem.reset()
init_subsystem(
PythonSetup,
{
PythonSetup.options_scope: {
"interpreter_constraints": RankedValue(Rank.CONFIG, constraints)
}
},
)
assert is_python2(compatibilities, PythonSetup.global_instance())
def test_is_python2(constraints):
assert is_python2(constraints)


@pytest.mark.parametrize(
["constraints", "compatibilities"],
"constraints",
[
([], [["CPython>=3.6"]]),
(["CPython>=3.6"], [None]),
(["CPython>=3.7"], [["CPython>=3.6"]]),
(["CPython>=3.7"], [["CPython>=3.6"], ["CPython>=3.8"]]),
(["CPython!=2.7.*"], [None]),
(["PyPy>=3.6"], [None]),
["CPython>=3.6"],
["CPython>=3.7"],
["CPython>=3.6", "CPython>=3.8"],
["CPython!=2.7.*"],
["PyPy>=3.6"],
],
)
def test_is_not_python2(constraints, compatibilities):
Subsystem.reset()
init_subsystem(
PythonSetup,
{
PythonSetup.options_scope: {
"interpreter_constraints": RankedValue(Rank.CONFIG, constraints)
}
},
)
assert not is_python2(compatibilities, PythonSetup.global_instance())
def test_is_not_python2(constraints):
assert not is_python2(constraints)
25 changes: 13 additions & 12 deletions src/python/pants/backend/python/target_types_test.py
Expand Up @@ -8,12 +8,17 @@
from pants.backend.python.subsystems.pytest import PyTest
from pants.backend.python.target_types import PythonBinarySources, PythonTestsTimeout
from pants.engine.addresses import Address
from pants.engine.rules import SubsystemRule
from pants.engine.target import InvalidFieldException
from pants.testutil.subsystem.util import global_subsystem_instance
from pants.testutil.option.util import create_options_bootstrapper
from pants.testutil.test_base import TestBase


class TestTimeout(TestBase):
@classmethod
def rules(cls):
return [*super().rules(), SubsystemRule(PyTest)]

def test_timeout_validation(self) -> None:
with pytest.raises(InvalidFieldException):
PythonTestsTimeout(-100, address=Address.parse(":tests"))
Expand All @@ -28,18 +33,14 @@ def assert_timeout_calculated(
expected: Optional[int],
global_default: Optional[int] = None,
global_max: Optional[int] = None,
timeouts_enabled: bool = True
timeouts_enabled: bool = True,
) -> None:
pytest = global_subsystem_instance(
PyTest,
options={
PyTest.options_scope: {
"timeouts": timeouts_enabled,
"timeout_default": global_default,
"timeout_maximum": global_max,
}
},
)
args = ["--backend-packages=pants.backend.python", f"--pytest-timeouts={timeouts_enabled}"]
if global_default is not None:
args.append(f"--pytest-timeout-default={global_default}")
if global_max is not None:
args.append(f"--pytest-timeout-maximum={global_max}")
pytest = self.request_single_product(PyTest, create_options_bootstrapper(args=args))
field = PythonTestsTimeout(field_value, address=Address.parse(":tests"))
assert field.calculate_from_global_options(pytest) == expected

Expand Down
9 changes: 9 additions & 0 deletions src/python/pants/python/python_setup.py
Expand Up @@ -195,6 +195,15 @@ def compatibility_or_constraints(
return self.interpreter_constraints
return tuple(compatibility or self.interpreter_constraints)

def compatibilities_or_constraints(
self, compatibilities: Iterable[Optional[Iterable[str]]]
) -> Tuple[str, ...]:
return tuple(
constraint
for compatibility in compatibilities
for constraint in self.compatibility_or_constraints(compatibility)
)

@classmethod
def expand_interpreter_search_paths(cls, interpreter_search_paths, pyenv_root_func=None):
special_strings = {
Expand Down
5 changes: 1 addition & 4 deletions src/python/pants/subsystem/subsystem.py
Expand Up @@ -47,10 +47,7 @@ class Subsystem(SubsystemClientMixin, Optionable):

class UninitializedSubsystemError(SubsystemError):
def __init__(self, class_name, scope):
super().__init__(
f'Subsystem "{class_name}" not initialized for scope "{scope}". Is subsystem missing '
"from subsystem_dependencies() in a task? "
)
super().__init__(f'Subsystem "{class_name}" not initialized for scope "{scope}"')

@classmethod
def is_subsystem_type(cls, obj):
Expand Down
1 change: 0 additions & 1 deletion src/python/pants/testutil/BUILD
Expand Up @@ -9,7 +9,6 @@ python_library(
':int-test-for-export',
'src/python/pants/testutil/engine',
'src/python/pants/testutil/option',
'src/python/pants/testutil/subsystem',
],
provides=pants_setup_py(
name='pantsbuild.pants.testutil',
Expand Down

0 comments on commit 84e0b11

Please sign in to comment.