Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tox4 #163

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open

Tox4 #163

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f66a161
Initial commit with base functionality.
Feb 13, 2023
bb87ba7
Merge find_conda functions.
Feb 20, 2023
0fc5068
Rearrange CondaEnvRunner.
Feb 20, 2023
48c40ca
Implement conda_env.
Feb 20, 2023
c792980
Handle conda_env.
Feb 20, 2023
838d5e1
Remove unused code.
Feb 21, 2023
7a0c51e
Installation.
Feb 21, 2023
93c897a
Reformat.
Feb 21, 2023
7504726
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 21, 2023
dfb59dc
Inherit from PythonRun and add type info.
Apr 24, 2023
4aed14c
Reformat plugin with isort and black.
Apr 24, 2023
6afdb36
WIP.
May 30, 2023
1330cad
Fixes.
May 31, 2023
26fe70f
Plugin fixes.
May 31, 2023
b51c6a7
Use executor instead of subprocess.
May 31, 2023
52a8bfb
Test infrastructure wip.
Jun 1, 2023
503e190
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 1, 2023
b2c68cd
Fix test passing.
Jun 1, 2023
b2b45c3
2nd test ready.
Jun 1, 2023
580330a
4th test.
Jun 1, 2023
b5104ee
Format.
Jun 1, 2023
e77fd71
conda_spec test added.
Jun 2, 2023
fcff0cc
Add conda-env test and fixes.
Jun 2, 2023
7bdd51e
Test for env. and spec.
Jun 2, 2023
311a14e
Add conda env. tests are ready.
Jun 2, 2023
af770cf
Add conda_name test.
Jun 2, 2023
10868ef
Format.
Jun 2, 2023
3b7b57e
Add cache tests.
Jun 2, 2023
8e863c4
Add more tests and fixes.
Jun 2, 2023
b27855d
Format.
Jun 2, 2023
9f761eb
Add more tests.
Jun 2, 2023
ebe2c70
Format.
Jun 2, 2023
fa09579
Linting fixes.
Jun 2, 2023
63faefc
Run tests with pytest directly.
Jun 2, 2023
bc0cf59
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 2, 2023
610ddd8
Merge remote-tracking branch 'origin/main' into tox4
Jan 19, 2024
7935e75
Add conda_python option.
Jan 19, 2024
305ff7f
Lint and type fixes.
Jan 19, 2024
023cc54
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 19, 2024
97f68b8
Try fixing tests.
Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ jobs:
- name: setup test suite
run: tox -vv --notest
- name: run test suite
run: tox --skip-pkg-install
run: |
. .tox/py310/bin/activate
cd tests
pytest . --timeout 180 --durations 5
env:
PYTEST_ADDOPTS: "-vv --durations=20"
CI_RUN: "yes"
Expand Down
8 changes: 8 additions & 0 deletions requirements_tests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-e .
devpi_process
pytest
pytest-cov
pytest-mock
pytest-ordering
pytest-timeout
tox>=4,<5
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers =
packages = find:
install_requires =
ruamel.yaml>=0.15.0,<0.18
tox>=3.8.1,<4
tox>=4,<5
python_requires = >=3.5

[options.packages.find]
Expand Down
102 changes: 101 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1 +1,101 @@
from tox._pytestplugin import * # noqa
from pathlib import Path
from types import TracebackType
from typing import Any, Dict, Optional, Sequence

import pytest
from pytest import MonkeyPatch
from pytest_mock import MockerFixture
from tox.execute.api import ExecuteInstance, ExecuteOptions, ExecuteStatus
from tox.execute.request import ExecuteRequest
from tox.execute.stream import SyncWrite
from tox.pytest import CaptureFixture, ToxProject, ToxProjectCreator

from tox_conda.plugin import CondaEnvRunner


@pytest.fixture(name="tox_project")
def init_fixture(
tmp_path: Path,
capfd: CaptureFixture,
monkeypatch: MonkeyPatch,
mocker: MockerFixture,
) -> ToxProjectCreator:
def _init(
files: Dict[str, Any], base: Optional[Path] = None, prj_path: Optional[Path] = None
) -> ToxProject:
"""create tox projects"""
return ToxProject(files, base, prj_path or tmp_path / "p", capfd, monkeypatch, mocker)

