summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2021-01-04 09:06:49 +0000
committerBernát Gábor <bgabor8@bloomberg.net>2021-01-04 09:17:55 +0000
commit49e796ab200e2c259f5bebeb426d06e7595d5cd7 (patch)
tree2a9e42b704cb1adb6ea2599cfc92a8e28d28343b /src
parent4073c136d53029a00c4727975bf6c3755cb8a58c (diff)
downloadtox-git-49e796ab200e2c259f5bebeb426d06e7595d5cd7.tar.gz
Add tests for the package dependency marker filters
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
Diffstat (limited to 'src')
-rw-r--r--src/tox/tox_env/package.py4
-rw-r--r--src/tox/tox_env/python/virtual_env/package/api.py58
-rw-r--r--src/tox/util/pep517/via_fresh_subprocess.py68
3 files changed, 102 insertions, 28 deletions
diff --git a/src/tox/tox_env/package.py b/src/tox/tox_env/package.py
index 86e822e6..a282e24f 100644
--- a/src/tox/tox_env/package.py
+++ b/src/tox/tox_env/package.py
@@ -4,7 +4,7 @@ A tox environment that can build packages.
from abc import ABC, abstractmethod
from argparse import ArgumentParser
from pathlib import Path
-from typing import TYPE_CHECKING, List, Optional, Set
+from typing import TYPE_CHECKING, List, Set
from packaging.requirements import Requirement
@@ -30,7 +30,7 @@ class PackageToxEnv(ToxEnv, ABC):
self.ref_count = AtomicCounter()
@abstractmethod
- def get_package_dependencies(self, extras: Optional[Set[str]] = None) -> List[Requirement]:
+ def get_package_dependencies(self, extras: Set[str]) -> List[Requirement]:
raise NotImplementedError
@abstractmethod
diff --git a/src/tox/tox_env/python/virtual_env/package/api.py b/src/tox/tox_env/python/virtual_env/package/api.py
index 377dd176..03ea06e8 100644
--- a/src/tox/tox_env/python/virtual_env/package/api.py
+++ b/src/tox/tox_env/python/virtual_env/package/api.py
@@ -136,40 +136,46 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend, ABC):
self._package = self._build_artifact()
return [self._package]
- def get_package_dependencies(self, extras: Optional[Set[str]] = None) -> List[Requirement]:
+ def get_package_dependencies(self, extras: Set[str]) -> List[Requirement]:
with self._lock:
- if self._package_dependencies is None:
- self._package_dependencies = self._load_package_dependencies(extras)
+ if self._package_dependencies is None: # pragma: no branch
+ self._ensure_meta_present()
+ self._package_dependencies = self.discover_package_dependencies(self._distribution_meta, extras)
return self._package_dependencies
- def _load_package_dependencies(self, extras: Optional[Set[str]]) -> List[Requirement]:
- self._ensure_meta_present()
- if extras is None:
- extras = set()
+ @staticmethod
+ def discover_package_dependencies( # type: ignore[no-any-unimported]
+ meta: PathDistribution, extras: Set[str]
+ ) -> List[Requirement]:
result: List[Requirement] = []
- if self._distribution_meta is None:
- raise RuntimeError
- requires = self._distribution_meta.requires or []
- for v in requires:
- req = Requirement(v)
+ requires = meta.requires or []
+ for req_str in requires:
+ req = Requirement(req_str)
markers: List[Union[str, Tuple[Variable, Variable, Variable]]] = getattr(req.marker, "_markers", []) or []
- extra: Optional[str] = None
+
+ # find the extra marker (if has)
_at: Optional[int] = None
- for _at, (m_key, op, m_val) in (
- (j, i) for j, i in enumerate(markers) if isinstance(i, tuple) and len(i) == 3
+ extra: Optional[str] = None
+ for _at, (marker_key, op, marker_value) in (
+ (_at_marker, marker)
+ for _at_marker, marker in enumerate(markers)
+ if isinstance(marker, tuple) and len(marker) == 3
):
- if m_key.value == "extra" and op.value == "==":
- extra = m_val.value
+ if marker_key.value == "extra" and op.value == "==": # pragma: no branch
+ extra = marker_value.value
break
- if extra is None or extra in extras:
- if _at is not None:
+ # continue only if this extra should be included
+ if not (extra is None or extra in extras):
+ continue
+ # delete the extra marker if present
+ if _at is not None:
+ del markers[_at]
+ _at -= 1
+ if _at > 0 and (isinstance(markers[_at], str) and markers[_at] in ("and", "or")):
del markers[_at]
- _at -= 1
- if _at > 0 and (isinstance(markers[_at], str) and markers[_at] in ("and", "or")):
- del markers[_at]
- if len(markers) == 0:
- req.marker = None
- result.append(req)
+ if len(markers) == 0:
+ req.marker = None
+ result.append(req)
return result
@property
@@ -211,7 +217,7 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend, ABC):
self._backend_executor.close()
@contextmanager
- def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[CmdStatus]:
+ def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[ToxCmdStatus]: # type: ignore[override]
with self.execute_async(
cmd=self.backend_cmd,
cwd=self._root,
diff --git a/src/tox/util/pep517/via_fresh_subprocess.py b/src/tox/util/pep517/via_fresh_subprocess.py
new file mode 100644
index 00000000..1f83a557
--- /dev/null
+++ b/src/tox/util/pep517/via_fresh_subprocess.py
@@ -0,0 +1,68 @@
+import os
+import sys
+from contextlib import contextmanager
+from pathlib import Path
+from subprocess import PIPE, Popen
+from threading import Thread
+from typing import IO, Any, Iterator, Optional, Tuple, cast
+
+from packaging.requirements import Requirement
+
+from .frontend import CmdStatus, Frontend
+
+
+class SubprocessCmdStatus(CmdStatus, Thread):
+ def __init__(self, process: "Popen[str]") -> None:
+ super().__init__()
+ self.process = process
+ self._out_err: Optional[Tuple[str, str]] = None
+ self.start()
+
+ def run(self) -> None:
+ self._out_err = self.process.communicate()
+
+ @property
+ def done(self) -> bool:
+ return self.process.returncode is not None
+
+ def out_err(self) -> Tuple[str, str]:
+ return cast(Tuple[str, str], self._out_err)
+
+
+class SubprocessFrontend(Frontend):
+ def __init__(
+ self,
+ root: Path,
+ backend_paths: Tuple[Path, ...],
+ backend_module: str,
+ backend_obj: Optional[str],
+ requires: Tuple[Requirement, ...],
+ ):
+ super().__init__(root, backend_paths, backend_module, backend_obj, requires, reuse_backend=False)
+
+ @contextmanager
+ def _send_msg( # type: ignore[override]
+ self, cmd: str, result_file: Path, msg: str
+ ) -> Iterator[SubprocessCmdStatus]:
+ env = os.environ.copy()
+ env["PYTHONPATH"] = os.pathsep.join(str(i) for i in self._backend_paths)
+ process = Popen(
+ args=[sys.executable] + self.backend_args,
+ stdout=PIPE,
+ stderr=PIPE,
+ stdin=PIPE,
+ universal_newlines=True,
+ cwd=self._root,
+ env=env,
+ )
+ cast(IO[str], process.stdin).write(f"{os.linesep}{msg}{os.linesep}")
+ yield SubprocessCmdStatus(process)
+
+ def send_cmd(self, cmd: str, **kwargs: Any) -> Tuple[Any, str, str]:
+ return self._send(cmd, **kwargs)
+
+
+__all__ = (
+ "SubprocessCmdStatus",
+ "SubprocessFrontend",
+)