From d04ac50985f097ffd7d76dd06f5e0eedfbcab8fe Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Sat, 28 Nov 2020 18:55:25 -0500 Subject: [PATCH 1/8] Add test helpers, rewrite integration tests --- riotfile.py | 2 +- tests/proctest.py | 73 ++++++ tests/test_cli.py | 385 ----------------------------- tests/test_integration.py | 501 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 575 insertions(+), 386 deletions(-) create mode 100644 tests/proctest.py create mode 100644 tests/test_integration.py diff --git a/riotfile.py b/riotfile.py index b00ceaaa..202fa938 100644 --- a/riotfile.py +++ b/riotfile.py @@ -12,7 +12,7 @@ }, pys=[3.6, 3.7, 3.8, 3.9], pkgs={ - "pytest": latest, + "pytest": "==6.1.2", "pytest-cov": latest, "mock": latest, }, diff --git a/tests/proctest.py b/tests/proctest.py new file mode 100644 index 00000000..c8e2b384 --- /dev/null +++ b/tests/proctest.py @@ -0,0 +1,73 @@ +import dataclasses +import os +import subprocess +import sys +import tempfile +import typing as t + + +if t.TYPE_CHECKING or sys.version_info[:2] >= (3, 9): + _T_CompletedProcess = subprocess.CompletedProcess[str] +else: + _T_CompletedProcess = subprocess.CompletedProcess + +_T_Path = t.Union[str, "os.PathLike[t.Any]"] + + +@dataclasses.dataclass(frozen=True) +class ProcResult: + proc: _T_CompletedProcess = dataclasses.field(repr=False) + + @property + def stdout(self) -> str: + return self.proc.stdout + + @property + def stderr(self) -> str: + return self.proc.stderr + + @property + def returncode(self) -> int: + return self.proc.returncode + + +class TestDir: + def __init__(self): + self._tmpdir = tempfile.TemporaryDirectory() + self.cwd = self._tmpdir.name + + def __fspath__(self): + """Implement Pathlike interface.""" + return self.cwd + + def cd(self, path: _T_Path) -> None: + res_path = path if os.path.isabs(path) else os.path.join(self.cwd, path) + self.cwd = res_path + + def mkdir(self, path: _T_Path) -> None: + res_path = path if os.path.isabs(path) else os.path.join(self.cwd, path) + os.mkdir(res_path) + + def mkfile(self, path: _T_Path, contents: str) -> None: + res_path = path if os.path.isabs(path) else os.path.join(self.cwd, path) + with open(res_path, "w") as f: + f.write(contents) + + def run( + self, + args: t.Union[str, t.Sequence[str]], + env: t.Optional[t.Dict[str, str]] = None, + cwd: t.Optional[_T_Path] = None, + ) -> ProcResult: + if isinstance(args, str): + args = args.split(" ") + + p = subprocess.run( + args, + env=env, + encoding=sys.getdefaultencoding(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=self.cwd, + ) + return ProcResult(p) diff --git a/tests/test_cli.py b/tests/test_cli.py index a68d2dfb..d4f1e09f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -40,72 +40,6 @@ def without_riotfile( yield -def test_main(cli: click.testing.CliRunner) -> None: - """Running main with no command returns usage.""" - result = cli.invoke(riot.cli.main) - assert result.exit_code == 0 - assert result.stdout.startswith("Usage: main") - - -def test_main_help(cli: click.testing.CliRunner) -> None: - """Running main with --help returns usage.""" - result = cli.invoke(riot.cli.main, ["--help"]) - assert result.exit_code == 0 - assert result.stdout.startswith("Usage: main") - - -def test_main_version(cli: click.testing.CliRunner) -> None: - """Running main with --version returns version string.""" - result = cli.invoke(riot.cli.main, ["--version"]) - assert result.exit_code == 0 - assert result.stdout.startswith("main, version ") - - -def test_list_empty(cli: click.testing.CliRunner) -> None: - """Running list with an empty riotfile prints nothing.""" - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke(riot.cli.main, ["list"]) - assert result.exit_code == 0 - assert result.stdout == "" - - -def test_list_no_riotfile(cli: click.testing.CliRunner) -> None: - """Running list with no riotfile fails with an error.""" - with without_riotfile(cli): - result = cli.invoke(riot.cli.main, ["list"]) - assert result.exit_code == 2 - assert result.stdout.startswith("Usage: main") - assert result.stdout.endswith( - "Error: Invalid value for '-f' / '--file': Path 'riotfile.py' does not exist.\n" - ) - - -def test_list_default_pattern(cli: click.testing.CliRunner) -> None: - """Running list with no pattern passes through the default pattern.""" - with mock.patch("riot.cli.Session.list_venvs") as list_venvs: - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke(riot.cli.main, ["list"]) - # Success, but no output because we don't have a matching pattern - assert result.exit_code == 0 - assert result.stdout == "" - - list_venvs.assert_called_once() - assert list_venvs.call_args.args[0].pattern == ".*" - - -def test_list_with_pattern(cli: click.testing.CliRunner) -> None: - """Running list with a pattern passes through the pattern.""" - with mock.patch("riot.cli.Session.list_venvs") as list_venvs: - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke(riot.cli.main, ["list", "^pattern.*"]) - # Success, but no output because we don't have a matching pattern - assert result.exit_code == 0 - assert result.stdout == "" - - list_venvs.assert_called_once() - assert list_venvs.call_args.args[0].pattern == "^pattern.*" - - def test_list_with_venv_pattern(cli: click.testing.CliRunner) -> None: """Running list with a venv pattern passes.""" with with_riotfile(cli, "simple_riotfile.py"): @@ -153,125 +87,6 @@ def test_list_with_python(cli: click.testing.CliRunner) -> None: ) -def test_run(cli: click.testing.CliRunner) -> None: - """Running run with default options.""" - with mock.patch("riot.cli.Session.run") as run: - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke(riot.cli.main, ["run"]) - # Success, but no output because we mock run - assert result.exit_code == 0 - assert result.stdout == "" - - run.assert_called_once() - kwargs = run.call_args.kwargs - assert set(kwargs.keys()) == set( - [ - "pattern", - "venv_pattern", - "recreate_venvs", - "skip_base_install", - "pass_env", - "cmdargs", - "pythons", - ] - ) - assert kwargs["pattern"].pattern == ".*" - assert kwargs["venv_pattern"].pattern == ".*" - assert kwargs["recreate_venvs"] is False - assert kwargs["skip_base_install"] is False - assert kwargs["pass_env"] is False - - -def test_run_with_long_args(cli: click.testing.CliRunner) -> None: - """Running run with long option names uses those options.""" - with mock.patch("riot.cli.Session.run") as run: - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke( - riot.cli.main, - ["run", "--recreate-venvs", "--skip-base-install", "--pass-env"], - ) - # Success, but no output because we mock run - assert result.exit_code == 0 - assert result.stdout == "" - - run.assert_called_once() - kwargs = run.call_args.kwargs - assert set(kwargs.keys()) == set( - [ - "pattern", - "venv_pattern", - "recreate_venvs", - "skip_base_install", - "pass_env", - "cmdargs", - "pythons", - ] - ) - assert kwargs["pattern"].pattern == ".*" - assert kwargs["venv_pattern"].pattern == ".*" - assert kwargs["recreate_venvs"] is True - assert kwargs["skip_base_install"] is True - assert kwargs["pass_env"] is True - - -def test_run_with_short_args(cli: click.testing.CliRunner) -> None: - """Running run with short option names uses those options.""" - with mock.patch("riot.cli.Session.run") as run: - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke(riot.cli.main, ["run", "-r", "-s"]) - # Success, but no output because we mock run - assert result.exit_code == 0 - assert result.stdout == "" - - run.assert_called_once() - kwargs = run.call_args.kwargs - assert set(kwargs.keys()) == set( - [ - "pattern", - "venv_pattern", - "recreate_venvs", - "skip_base_install", - "pass_env", - "cmdargs", - "pythons", - ] - ) - assert kwargs["pattern"].pattern == ".*" - assert kwargs["venv_pattern"].pattern == ".*" - assert kwargs["recreate_venvs"] is True - assert kwargs["skip_base_install"] is True - assert kwargs["pass_env"] is False - - -def test_run_with_pattern(cli: click.testing.CliRunner) -> None: - """Running run with pattern passes in that pattern.""" - with mock.patch("riot.cli.Session.run") as run: - with with_riotfile(cli, "empty_riotfile.py"): - result = cli.invoke(riot.cli.main, ["run", "^pattern.*"]) - # Success, but no output because we mock run - assert result.exit_code == 0 - assert result.stdout == "" - - run.assert_called_once() - kwargs = run.call_args.kwargs - assert set(kwargs.keys()) == set( - [ - "pattern", - "venv_pattern", - "recreate_venvs", - "skip_base_install", - "pass_env", - "cmdargs", - "pythons", - ] - ) - assert kwargs["pattern"].pattern == "^pattern.*" - assert kwargs["venv_pattern"].pattern == ".*" - assert kwargs["recreate_venvs"] is False - assert kwargs["skip_base_install"] is False - assert kwargs["pass_env"] is False - - def test_run_no_venv_pattern(cli: click.testing.CliRunner) -> None: """Running run with pattern passes in that pattern.""" with mock.patch("riot.riot.logger.debug") as mock_debug: @@ -389,203 +204,3 @@ def test_generate_base_venvs_with_pattern(cli: click.testing.CliRunner) -> None: assert kwargs["pattern"].pattern == "^pattern.*" assert kwargs["recreate"] is False assert kwargs["skip_deps"] is False - - -@pytest.mark.parametrize( - "name,cmdargs,cmdrun", - [ - ("test_cmdargs", [], "echo cmdargs="), - ("test_cmdargs", ["--", "-k", "filter"], "echo cmdargs=-k filter"), - ("test_nocmdargs", [], "echo no cmdargs"), - ("test_nocmdargs", ["--", "-k", "filter"], "echo no cmdargs"), - ], -) -def test_run_suites_cmdargs( - cli: click.testing.CliRunner, name: str, cmdargs: typing.List[str], cmdrun: str -) -> None: - """Running command with optional infix cmdargs.""" - with cli.isolated_filesystem(): - with open("riotfile.py", "w") as f: - f.write( - """ -from riot import Venv - -venv = Venv( - venvs=[ - Venv( - name="test_nocmdargs", - command="echo no cmdargs", - venvs=[ - Venv( - pys=[3], - ), - ], - ), - Venv( - name="test_cmdargs", - command="echo cmdargs={cmdargs}", - venvs=[ - Venv( - pys=[3], - ), - ], - ), - ] -) - """ - ) - with mock.patch("subprocess.run") as subprocess_run: - subprocess_run.return_value.returncode = 0 - args = ["run", name] + cmdargs - result = cli.invoke(riot.cli.main, args, catch_exceptions=False) - assert result.exit_code == 0 - assert result.stdout == "" - - subprocess_run.assert_called() - - cmd = subprocess_run.call_args_list[-1].args[0] - assert cmd.endswith(cmdrun) - - -def test_nested_venv(cli: click.testing.CliRunner) -> None: - with cli.isolated_filesystem(): - with open("riotfile.py", "w") as f: - f.write( - """ -from riot import Venv - -venv = Venv( - pys=[3], - pkgs={ - "pytest": [""], - }, - venvs=[ - Venv( - name="success", - command="pytest test_success.py", - ), - Venv( - name="failure", - command="pytest test_failure.py", - ), - ], -) - """ - ) - - with open("test_success.py", "w") as f: - f.write( - """ -def test_success(): - assert 1 == 1 - """ - ) - - with open("test_failure.py", "w") as f: - f.write( - """ -def test_failure(): - assert 1 == 0 - """ - ) - - result = cli.invoke( - riot.cli.main, ["run", "-s", "success"], catch_exceptions=False - ) - assert result.exit_code == 0 - assert result.stdout == "" - - result = cli.invoke( - riot.cli.main, ["run", "-s", "failure"], catch_exceptions=False - ) - assert result.exit_code == 1 - assert result.stdout == "" - - -def test_types(cli: click.testing.CliRunner) -> None: - with cli.isolated_filesystem(): - with open("riotfile.py", "w") as f: - f.write( - """ -from riot import Venv - -venv = Venv( - venvs=[ - Venv( - pys=[3], - name="success", - command="exit 0", - ), - Venv( - pys=[3], - name="success2", - command="exit 0", - ), - ], -) - """ - ) - - with open("test_success.py", "w") as f: - f.write( - """ -def test_success(): - assert 1 == 1 - """ - ) - - result = cli.invoke( - riot.cli.main, ["run", "-s", "success"], catch_exceptions=False - ) - assert result.exit_code == 0 - assert result.stdout == "" - - result = cli.invoke( - riot.cli.main, ["run", "-s", "success2"], catch_exceptions=False - ) - assert result.exit_code == 0 - assert result.stdout == "" - - -def test_bad_riotfile_name(cli: click.testing.CliRunner) -> None: - with cli.isolated_filesystem(): - with open("riotfile", "w") as f: - f.write( - """ -from riot import Venv - -venv = Venv( - venvs=[ - Venv( - pys=[3], - name="success", - command="echo hello", - ), - ], -) - """ - ) - - result = cli.invoke( - riot.cli.main, ["-f", "riotfile", "list"], catch_exceptions=False - ) - assert result.exit_code == 1 - assert ( - result.stdout - == "Failed to construct config file:\nInvalid file format for riotfile. Expected file with .py extension got 'riotfile'.\n" - ) - - -def test_riotfile_execute_error(cli: click.testing.CliRunner) -> None: - with cli.isolated_filesystem(): - with open("riotfile.py", "w") as f: - f.write( - """ -this is invalid syntax - """ - ) - - result = cli.invoke(riot.cli.main, ["list"], catch_exceptions=False) - assert result.exit_code == 1 - assert "Failed to parse" in result.stdout - assert "SyntaxError: invalid syntax" in result.stdout diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 00000000..036424fb --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,501 @@ +import re + +import pytest + +from .proctest import TestDir + + +@pytest.fixture +def tdir(): + yield TestDir() + + +def test_no_riotfile(tdir: TestDir) -> None: + result = tdir.run("riot") + assert ( + result.stdout + == """ +Usage: riot [OPTIONS] COMMAND [ARGS]... + +Options: + -f, --file PATH [default: riotfile.py] + -v, --verbose + -d, --debug + --version Show the version and exit. + --help Show this message and exit. + +Commands: + generate Generate base virtual environments. + list List all virtual env instances matching a pattern. + run Run virtualenv instances with names matching a pattern. +""".lstrip() + ) + assert result.stderr == "" + assert result.returncode == 0 + + result = tdir.run("riot list") + assert ( + result.stderr + == """ +Usage: riot [OPTIONS] COMMAND [ARGS]... +Try 'riot --help' for help. + +Error: Invalid value for '-f' / '--file': Path 'riotfile.py' does not exist. +""".lstrip() + ) + assert result.stdout == "" + assert result.returncode == 2 + + +def test_bad_riotfile(tdir: TestDir) -> None: + result = tdir.run("riot --file rf.py") + assert ( + result.stderr + == """ +Usage: riot [OPTIONS] COMMAND [ARGS]... +Try 'riot --help' for help. + +Error: Invalid value for '-f' / '--file': Path 'rf.py' does not exist. +""".lstrip() + ) + assert result.returncode == 2 + + tdir.mkfile( + "rf", + """ +from riot import Venv +venv = Venv() +""", + ) + result = tdir.run("riot --file rf list") + assert ( + result.stderr + == """ +Failed to construct config file: +Invalid file format for riotfile. Expected file with .py extension got 'rf'. +""".lstrip() + ) + assert result.returncode == 1 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv()typo1234 +""", + ) + result = tdir.run("riot --file riotfile.py list") + assert ( + """ +Failed to construct config file: +Failed to parse riotfile 'riotfile.py'. +""".lstrip() + in result.stderr + ) + assert ( + """ +SyntaxError: invalid syntax +""".lstrip() + in result.stderr + ) + assert result.returncode == 1 + + +def test_help(tdir: TestDir) -> None: + result = tdir.run("riot --help") + assert ( + result.stdout + == """ +Usage: riot [OPTIONS] COMMAND [ARGS]... + +Options: + -f, --file PATH [default: riotfile.py] + -v, --verbose + -d, --debug + --version Show the version and exit. + --help Show this message and exit. + +Commands: + generate Generate base virtual environments. + list List all virtual env instances matching a pattern. + run Run virtualenv instances with names matching a pattern. +""".lstrip() + ) + assert result.stderr == "" + assert result.returncode == 0 + + +def test_version(tdir: TestDir) -> None: + result = tdir.run("riot --version") + assert result.stdout.startswith("riot, version ") + assert result.stderr == "" + assert result.returncode == 0 + + +def test_list_no_file_empty_file(tdir: TestDir) -> None: + result = tdir.run("riot list") + assert ( + result.stderr + == """ +Usage: riot [OPTIONS] COMMAND [ARGS]... +Try 'riot --help' for help. + +Error: Invalid value for '-f' / '--file': Path 'riotfile.py' does not exist. +""".lstrip() + ) + assert result.returncode == 2 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +""", + ) + result = tdir.run("riot list") + assert result.stderr == "" + assert result.stdout == "" + assert result.returncode == 0 + + +def test_list_configurations(tdir: TestDir) -> None: + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + name="test", + pys=[3], + command="echo hi", +) +""", + ) + result = tdir.run("riot list") + assert result.stderr == "" + assert result.stdout == "test Interpreter(_hint='3') \n" + assert result.returncode == 0 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + name="test", + pys=[3], + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + } +) +""", + ) + result = tdir.run("riot list") + assert result.stderr == "" + assert re.search( + r""" +test .* 'pkg1==1.0' +test .* 'pkg1==2.0' +""".lstrip(), + result.stdout, + ) + assert result.returncode == 0 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + name="test", + pys=[3], + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + "pkg2": ["==2.0", "==3.0"], + } +) +""", + ) + result = tdir.run("riot list") + assert result.stderr == "" + assert re.search( + r""" +test .* 'pkg1==1.0' 'pkg2==2.0' +test .* 'pkg1==1.0' 'pkg2==3.0' +test .* 'pkg1==2.0' 'pkg2==2.0' +test .* 'pkg1==2.0' 'pkg2==3.0' +""".lstrip(), + result.stdout, + ) + assert result.returncode == 0 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys=[3], + venvs=[ + Venv( + name="test1", + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + "pkg2": ["==3.0", "==4.0"], + } + ), + Venv( + name="test2", + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + "pkg2": ["==3.0", "==4.0"], + } + ), + ] +) +""", + ) + result = tdir.run("riot list") + assert result.stderr == "" + assert re.search( + r""" +test1 .* 'pkg1==1.0' 'pkg2==3.0' +test1 .* 'pkg1==1.0' 'pkg2==4.0' +test1 .* 'pkg1==2.0' 'pkg2==3.0' +test1 .* 'pkg1==2.0' 'pkg2==4.0' +test2 .* 'pkg1==1.0' 'pkg2==3.0' +test2 .* 'pkg1==1.0' 'pkg2==4.0' +test2 .* 'pkg1==2.0' 'pkg2==3.0' +test2 .* 'pkg1==2.0' 'pkg2==4.0' +""".lstrip(), + result.stdout, + ) + assert result.returncode == 0 + + +def test_list_filter(tdir: TestDir) -> None: + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + name="test", + pys=[3], + command="echo hi", +) +""", + ) + result = tdir.run("riot list test") + assert result.stderr == "" + assert re.search(r"test .*", result.stdout) + assert result.returncode == 0 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + name="test", + pys=[3], + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + "pkg2": ["==2.0", "==3.0"], + } +) +""", + ) + result = tdir.run("riot list test") + assert result.stderr == "" + assert re.search( + r""" +test .* 'pkg1==1.0' 'pkg2==2.0' +test .* 'pkg1==1.0' 'pkg2==3.0' +test .* 'pkg1==2.0' 'pkg2==2.0' +test .* 'pkg1==2.0' 'pkg2==3.0' +""".lstrip(), + result.stdout, + ) + assert result.returncode == 0 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys=[3], + venvs=[ + Venv( + name="test1", + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + "pkg2": ["==3.0", "==4.0"], + } + ), + Venv( + name="test2", + command="echo hi", + pkgs={ + "pkg1": ["==1.0", "==2.0"], + "pkg2": ["==3.0", "==4.0"], + } + ), + ] +) +""", + ) + result = tdir.run("riot list test2") + assert result.stderr == "" + assert re.search( + r""" +test2 .* 'pkg1==1.0' 'pkg2==3.0' +test2 .* 'pkg1==1.0' 'pkg2==4.0' +test2 .* 'pkg1==2.0' 'pkg2==3.0' +test2 .* 'pkg1==2.0' 'pkg2==4.0' +""".lstrip(), + result.stdout, + ) + assert result.returncode == 0 + + +def test_run(tdir: TestDir) -> None: + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys=[3], + pkgs={ + "pytest": [""], + }, + venvs=[ + Venv( + name="pass", + command="pytest test_success.py", + ), + Venv( + name="fail", + command="pytest test_failure.py", + ), + ], +) +""", + ) + tdir.mkfile( + "test_success.py", + """ +def test_success(): + assert 1 == 1 +""", + ) + tdir.mkfile( + "test_failure.py", + """ +def test_failure(): + assert 1 == 0 +""", + ) + result = tdir.run("riot run -s pass") + assert re.search( + r""" +============================= test session starts ============================== +platform.* +rootdir:.* +collected 1 item + +test_success.py .* + +============================== 1 passed in .*s =============================== + +-------------------summary------------------- +✔️ pass: .* 'pytest'\n""".lstrip(), + result.stdout, + ) + assert result.stderr == "" + assert result.returncode == 0 + + result = tdir.run("riot run -s fail") + assert "✖️ fail: Interpreter(_hint='3') 'pytest'\n" in result.stdout + assert result.stderr == "" + assert result.returncode == 1 + + +def test_run_cmdargs(tdir: TestDir) -> None: + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys=[3], + name="test_cmdargs", + command="echo hi", +) +""", + ) + result = tdir.run("riot run -s test_cmdargs -- -k filter") + assert "cmdargs=-k filter" not in result.stdout + assert result.stderr == "" + assert result.returncode == 0 + + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys=3, + name="test_cmdargs", + command="echo cmdargs={cmdargs}", +) +""", + ) + result = tdir.run("riot run -s test_cmdargs -- -k filter") + assert "cmdargs=-k filter" in result.stdout + assert result.stderr == "" + assert result.returncode == 0 + + +def test_dev_install_fail(tdir: TestDir) -> None: + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys=3, + name="test", + command="echo hello", +) +""", + ) + result = tdir.run("riot run test") + assert ( + """ +ERROR: File "setup.py" not found. Directory cannot be installed in editable mode: +""".strip() + in result.stderr + ) + assert "Dev install failed, aborting!" in result.stderr + assert result.stdout == "" + assert result.returncode == 1 + + +def test_bad_interpreter(tdir: TestDir) -> None: + tdir.mkfile( + "riotfile.py", + """ +from riot import Venv +venv = Venv( + pys="DNE", + name="test", + command="echo hello", +) +""", + ) + result = tdir.run("riot run -s -pDNE test") + assert ( + """ +FileNotFoundError: Python interpreter DNE not found +""".strip() + in result.stderr + ) + assert result.stdout == "" + assert result.returncode == 1 From e624d14ae39dc09dcea2ec0ef5448515a5f8b718 Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Fri, 21 May 2021 17:25:45 -0400 Subject: [PATCH 2/8] Remove custom proctest, use tmp_path pytest fixture --- riotfile.py | 2 +- tests/proctest.py | 73 ----------------- tests/test_integration.py | 159 +++++++++++++++++++++----------------- 3 files changed, 87 insertions(+), 147 deletions(-) delete mode 100644 tests/proctest.py diff --git a/riotfile.py b/riotfile.py index 202fa938..b00ceaaa 100644 --- a/riotfile.py +++ b/riotfile.py @@ -12,7 +12,7 @@ }, pys=[3.6, 3.7, 3.8, 3.9], pkgs={ - "pytest": "==6.1.2", + "pytest": latest, "pytest-cov": latest, "mock": latest, }, diff --git a/tests/proctest.py b/tests/proctest.py deleted file mode 100644 index c8e2b384..00000000 --- a/tests/proctest.py +++ /dev/null @@ -1,73 +0,0 @@ -import dataclasses -import os -import subprocess -import sys -import tempfile -import typing as t - - -if t.TYPE_CHECKING or sys.version_info[:2] >= (3, 9): - _T_CompletedProcess = subprocess.CompletedProcess[str] -else: - _T_CompletedProcess = subprocess.CompletedProcess - -_T_Path = t.Union[str, "os.PathLike[t.Any]"] - - -@dataclasses.dataclass(frozen=True) -class ProcResult: - proc: _T_CompletedProcess = dataclasses.field(repr=False) - - @property - def stdout(self) -> str: - return self.proc.stdout - - @property - def stderr(self) -> str: - return self.proc.stderr - - @property - def returncode(self) -> int: - return self.proc.returncode - - -class TestDir: - def __init__(self): - self._tmpdir = tempfile.TemporaryDirectory() - self.cwd = self._tmpdir.name - - def __fspath__(self): - """Implement Pathlike interface.""" - return self.cwd - - def cd(self, path: _T_Path) -> None: - res_path = path if os.path.isabs(path) else os.path.join(self.cwd, path) - self.cwd = res_path - - def mkdir(self, path: _T_Path) -> None: - res_path = path if os.path.isabs(path) else os.path.join(self.cwd, path) - os.mkdir(res_path) - - def mkfile(self, path: _T_Path, contents: str) -> None: - res_path = path if os.path.isabs(path) else os.path.join(self.cwd, path) - with open(res_path, "w") as f: - f.write(contents) - - def run( - self, - args: t.Union[str, t.Sequence[str]], - env: t.Optional[t.Dict[str, str]] = None, - cwd: t.Optional[_T_Path] = None, - ) -> ProcResult: - if isinstance(args, str): - args = args.split(" ") - - p = subprocess.run( - args, - env=env, - encoding=sys.getdefaultencoding(), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=self.cwd, - ) - return ProcResult(p) diff --git a/tests/test_integration.py b/tests/test_integration.py index 036424fb..c387ec4d 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,17 +1,38 @@ +import os +import pathlib import re +import subprocess +import sys +from typing import Any +from typing import Dict +from typing import Optional +from typing import Sequence +from typing import Union import pytest -from .proctest import TestDir +from riot.riot import _T_CompletedProcess -@pytest.fixture -def tdir(): - yield TestDir() +_T_Path = Union[str, "os.PathLike[Any]"] -def test_no_riotfile(tdir: TestDir) -> None: - result = tdir.run("riot") +def run(args: Union[str, Sequence[str]], cwd: _T_Path, env: Optional[Dict[str, str]] = None) -> _T_CompletedProcess: + if isinstance(args, str): + args = args.split(" ") + + return subprocess.run( + args, + env=env, + encoding=sys.getdefaultencoding(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=cwd, + ) + + +def test_no_riotfile(tmp_path: pathlib.Path) -> None: + result = run("riot", cwd=tmp_path) assert ( result.stdout == """ @@ -33,7 +54,7 @@ def test_no_riotfile(tdir: TestDir) -> None: assert result.stderr == "" assert result.returncode == 0 - result = tdir.run("riot list") + result = run("riot list", cwd=tmp_path) assert ( result.stderr == """ @@ -47,8 +68,8 @@ def test_no_riotfile(tdir: TestDir) -> None: assert result.returncode == 2 -def test_bad_riotfile(tdir: TestDir) -> None: - result = tdir.run("riot --file rf.py") +def test_bad_riotfile(tmp_path: pathlib.Path) -> None: + result = run("riot --file rf.py", tmp_path) assert ( result.stderr == """ @@ -60,14 +81,14 @@ def test_bad_riotfile(tdir: TestDir) -> None: ) assert result.returncode == 2 - tdir.mkfile( - "rf", + rf_path = tmp_path / "rf" + rf_path.write_text( """ from riot import Venv venv = Venv() """, ) - result = tdir.run("riot --file rf list") + result = run("riot --file rf list", cwd=tmp_path) assert ( result.stderr == """ @@ -77,14 +98,14 @@ def test_bad_riotfile(tdir: TestDir) -> None: ) assert result.returncode == 1 - tdir.mkfile( - "riotfile.py", + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv()typo1234 """, ) - result = tdir.run("riot --file riotfile.py list") + result = run("riot --file riotfile.py list", cwd=tmp_path) assert ( """ Failed to construct config file: @@ -101,8 +122,8 @@ def test_bad_riotfile(tdir: TestDir) -> None: assert result.returncode == 1 -def test_help(tdir: TestDir) -> None: - result = tdir.run("riot --help") +def test_help(tmp_path: pathlib.Path) -> None: + result = run("riot --help", cwd=tmp_path) assert ( result.stdout == """ @@ -125,15 +146,15 @@ def test_help(tdir: TestDir) -> None: assert result.returncode == 0 -def test_version(tdir: TestDir) -> None: - result = tdir.run("riot --version") +def test_version(tmp_path: pathlib.Path) -> None: + result = run("riot --version", cwd=tmp_path) assert result.stdout.startswith("riot, version ") assert result.stderr == "" assert result.returncode == 0 -def test_list_no_file_empty_file(tdir: TestDir) -> None: - result = tdir.run("riot list") +def test_list_no_file_empty_file(tmp_path: pathlib.Path) -> None: + result = run("riot list", cwd=tmp_path) assert ( result.stderr == """ @@ -145,21 +166,20 @@ def test_list_no_file_empty_file(tdir: TestDir) -> None: ) assert result.returncode == 2 - tdir.mkfile( - "riotfile.py", + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv """, ) - result = tdir.run("riot list") + result = run("riot list", cwd=tmp_path) assert result.stderr == "" assert result.stdout == "" assert result.returncode == 0 - -def test_list_configurations(tdir: TestDir) -> None: - tdir.mkfile( - "riotfile.py", +def test_list_configurations(tmp_path: pathlib.Path) -> None: + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -169,13 +189,12 @@ def test_list_configurations(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list") + result = run("riot list", cwd=tmp_path) assert result.stderr == "" assert result.stdout == "test Interpreter(_hint='3') \n" assert result.returncode == 0 - tdir.mkfile( - "riotfile.py", + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -188,7 +207,7 @@ def test_list_configurations(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list") + result = run("riot list", cwd=tmp_path) assert result.stderr == "" assert re.search( r""" @@ -199,8 +218,7 @@ def test_list_configurations(tdir: TestDir) -> None: ) assert result.returncode == 0 - tdir.mkfile( - "riotfile.py", + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -214,7 +232,7 @@ def test_list_configurations(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list") + result = run("riot list", cwd=tmp_path) assert result.stderr == "" assert re.search( r""" @@ -227,8 +245,7 @@ def test_list_configurations(tdir: TestDir) -> None: ) assert result.returncode == 0 - tdir.mkfile( - "riotfile.py", + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -254,7 +271,7 @@ def test_list_configurations(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list") + result = run("riot list", cwd=tmp_path) assert result.stderr == "" assert re.search( r""" @@ -272,9 +289,9 @@ def test_list_configurations(tdir: TestDir) -> None: assert result.returncode == 0 -def test_list_filter(tdir: TestDir) -> None: - tdir.mkfile( - "riotfile.py", +def test_list_filter(tmp_path: pathlib.Path) -> None: + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -284,13 +301,12 @@ def test_list_filter(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list test") + result = run("riot list test", cwd=tmp_path) assert result.stderr == "" assert re.search(r"test .*", result.stdout) assert result.returncode == 0 - tdir.mkfile( - "riotfile.py", + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -304,7 +320,7 @@ def test_list_filter(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list test") + result = run("riot list test", cwd=tmp_path) assert result.stderr == "" assert re.search( r""" @@ -317,8 +333,7 @@ def test_list_filter(tdir: TestDir) -> None: ) assert result.returncode == 0 - tdir.mkfile( - "riotfile.py", + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -344,7 +359,7 @@ def test_list_filter(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot list test2") + result = run("riot list test2", cwd=tmp_path) assert result.stderr == "" assert re.search( r""" @@ -358,9 +373,9 @@ def test_list_filter(tdir: TestDir) -> None: assert result.returncode == 0 -def test_run(tdir: TestDir) -> None: - tdir.mkfile( - "riotfile.py", +def test_run(tmp_path: pathlib.Path) -> None: + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -381,21 +396,21 @@ def test_run(tdir: TestDir) -> None: ) """, ) - tdir.mkfile( - "test_success.py", + success_path = tmp_path / "test_success.py" + success_path.write_text( """ def test_success(): assert 1 == 1 """, ) - tdir.mkfile( - "test_failure.py", + fail_path = tmp_path / "test_failure.py" + fail_path.write_text( """ def test_failure(): assert 1 == 0 """, ) - result = tdir.run("riot run -s pass") + result = run("riot run -s pass", cwd=tmp_path) assert re.search( r""" ============================= test session starts ============================== @@ -414,15 +429,14 @@ def test_failure(): assert result.stderr == "" assert result.returncode == 0 - result = tdir.run("riot run -s fail") + result = run("riot run -s fail", cwd=tmp_path) assert "✖️ fail: Interpreter(_hint='3') 'pytest'\n" in result.stdout assert result.stderr == "" assert result.returncode == 1 - -def test_run_cmdargs(tdir: TestDir) -> None: - tdir.mkfile( - "riotfile.py", +def test_run_cmdargs(tmp_path: pathlib.Path) -> None: + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -432,13 +446,12 @@ def test_run_cmdargs(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot run -s test_cmdargs -- -k filter") + result = run("riot run -s test_cmdargs -- -k filter", cwd=tmp_path) assert "cmdargs=-k filter" not in result.stdout assert result.stderr == "" assert result.returncode == 0 - tdir.mkfile( - "riotfile.py", + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -448,15 +461,15 @@ def test_run_cmdargs(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot run -s test_cmdargs -- -k filter") + result = run("riot run -s test_cmdargs -- -k filter", cwd=tmp_path) assert "cmdargs=-k filter" in result.stdout assert result.stderr == "" assert result.returncode == 0 -def test_dev_install_fail(tdir: TestDir) -> None: - tdir.mkfile( - "riotfile.py", +def test_dev_install_fail(tmp_path: pathlib.Path) -> None: + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -466,7 +479,7 @@ def test_dev_install_fail(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot run test") + result = run("riot run test", cwd=tmp_path) assert ( """ ERROR: File "setup.py" not found. Directory cannot be installed in editable mode: @@ -478,9 +491,9 @@ def test_dev_install_fail(tdir: TestDir) -> None: assert result.returncode == 1 -def test_bad_interpreter(tdir: TestDir) -> None: - tdir.mkfile( - "riotfile.py", +def test_bad_interpreter(tmp_path: pathlib.Path) -> None: + rf_path = tmp_path / "riotfile.py" + rf_path.write_text( """ from riot import Venv venv = Venv( @@ -490,7 +503,7 @@ def test_bad_interpreter(tdir: TestDir) -> None: ) """, ) - result = tdir.run("riot run -s -pDNE test") + result = run("riot run -s -pDNE test", cwd=tmp_path) assert ( """ FileNotFoundError: Python interpreter DNE not found From 0ad947de3b1b1deee93d128d66e98a40d9a41181 Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Fri, 21 May 2021 17:51:36 -0400 Subject: [PATCH 3/8] Fix linting and tests --- tests/test_integration.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index c2506edf..70212b0e 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -9,8 +9,6 @@ from typing import Sequence from typing import Union -import pytest - from riot.riot import _T_CompletedProcess @@ -194,7 +192,7 @@ def test_list_configurations(tmp_path: pathlib.Path) -> None: ) result = run("riot list", cwd=tmp_path) assert result.stderr == "" - assert result.stdout == "test Python Interpreter(_hint='3')\n" + assert result.stdout == "test Python Interpreter(_hint='3') \n" assert result.returncode == 0 rf_path.write_text( @@ -486,12 +484,7 @@ def test_dev_install_fail(tmp_path: pathlib.Path) -> None: """, ) result = run("riot run test", cwd=tmp_path) - assert ( - """ -ERROR: File "setup.py" not found. Directory cannot be installed in editable mode: -""".strip() - in result.stderr - ) + assert 'ERROR: File "setup.py"' in result.stderr assert "Dev install failed, aborting!" in result.stderr assert result.stdout == "" assert result.returncode == 1 From f322ec5d88dfd3596bed02c4b2af51b72043ad30 Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Fri, 21 May 2021 18:23:02 -0400 Subject: [PATCH 4/8] set shell=True when args is a str --- tests/test_integration.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 70212b0e..07904b5e 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -18,9 +18,6 @@ def run( args: Union[str, Sequence[str]], cwd: _T_Path, env: Optional[Dict[str, str]] = None ) -> _T_CompletedProcess: - if isinstance(args, str): - args = args.split(" ") - return subprocess.run( args, env=env, @@ -28,6 +25,7 @@ def run( stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, + shell=isinstance(args, str), ) From 7b2bb8860dd48f6800938bd54d96bac5dd984f5f Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Fri, 21 May 2021 18:47:06 -0400 Subject: [PATCH 5/8] Add tmp_run fixture Reduces the boilerplate of having to specify cwd every time when commands are 99% of the time run in the tmp_path. --- tests/test_integration.py | 84 ++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 07904b5e..2d0a07d7 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -9,6 +9,8 @@ from typing import Sequence from typing import Union +import pytest + from riot.riot import _T_CompletedProcess @@ -29,8 +31,24 @@ def run( ) -def test_no_riotfile(tmp_path: pathlib.Path) -> None: - result = run("riot", cwd=tmp_path) +@pytest.fixture +def tmp_run(tmp_path): + """Run a command by default in tmp_path.""" + + def _run( + args: Union[str, Sequence[str]], + cwd: Optional[_T_Path] = None, + env: Optional[Dict[str, str]] = None, + ): + if cwd is None: + cwd = tmp_path + return run(args, cwd, env) + + yield _run + + +def test_no_riotfile(tmp_path: pathlib.Path, tmp_run) -> None: + result = tmp_run("riot") assert ( result.stdout == """ @@ -52,7 +70,7 @@ def test_no_riotfile(tmp_path: pathlib.Path) -> None: assert result.stderr == "" assert result.returncode == 0 - result = run("riot list", cwd=tmp_path) + result = tmp_run("riot list") assert ( result.stderr == """ @@ -66,8 +84,8 @@ def test_no_riotfile(tmp_path: pathlib.Path) -> None: assert result.returncode == 2 -def test_bad_riotfile(tmp_path: pathlib.Path) -> None: - result = run("riot --file rf.py", tmp_path) +def test_bad_riotfile(tmp_path: pathlib.Path, tmp_run) -> None: + result = tmp_run("riot --file rf.py", tmp_path) assert ( result.stderr == """ @@ -86,7 +104,7 @@ def test_bad_riotfile(tmp_path: pathlib.Path) -> None: venv = Venv() """, ) - result = run("riot --file rf list", cwd=tmp_path) + result = tmp_run("riot --file rf list") assert ( result.stderr == """ @@ -103,7 +121,7 @@ def test_bad_riotfile(tmp_path: pathlib.Path) -> None: venv = Venv()typo1234 """, ) - result = run("riot --file riotfile.py list", cwd=tmp_path) + result = tmp_run("riot --file riotfile.py list") assert ( """ Failed to construct config file: @@ -120,8 +138,8 @@ def test_bad_riotfile(tmp_path: pathlib.Path) -> None: assert result.returncode == 1 -def test_help(tmp_path: pathlib.Path) -> None: - result = run("riot --help", cwd=tmp_path) +def test_help(tmp_path: pathlib.Path, tmp_run) -> None: + result = tmp_run("riot --help") assert ( result.stdout == """ @@ -144,15 +162,15 @@ def test_help(tmp_path: pathlib.Path) -> None: assert result.returncode == 0 -def test_version(tmp_path: pathlib.Path) -> None: - result = run("riot --version", cwd=tmp_path) +def test_version(tmp_path: pathlib.Path, tmp_run) -> None: + result = tmp_run("riot --version") assert result.stdout.startswith("riot, version ") assert result.stderr == "" assert result.returncode == 0 -def test_list_no_file_empty_file(tmp_path: pathlib.Path) -> None: - result = run("riot list", cwd=tmp_path) +def test_list_no_file_empty_file(tmp_path: pathlib.Path, tmp_run) -> None: + result = tmp_run("riot list") assert ( result.stderr == """ @@ -170,13 +188,13 @@ def test_list_no_file_empty_file(tmp_path: pathlib.Path) -> None: from riot import Venv """, ) - result = run("riot list", cwd=tmp_path) + result = tmp_run("riot list") assert result.stderr == "" assert result.stdout == "" assert result.returncode == 0 -def test_list_configurations(tmp_path: pathlib.Path) -> None: +def test_list_configurations(tmp_path: pathlib.Path, tmp_run) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -188,7 +206,7 @@ def test_list_configurations(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list", cwd=tmp_path) + result = tmp_run("riot list") assert result.stderr == "" assert result.stdout == "test Python Interpreter(_hint='3') \n" assert result.returncode == 0 @@ -206,7 +224,7 @@ def test_list_configurations(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list", cwd=tmp_path) + result = tmp_run("riot list") assert result.stderr == "" assert re.search( r""" @@ -231,7 +249,7 @@ def test_list_configurations(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list", cwd=tmp_path) + result = tmp_run("riot list") assert result.stderr == "" assert re.search( r""" @@ -270,7 +288,7 @@ def test_list_configurations(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list", cwd=tmp_path) + result = tmp_run("riot list") assert result.stderr == "" assert re.search( r""" @@ -288,7 +306,7 @@ def test_list_configurations(tmp_path: pathlib.Path) -> None: assert result.returncode == 0 -def test_list_filter(tmp_path: pathlib.Path) -> None: +def test_list_filter(tmp_path: pathlib.Path, tmp_run) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -300,7 +318,7 @@ def test_list_filter(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list test", cwd=tmp_path) + result = tmp_run("riot list test") assert result.stderr == "" assert re.search(r"test .*", result.stdout) assert result.returncode == 0 @@ -319,7 +337,7 @@ def test_list_filter(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list test", cwd=tmp_path) + result = tmp_run("riot list test") assert result.stderr == "" assert re.search( r""" @@ -358,7 +376,7 @@ def test_list_filter(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot list test2", cwd=tmp_path) + result = tmp_run("riot list test2") assert result.stderr == "" assert re.search( r""" @@ -372,7 +390,7 @@ def test_list_filter(tmp_path: pathlib.Path) -> None: assert result.returncode == 0 -def test_run(tmp_path: pathlib.Path) -> None: +def test_run(tmp_path: pathlib.Path, tmp_run) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -409,7 +427,7 @@ def test_failure(): assert 1 == 0 """, ) - result = run("riot run -s pass", cwd=tmp_path) + result = tmp_run("riot run -s pass") print(result.stdout) assert re.search( r""" @@ -430,13 +448,13 @@ def test_failure(): assert result.stderr == "" assert result.returncode == 0 - result = run("riot run -s fail", cwd=tmp_path) + result = tmp_run("riot run -s fail") assert "x fail: pythonInterpreter(_hint='3') 'pytest'\n" in result.stdout assert result.stderr == "" assert result.returncode == 1 -def test_run_cmdargs(tmp_path: pathlib.Path) -> None: +def test_run_cmdargs(tmp_path: pathlib.Path, tmp_run) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -448,7 +466,7 @@ def test_run_cmdargs(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot run -s test_cmdargs -- -k filter", cwd=tmp_path) + result = tmp_run("riot run -s test_cmdargs -- -k filter") assert "cmdargs=-k filter" not in result.stdout assert result.stderr == "" assert result.returncode == 0 @@ -463,13 +481,13 @@ def test_run_cmdargs(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot run -s test_cmdargs -- -k filter", cwd=tmp_path) + result = tmp_run("riot run -s test_cmdargs -- -k filter") assert "cmdargs=-k filter" in result.stdout assert result.stderr == "" assert result.returncode == 0 -def test_dev_install_fail(tmp_path: pathlib.Path) -> None: +def test_dev_install_fail(tmp_path: pathlib.Path, tmp_run) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -481,14 +499,14 @@ def test_dev_install_fail(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot run test", cwd=tmp_path) + result = tmp_run("riot run test") assert 'ERROR: File "setup.py"' in result.stderr assert "Dev install failed, aborting!" in result.stderr assert result.stdout == "" assert result.returncode == 1 -def test_bad_interpreter(tmp_path: pathlib.Path) -> None: +def test_bad_interpreter(tmp_path: pathlib.Path, tmp_run) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -500,7 +518,7 @@ def test_bad_interpreter(tmp_path: pathlib.Path) -> None: ) """, ) - result = run("riot run -s -pDNE test", cwd=tmp_path) + result = tmp_run("riot run -s -pDNE test") assert ( """ FileNotFoundError: Python interpreter DNE not found From e69237267bc9972bffed75e8f5de2a04cf66aedb Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Fri, 21 May 2021 19:01:59 -0400 Subject: [PATCH 6/8] Add typing for tmp_run --- tests/test_integration.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 2d0a07d7..eb676818 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -5,7 +5,9 @@ import sys from typing import Any from typing import Dict +from typing import Generator from typing import Optional +from typing import Protocol from typing import Sequence from typing import Union @@ -31,15 +33,25 @@ def run( ) +class _T_TmpRun(Protocol): + def __call__( + self, + args: Union[str, Sequence[str]], + cwd: Optional[_T_Path] = None, + env: Optional[Dict[str, str]] = None, + ) -> _T_CompletedProcess: + ... + + @pytest.fixture -def tmp_run(tmp_path): +def tmp_run(tmp_path: pathlib.Path) -> Generator[_T_TmpRun, None, None]: """Run a command by default in tmp_path.""" def _run( args: Union[str, Sequence[str]], cwd: Optional[_T_Path] = None, env: Optional[Dict[str, str]] = None, - ): + ) -> _T_CompletedProcess: if cwd is None: cwd = tmp_path return run(args, cwd, env) @@ -47,7 +59,7 @@ def _run( yield _run -def test_no_riotfile(tmp_path: pathlib.Path, tmp_run) -> None: +def test_no_riotfile(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: result = tmp_run("riot") assert ( result.stdout @@ -84,7 +96,7 @@ def test_no_riotfile(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 2 -def test_bad_riotfile(tmp_path: pathlib.Path, tmp_run) -> None: +def test_bad_riotfile(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: result = tmp_run("riot --file rf.py", tmp_path) assert ( result.stderr @@ -138,7 +150,7 @@ def test_bad_riotfile(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 1 -def test_help(tmp_path: pathlib.Path, tmp_run) -> None: +def test_help(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: result = tmp_run("riot --help") assert ( result.stdout @@ -162,14 +174,14 @@ def test_help(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 0 -def test_version(tmp_path: pathlib.Path, tmp_run) -> None: +def test_version(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: result = tmp_run("riot --version") assert result.stdout.startswith("riot, version ") assert result.stderr == "" assert result.returncode == 0 -def test_list_no_file_empty_file(tmp_path: pathlib.Path, tmp_run) -> None: +def test_list_no_file_empty_file(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: result = tmp_run("riot list") assert ( result.stderr @@ -194,7 +206,7 @@ def test_list_no_file_empty_file(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 0 -def test_list_configurations(tmp_path: pathlib.Path, tmp_run) -> None: +def test_list_configurations(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -306,7 +318,7 @@ def test_list_configurations(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 0 -def test_list_filter(tmp_path: pathlib.Path, tmp_run) -> None: +def test_list_filter(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -390,7 +402,7 @@ def test_list_filter(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 0 -def test_run(tmp_path: pathlib.Path, tmp_run) -> None: +def test_run(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -454,7 +466,7 @@ def test_failure(): assert result.returncode == 1 -def test_run_cmdargs(tmp_path: pathlib.Path, tmp_run) -> None: +def test_run_cmdargs(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -487,7 +499,7 @@ def test_run_cmdargs(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 0 -def test_dev_install_fail(tmp_path: pathlib.Path, tmp_run) -> None: +def test_dev_install_fail(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ @@ -506,7 +518,7 @@ def test_dev_install_fail(tmp_path: pathlib.Path, tmp_run) -> None: assert result.returncode == 1 -def test_bad_interpreter(tmp_path: pathlib.Path, tmp_run) -> None: +def test_bad_interpreter(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: rf_path = tmp_path / "riotfile.py" rf_path.write_text( """ From f723e9ceba7d597b73f72d3683e1379634eba9bf Mon Sep 17 00:00:00 2001 From: kyle-verhoog Date: Fri, 21 May 2021 21:30:44 -0400 Subject: [PATCH 7/8] Use typing_extensions for Protocol Protocol was only introduced in Python 3.8. typing_extensions makes it available for older Pythons. --- riotfile.py | 1 + tests/test_integration.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/riotfile.py b/riotfile.py index af332c91..9c997f9d 100644 --- a/riotfile.py +++ b/riotfile.py @@ -11,6 +11,7 @@ "pytest": latest, "pytest-cov": latest, "mock": latest, + "typing-extensions": latest, }, ), Venv( diff --git a/tests/test_integration.py b/tests/test_integration.py index eb676818..443bf84d 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -7,13 +7,12 @@ from typing import Dict from typing import Generator from typing import Optional -from typing import Protocol from typing import Sequence from typing import Union import pytest - from riot.riot import _T_CompletedProcess +from typing_extensions import Protocol _T_Path = Union[str, "os.PathLike[Any]"] From 8e5de95ebda8b38c977ef69913600104bb828268 Mon Sep 17 00:00:00 2001 From: Kyle Verhoog Date: Mon, 24 May 2021 10:27:14 -0400 Subject: [PATCH 8/8] Remove unused fixtures Co-authored-by: Brett Langdon --- tests/test_integration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 443bf84d..10c47222 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -149,7 +149,7 @@ def test_bad_riotfile(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: assert result.returncode == 1 -def test_help(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: +def test_help(tmp_run: _T_TmpRun) -> None: result = tmp_run("riot --help") assert ( result.stdout @@ -173,7 +173,7 @@ def test_help(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: assert result.returncode == 0 -def test_version(tmp_path: pathlib.Path, tmp_run: _T_TmpRun) -> None: +def test_version(tmp_run: _T_TmpRun) -> None: result = tmp_run("riot --version") assert result.stdout.startswith("riot, version ") assert result.stderr == ""