return _init


@pytest.fixture
def mock_conda_env_runner(request, monkeypatch):
class MockExecuteStatus(ExecuteStatus):
def __init__(
self, options: ExecuteOptions, out: SyncWrite, err: SyncWrite, exit_code: int
) -> None:
super().__init__(options, out, err)
self._exit_code = exit_code

@property
def exit_code(self) -> Optional[int]:
return self._exit_code

def wait(self, timeout: Optional[float] = None) -> Optional[int]: # noqa: U100
return self._exit_code

def write_stdin(self, content: str) -> None: # noqa: U100
return None # pragma: no cover

def interrupt(self) -> None:
return None # pragma: no cover

class MockExecuteInstance(ExecuteInstance):
def __init__(
self,
request: ExecuteRequest,
options: ExecuteOptions,
out: SyncWrite,
err: SyncWrite,
exit_code: int,
) -> None:
super().__init__(request, options, out, err)
self.exit_code = exit_code

def __enter__(self) -> ExecuteStatus:
return MockExecuteStatus(self.options, self._out, self._err, self.exit_code)

def __exit__(
self,
exc_type: Optional[BaseException], # noqa: U100
exc_val: Optional[BaseException], # noqa: U100
exc_tb: Optional[TracebackType], # noqa: U100
) -> None:
pass

@property
def cmd(self) -> Sequence[str]:
return self.request.cmd

shell_cmds = []
no_mocked_run_ids = getattr(request, "param", None)
if no_mocked_run_ids is None:
no_mocked_run_ids = ["_get_python"]
original_execute_instance_factor = CondaEnvRunner._execute_instance_factory

def mock_execute_instance_factory(
request: ExecuteRequest, options: ExecuteOptions, out: SyncWrite, err: SyncWrite
):
shell_cmds.append(request.shell_cmd)

if request.run_id not in no_mocked_run_ids:
return MockExecuteInstance(request, options, out, err, 0)
else:
return original_execute_instance_factor(request, options, out, err)

monkeypatch.setattr(CondaEnvRunner, "_execute_instance_factory", mock_execute_instance_factory)
monkeypatch.setenv("CONDA_EXE", "conda")
monkeypatch.setenv("CONDA_DEFAULT_ENV", "test-env")

yield shell_cmds
222 changes: 222 additions & 0 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
"""Cache tests."""

from fnmatch import fnmatch


def assert_create_command(cmd):
assert fnmatch(cmd, "*conda create*") or fnmatch(cmd, "*conda env create*")


def assert_install_command(cmd):
assert fnmatch(cmd, "*conda install*")


def test_conda_no_recreate(tox_project, mock_conda_env_runner):
ini = """
[testenv:py123]
skip_install = True
conda_env = conda-env.yml
conda_deps =
asdf
"""
yaml = """
name: tox-conda
channels:
- conda-forge
- nodefaults
dependencies:
- numpy
- astropy
- pip:
- pytest
"""
for _ in range(2):
proj = tox_project({"tox.ini": ini})
(proj.path / "conda-env.yml").write_text(yaml)
outcome = proj.run("-e", "py123")
outcome.assert_success()

executed_shell_commands = mock_conda_env_runner
# get_python, create env, install deps, get_python, and nothing else because no changes
assert len(executed_shell_commands) == 4
assert_create_command(executed_shell_commands[1])
assert_install_command(executed_shell_commands[2])


def test_conda_recreate_by_dependency_change(tox_project, mock_conda_env_runner):
ini = """
[testenv:py123]
skip_install = True
conda_deps =
asdf
"""
ini_modified = """
[testenv:py123]
skip_install = True
conda_deps =
asdf
black
"""
outcome = tox_project({"tox.ini": ini}).run("-e", "py123")
outcome.assert_success()

outcome = tox_project({"tox.ini": ini_modified}).run("-e", "py123")
outcome.assert_success()

executed_shell_commands = mock_conda_env_runner
# get_python, create env, install deps, get_python, create env, install deps
assert len(executed_shell_commands) == 6

