diff --git a/pyproject.toml b/pyproject.toml index e98c390..61bf8ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pytest-watcher" -version = "0.2.4" +version = "0.2.5" description = "Continiously runs pytest on changes in *.py files" authors = ["Olzhas Arystanov "] license = "MIT" diff --git a/pytest_watcher/__init__.py b/pytest_watcher/__init__.py index 8008780..c9973ac 100644 --- a/pytest_watcher/__init__.py +++ b/pytest_watcher/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.2.4" +__version__ = "0.2.5" from .watcher import run diff --git a/pytest_watcher/watcher.py b/pytest_watcher/watcher.py index a65c0a5..957dd2f 100644 --- a/pytest_watcher/watcher.py +++ b/pytest_watcher/watcher.py @@ -57,7 +57,7 @@ def _run_pytest(args) -> None: subprocess.run(["pytest", *args]) -def _parse_arguments(args: Sequence[str]) -> Tuple[Path, float, Sequence[str]]: +def _parse_arguments(args: Sequence[str]) -> Tuple[Path, bool, float, Sequence[str]]: parser = argparse.ArgumentParser( prog="pytest_watcher", description=""" @@ -67,6 +67,7 @@ def _parse_arguments(args: Sequence[str]) -> Tuple[Path, float, Sequence[str]]: """, ) parser.add_argument("path", type=Path, help="path to watch") + parser.add_argument("--now", action="store_true", help="Run pytest instantly") parser.add_argument( "--delay", type=float, @@ -76,7 +77,7 @@ def _parse_arguments(args: Sequence[str]) -> Tuple[Path, float, Sequence[str]]: namespace, pytest_args = parser.parse_known_args(args) - return namespace.path, namespace.delay, pytest_args + return namespace.path, namespace.now, namespace.delay, pytest_args def _run_main_loop(delay: float, pytest_args: Sequence[str]) -> None: @@ -93,7 +94,7 @@ def _run_main_loop(delay: float, pytest_args: Sequence[str]) -> None: def run(): - path_to_watch, delay, pytest_args = _parse_arguments(sys.argv[1:]) + path_to_watch, now, delay, pytest_args = _parse_arguments(sys.argv[1:]) event_handler = EventHandler() @@ -102,6 +103,9 @@ def run(): observer.schedule(event_handler, path_to_watch, recursive=True) observer.start() + if now: + emit_trigger() + try: while True: _run_main_loop(delay, pytest_args) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..aa040e2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,29 @@ +import pytest +from pytest_mock import MockerFixture + + +@pytest.fixture() +def mock_subprocess_run(mocker: MockerFixture): + return mocker.patch("pytest_watcher.watcher.subprocess.run", autospec=True) + + +@pytest.fixture(autouse=True) +def mock_time_sleep(mocker: MockerFixture): + return mocker.patch("pytest_watcher.watcher.time.sleep", autospec=True) + + +@pytest.fixture +def mock_emit_trigger(mocker: MockerFixture): + return mocker.patch("pytest_watcher.watcher.emit_trigger", autospec=True) + + +@pytest.fixture +def mock_observer(mocker: MockerFixture): + return mocker.patch("pytest_watcher.watcher.Observer", autospec=True) + + +@pytest.fixture +def mock_run_main_loop(mocker: MockerFixture): + mock = mocker.patch("pytest_watcher.watcher._run_main_loop", autospec=True) + mock.side_effect = InterruptedError + return mock diff --git a/tests/test_pytest_watcher.py b/tests/test_pytest_watcher.py index 791d369..5c1e138 100644 --- a/tests/test_pytest_watcher.py +++ b/tests/test_pytest_watcher.py @@ -1,26 +1,17 @@ +import sys from datetime import datetime from unittest.mock import MagicMock import pytest from freezegun import freeze_time -from pytest_mock import MockerFixture +from pytest_mock.plugin import MockerFixture from watchdog import events from pytest_watcher import __version__, watcher def test_version(): - assert __version__ == "0.2.4" - - -@pytest.fixture() -def mock_subprocess_run(mocker: MockerFixture): - return mocker.patch("pytest_watcher.watcher.subprocess.run") - - -@pytest.fixture(autouse=True) -def mock_time_sleep(mocker: MockerFixture): - return mocker.patch("pytest_watcher.watcher.time.sleep") + assert __version__ == "0.2.5" @pytest.fixture(autouse=True) @@ -46,10 +37,6 @@ def test_is_path_watched(filepath, expected): class TestEventHandler: - @pytest.fixture - def mock_emit_trigger(self, mocker: MockerFixture): - return mocker.patch("pytest_watcher.watcher.emit_trigger") - @pytest.mark.parametrize("event_type", watcher.EventHandler.EVENTS_WATCHED) def test_src_watched(self, event_type, mock_emit_trigger: MagicMock): event = events.FileSystemEvent("main.py") @@ -113,19 +100,22 @@ def test_emit_trigger(): # fmt: off @pytest.mark.parametrize( - ("sys_args", "path_to_watch", "delay", "pytest_args"), + ("sys_args", "path_to_watch", "now", "delay", "pytest_args"), [ - (["/home/"], "/home", 0.5, []), - (["/home/", "--lf", "--nf", "-x"], "/home", 0.5, ["--lf", "--nf", "-x"]), - ([".", "--lf", "--nf", "-x"], ".", 0.5, ["--lf", "--nf", "-x"]), - ([".", "--delay=0.2", "--lf", "--nf", "-x"], ".", 0.2, ["--lf", "--nf", "-x"]), - ([".", "--lf", "--nf", "--delay=0.3", "-x"], ".", 0.3, ["--lf", "--nf", "-x"]), + (["/home/"], "/home", False, 0.5, []), + (["/home/", "--lf", "--nf", "-x"], "/home", False, 0.5, ["--lf", "--nf", "-x"]), + (["/home/", "--lf", "--now", "--nf", "-x"], "/home", True, 0.5, ["--lf", "--nf", "-x"]), + (["/home/", "--now", "--lf", "--nf", "-x"], "/home", True, 0.5, ["--lf", "--nf", "-x"]), + ([".", "--lf", "--nf", "-x"], ".", False, 0.5, ["--lf", "--nf", "-x"]), + ([".", "--delay=0.2", "--lf", "--nf", "-x"], ".", False, 0.2, ["--lf", "--nf", "-x"]), + ([".", "--lf", "--nf", "--delay=0.3", "-x"], ".", False, 0.3, ["--lf", "--nf", "-x"]), ], ) -def test_parse_arguments(sys_args, path_to_watch, delay, pytest_args): - _path, _delay, _pytest_args = watcher._parse_arguments(sys_args) +def test_parse_arguments(sys_args, path_to_watch, now, delay, pytest_args): + _path, _now, _delay, _pytest_args = watcher._parse_arguments(sys_args) assert str(_path) == path_to_watch + assert _now == now assert _delay == delay assert _pytest_args == pytest_args @@ -173,3 +163,49 @@ def test_run_main_loop_trigger( mock_time_sleep.assert_called_once_with(5) assert watcher.trigger is None + + +def test_run( + mocker: MockerFixture, + mock_observer: MagicMock, + mock_emit_trigger: MagicMock, + mock_run_main_loop: MagicMock, +): + args = ["ptw", ".", "--lf", "--nf"] + + mocker.patch.object(sys, "argv", args) + + with pytest.raises(InterruptedError): + watcher.run() + + mock_observer.assert_called_once_with() + observer_instance = mock_observer.return_value + observer_instance.schedule.assert_called_once() + observer_instance.start.assert_called_once() + + mock_emit_trigger.assert_not_called() + + mock_run_main_loop.assert_called_once_with(0.5, ["--lf", "--nf"]) + + +def test_run_now( + mocker: MockerFixture, + mock_observer: MagicMock, + mock_emit_trigger: MagicMock, + mock_run_main_loop: MagicMock, +): + args = ["ptw", ".", "--lf", "--nf", "--now"] + + mocker.patch.object(sys, "argv", args) + + with pytest.raises(InterruptedError): + watcher.run() + + mock_observer.assert_called_once_with() + observer_instance = mock_observer.return_value + observer_instance.schedule.assert_called_once() + observer_instance.start.assert_called_once() + + mock_emit_trigger.assert_called_once_with() + + mock_run_main_loop.assert_called_once_with(0.5, ["--lf", "--nf"]) diff --git a/tox.ini b/tox.ini index 807cebd..a4de755 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = True -envlist = py36,py37,py38,py39,py310 +envlist = py36,py37,py38,py39,py310,py311 [tox:.package] basepython = python3