From 748988f4b96c0646fada183e3b5bd6e51d53bf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 1 Jun 2022 08:17:29 +0200 Subject: [PATCH] rewrite tests using pytest this allows us to use fixtures that are more flexible --- .gitignore | 5 ++ run-tests.nix | 2 +- setup.cfg | 23 +++++++++ shell.nix | 5 +- tests/conftest.py | 6 +++ tests/direnv_project.py | 47 +++++++++++++++++ tests/procs.py | 25 +++++++++ tests/root.py | 24 +++++++++ tests/test.py | 111 ---------------------------------------- tests/test_gc.py | 60 ++++++++++++++++++++++ 10 files changed, 195 insertions(+), 113 deletions(-) create mode 100644 setup.cfg create mode 100644 tests/conftest.py create mode 100644 tests/direnv_project.py create mode 100644 tests/procs.py create mode 100644 tests/root.py delete mode 100644 tests/test.py create mode 100644 tests/test_gc.py diff --git a/.gitignore b/.gitignore index 375afd0..b0b42dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .direnv/ /template/flake.lock + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class diff --git a/run-tests.nix b/run-tests.nix index 5ba3e20..f6f33ee 100644 --- a/run-tests.nix +++ b/run-tests.nix @@ -18,5 +18,5 @@ writeScript "run-tests" '' ${mypy}/bin/mypy tests echo -e "\x1b[32m## run unittest\x1b[0m" - ${python3.interpreter} -m unittest discover tests + ${python3.pkgs.pytest}/bin/pytest . '' diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1294fa5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,23 @@ +[wheel] +universal = 1 + +[pycodestyle] +max-line-length = 88 +ignore = E501,E741,W503 + +[flake8] +max-line-length = 88 +ignore = E501,E741,W503 +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist + +[mypy] +warn_redundant_casts = true +disallow_untyped_calls = true +disallow_untyped_defs = true +no_implicit_optional = true + +[mypy-pytest.*] +ignore_missing_imports = True + +[isort] +profile = black diff --git a/shell.nix b/shell.nix index 89ef822..10e427a 100644 --- a/shell.nix +++ b/shell.nix @@ -3,7 +3,10 @@ with pkgs; mkShell { nativeBuildInputs = [ - python3 + python3.pkgs.pytest + python3.pkgs.mypy + python3.pkgs.black + python3.pkgs.flake8 shellcheck direnv ]; diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..43c8c83 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +pytest_plugins = [ + "direnv_project", + "root", +] diff --git a/tests/direnv_project.py b/tests/direnv_project.py new file mode 100644 index 0000000..b2b5333 --- /dev/null +++ b/tests/direnv_project.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +from dataclasses import dataclass +import shutil +from tempfile import TemporaryDirectory +from typing import Iterator +from pathlib import Path + +import pytest + +from procs import run + + +@dataclass +class DirenvProject: + dir: Path + nix_direnv: Path + + @property + def envrc(self) -> Path: + return self.dir / ".envrc" + + def setup_envrc(self, content: str) -> None: + self.envrc.write_text( + f""" +source {self.nix_direnv} +{content} + """ + ) + run(["direnv", "allow"], cwd=self.dir) + + +@pytest.fixture +def direnv_project(test_root: Path, project_root: Path) -> Iterator[DirenvProject]: + """ + Setups a direnv test project + """ + with TemporaryDirectory() as _dir: + dir = Path(_dir) / "proj" + shutil.copytree(test_root / "testenv", dir) + nix_direnv = project_root / "direnvrc" + + c = DirenvProject(Path(dir), nix_direnv) + try: + yield c + finally: + pass diff --git a/tests/procs.py b/tests/procs.py new file mode 100644 index 0000000..81cf150 --- /dev/null +++ b/tests/procs.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +import subprocess +from typing import List, Union, IO, Any +from pathlib import Path + + +_FILE = Union[None, int, IO[Any]] +_DIR = Union[None, Path, str] + + +def run( + cmd: List[str], + text: bool = True, + check: bool = True, + cwd: _DIR = None, + stderr: _FILE = None, + stdout: _FILE = None, +) -> subprocess.CompletedProcess: + if cwd is not None: + print(f"cd {cwd}") + print("$ " + " ".join(cmd)) + return subprocess.run( + cmd, text=text, check=check, cwd=cwd, stderr=stderr, stdout=stdout + ) diff --git a/tests/root.py b/tests/root.py new file mode 100644 index 0000000..087c307 --- /dev/null +++ b/tests/root.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +import pytest + +TEST_ROOT = Path(__file__).parent.resolve() +PROJECT_ROOT = TEST_ROOT.parent + + +@pytest.fixture +def test_root() -> Path: + """ + Root directory of the tests + """ + return TEST_ROOT + + +@pytest.fixture +def project_root() -> Path: + """ + Root directory of the tests + """ + return PROJECT_ROOT diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index 724e8bb..0000000 --- a/tests/test.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python2 - -from tempfile import TemporaryDirectory -import os -import sys -import subprocess -from pathlib import Path -import shutil -import unittest -from typing import List - - -TEST_ROOT = Path(__file__).resolve().parent -RENEWED_MESSAGE = "renewed cache" -CACHED_MESSAGE = "using cached dev shell" - - -def run(cmd: List[str], **kwargs) -> subprocess.CompletedProcess: - print("$ " + " ".join(cmd)) - return subprocess.run(cmd, **kwargs) - - -class TestBaseNamespace: - """Nested so test discovery doesn't run the base class tests directly.""" - - class TestBase(unittest.TestCase): - env: dict - dir: TemporaryDirectory - testenv: Path - direnvrc: str - direnvrc_command: str - out1: subprocess.CompletedProcess - out2: subprocess.CompletedProcess - - @classmethod - def setUpClass(cls) -> None: - cls.env = os.environ.copy() - cls.dir = TemporaryDirectory() - cls.env["HOME"] = str(cls.dir.name) - cls.testenv = Path(cls.dir.name).joinpath("testenv") - shutil.copytree(TEST_ROOT.joinpath("testenv"), cls.testenv) - cls.direnvrc = str(TEST_ROOT.parent.joinpath("direnvrc")) - - with open(cls.testenv.joinpath(".envrc"), "w") as f: - f.write(f"source {cls.direnvrc}\n{cls.direnvrc_command}") - - run(["direnv", "allow"], cwd=str(cls.testenv), env=cls.env, check=True) - - run(["nix-collect-garbage"], check=True) - - cls.out1 = run( - ["direnv", "exec", str(cls.testenv), "hello"], - env=cls.env, - stderr=subprocess.PIPE, - text=True, - ) - sys.stderr.write(cls.out1.stderr) - - run(["nix-collect-garbage"], check=True) - - cls.out2 = run( - ["direnv", "exec", str(cls.testenv), "hello"], - env=cls.env, - stderr=subprocess.PIPE, - text=True, - ) - sys.stderr.write(cls.out2.stderr) - - @classmethod - def tearDownClass(cls) -> None: - cls.dir.cleanup() - - def test_fresh_shell_message(self) -> None: - self.assertIn(RENEWED_MESSAGE, self.out1.stderr) - - def test_fresh_shell_shellHook_gets_executed(self) -> None: - self.assertIn("Executing shellHook.", self.out1.stderr) - - def test_fresh_shell_returncode(self) -> None: - self.assertEqual(self.out1.returncode, 0) - - def test_cached_shell_message(self) -> None: - self.assertIn(CACHED_MESSAGE, self.out2.stderr) - - def test_cached_shell_shellHook_gets_executed(self) -> None: - self.assertIn("Executing shellHook.", self.out2.stderr) - - def test_cached_shell_returncode(self) -> None: - self.assertEqual(self.out2.returncode, 0) - - -class NixShellTest(TestBaseNamespace.TestBase): - direnvrc_command = "use nix" - - -class FlakeTest(TestBaseNamespace.TestBase): - direnvrc_command = "use flake" - - def test_gcroot_symlink_created_and_valid(self) -> None: - inputs = list(self.testenv.joinpath(".direnv/flake-inputs").iterdir()) - # should only contain our flake-utils flake - if len(inputs) != 3: - subprocess.run(["nix", "flake", "archive", "--json"], cwd=self.testenv) - print(inputs) - self.assertEqual(len(inputs), 3) - for symlink in inputs: - self.assertTrue(symlink.is_dir()) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_gc.py b/tests/test_gc.py new file mode 100644 index 0000000..0c0b45f --- /dev/null +++ b/tests/test_gc.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python2 + +import sys +import subprocess +import unittest + +from procs import run +from direnv_project import DirenvProject + + +def common_test(direnv_project: DirenvProject) -> None: + run(["nix-collect-garbage"]) + + testenv = str(direnv_project.dir) + + out1 = run( + ["direnv", "exec", testenv, "hello"], + stderr=subprocess.PIPE, + check=False, + cwd=direnv_project.dir, + ) + sys.stderr.write(out1.stderr) + assert out1.returncode == 0 + assert "renewed cache" in out1.stderr + assert "Executing shellHook." in out1.stderr + + run(["nix-collect-garbage"]) + + out2 = run( + ["direnv", "exec", testenv, "hello"], + stderr=subprocess.PIPE, + check=False, + cwd=direnv_project.dir, + ) + sys.stderr.write(out2.stderr) + assert out1.returncode == 0 + assert "using cached dev shell" in out2.stderr + assert "Executing shellHook." in out2.stderr + + +def test_use_nix(direnv_project: DirenvProject) -> None: + direnv_project.setup_envrc("use nix") + common_test(direnv_project) + + +def test_use_flake(direnv_project: DirenvProject) -> None: + direnv_project.setup_envrc("use flake") + common_test(direnv_project) + inputs = list((direnv_project.dir / ".direnv/flake-inputs").iterdir()) + # should only contain our flake-utils flake + if len(inputs) != 3: + run(["nix", "flake", "archive", "--json"], cwd=direnv_project.dir) + print(inputs) + assert len(inputs) == 3 + for symlink in inputs: + assert symlink.is_dir() + + +if __name__ == "__main__": + unittest.main()