rewrite tests using pytest

this allows us to use fixtures that are more flexible
This commit is contained in:
Jörg Thalheim 2022-06-01 08:17:29 +02:00
parent 6dbc942fea
commit 748988f4b9
No known key found for this signature in database
10 changed files with 195 additions and 113 deletions

5
.gitignore vendored
View file

@ -1,2 +1,7 @@
.direnv/
/template/flake.lock
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

View file

@ -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 .
''

23
setup.cfg Normal file
View file

@ -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

View file

@ -3,7 +3,10 @@
with pkgs;
mkShell {
nativeBuildInputs = [
python3
python3.pkgs.pytest
python3.pkgs.mypy
python3.pkgs.black
python3.pkgs.flake8
shellcheck
direnv
];

6
tests/conftest.py Normal file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env python3
pytest_plugins = [
"direnv_project",
"root",
]

47
tests/direnv_project.py Normal file
View file

@ -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

25
tests/procs.py Normal file
View file

@ -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
)

24
tests/root.py Normal file
View file

@ -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

View file

@ -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()

60
tests/test_gc.py Normal file
View file

@ -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()