assert_create_command(executed_shell_commands[1])
assert_install_command(executed_shell_commands[2])
assert_create_command(executed_shell_commands[4])
assert_install_command(executed_shell_commands[5])


def test_conda_recreate_by_env_file_path_change(tox_project, mock_conda_env_runner):
ini = """
[testenv:py123]
skip_install = True
conda_env = conda-env-1.yml
"""
ini_modified = """
[testenv:py123]
skip_install = True
conda_env = conda-env-2.yml
"""
yaml = """
name: tox-conda
channels:
- conda-forge
- nodefaults
dependencies:
- numpy
- astropy
- pip:
- pytest
"""

proj_1 = tox_project({"tox.ini": ini})
(proj_1.path / "conda-env-1.yml").write_text(yaml)
outcome = proj_1.run("-e", "py123")
outcome.assert_success()

proj_2 = tox_project({"tox.ini": ini_modified})
(proj_2.path / "conda-env-2.yml").write_text(yaml)
outcome = proj_2.run("-e", "py123")
outcome.assert_success()

executed_shell_commands = mock_conda_env_runner
# get_python, create env, get_python, create env
assert len(executed_shell_commands) == 4

assert_create_command(executed_shell_commands[1])
assert_create_command(executed_shell_commands[3])


def test_conda_recreate_by_env_file_content_change(tox_project, mock_conda_env_runner):
ini = """
[testenv:py123]
skip_install = True
conda_env = conda-env.yml
"""
yaml = """
name: tox-conda
channels:
- conda-forge
- nodefaults
dependencies:
- numpy
- astropy
"""
yaml_modified = """
name: tox-conda
channels:
- conda-forge
- nodefaults
dependencies:
- numpy
- astropy
- pip:
- pytest
"""

proj = tox_project({"tox.ini": ini})
(proj.path / "conda-env.yml").write_text(yaml)
outcome = proj.run("-e", "py123")
outcome.assert_success()

(proj.path / "conda-env.yml").write_text(yaml_modified)
outcome = proj.run("-e", "py123")
outcome.assert_success()

executed_shell_commands = mock_conda_env_runner
# get_python, create env, get_python, create env
assert len(executed_shell_commands) == 4

assert_create_command(executed_shell_commands[1])
assert_create_command(executed_shell_commands[3])


def test_conda_recreate_by_spec_file_path_change(tox_project, mock_conda_env_runner):
ini = """
[testenv:py123]
skip_install = True
conda_spec = conda_spec-1.txt
"""
ini_modified = """
[testenv:py123]
skip_install = True
conda_spec = conda_spec-2.txt
"""
proj_1 = tox_project({"tox.ini": ini})
(proj_1.path / "conda_spec-1.txt").touch()
outcome = proj_1.run("-e", "py123")
outcome.assert_success()

proj_2 = tox_project({"tox.ini": ini_modified})
(proj_2.path / "conda_spec-2.txt").touch()
outcome = proj_2.run("-e", "py123")
outcome.assert_success()

executed_shell_commands = mock_conda_env_runner
# get_python, create env, install deps, get_python, create env, install deps
assert len(executed_shell_commands) == 6

assert_create_command(executed_shell_commands[1])
assert_install_command(executed_shell_commands[2])
assert_create_command(executed_shell_commands[4])
assert_install_command(executed_shell_commands[5])


def test_conda_recreate_by_spec_file_content_change(tox_project, mock_conda_env_runner):
ini = """
[testenv:py123]
skip_install = True
conda_spec = conda_spec.txt
"""
txt = """
black
"""
txt_modified = """
black
numpy
"""

proj = tox_project({"tox.ini": ini})
(proj.path / "conda_spec.txt").write_text(txt)
outcome = proj.run("-e", "py123")
outcome.assert_success()

(proj.path / "conda_spec.txt").write_text(txt_modified)
outcome = proj.run("-e", "py123")
outcome.assert_success()

executed_shell_commands = mock_conda_env_runner
# get_python, create env, install deps, get_python, create env, install deps
assert len(executed_shell_commands) == 6

assert_create_command(executed_shell_commands[1])
assert_install_command(executed_shell_commands[2])
assert_create_command(executed_shell_commands[4])
assert_install_command(executed_shell_commands[5])
Loading
Loading