mirror of
https://github.com/nix-community/nix-direnv.git
synced 2025-11-08 19:46:11 +01:00
refactor tests
* factor out duplicated code * make tests an importable package * idiomatic pytest usage * do not touch files outside of the tmp test tree and do not depend on external state (except for /nix/store ☹) * run coverage to root out dead code
This commit is contained in:
parent
c3c23453e4
commit
0d145c01d5
12 changed files with 252 additions and 240 deletions
|
|
@ -4,6 +4,11 @@ line-length = 88
|
|||
select = ["E", "F", "I"]
|
||||
ignore = [ "E501" ]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"pytest.skip",
|
||||
]
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
warn_redundant_casts = true
|
||||
|
|
|
|||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
105
tests/case.py
Normal file
105
tests/case.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
CMD = Path | str
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TEST_ROOT = Path(__file__).parent
|
||||
|
||||
PROJECT_ROOT = TEST_ROOT.parent
|
||||
|
||||
NIX_DIRENV = PROJECT_ROOT / "direnvrc"
|
||||
|
||||
|
||||
class TestCase:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup(self, tmp_path: Path) -> None:
|
||||
self.root = tmp_path
|
||||
log.debug(f"path: {self.root}")
|
||||
|
||||
@functools.cached_property
|
||||
def home(self) -> Path:
|
||||
return self.root / "home"
|
||||
|
||||
@functools.cached_property
|
||||
def path(self) -> Path:
|
||||
path = self.root / "cwd"
|
||||
shutil.copytree(TEST_ROOT / "testenv", path)
|
||||
return path
|
||||
|
||||
def _log_output(
|
||||
self,
|
||||
result: subprocess.CompletedProcess | subprocess.CalledProcessError,
|
||||
errlevel: str = "debug",
|
||||
) -> None:
|
||||
for out in ("stdout", "stderr"):
|
||||
text = getattr(result, out).strip()
|
||||
setattr(result, out, text)
|
||||
if text:
|
||||
getattr(log, errlevel if out == "stderr" else "debug")(
|
||||
f"{out[3:]}:{os.linesep if os.linesep in text else ' '}{text}"
|
||||
)
|
||||
|
||||
def run(
|
||||
self,
|
||||
*cmd: CMD,
|
||||
**env: str,
|
||||
) -> subprocess.CompletedProcess:
|
||||
env = dict(PATH=os.environ["PATH"], HOME=str(self.home)) | env
|
||||
command = list(map(str, cmd))
|
||||
log.debug(f"$ {subprocess.list2cmdline(command)}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
text=True,
|
||||
cwd=self.path,
|
||||
env=env,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc: # pragma: no cover
|
||||
self._log_output(exc, errlevel="error")
|
||||
raise
|
||||
self._log_output(result)
|
||||
return result
|
||||
|
||||
def nix_run(self, *cmd: CMD, **env: str) -> subprocess.CompletedProcess:
|
||||
return self.run("nix", *cmd, **env)
|
||||
|
||||
def direnv_run(self, *cmd: CMD, **env: str) -> subprocess.CompletedProcess:
|
||||
return self.run("direnv", *cmd, **env)
|
||||
|
||||
def direnv_exec(self, *cmd: CMD, **env: str) -> subprocess.CompletedProcess:
|
||||
return self.run("direnv", "exec", ".", *cmd, **env)
|
||||
|
||||
def direnv_var(self, name: str, **env: str) -> subprocess.CompletedProcess:
|
||||
return self.direnv_exec("sh", "-c", f"echo -n ${name}", **env)
|
||||
|
||||
def setup_envrc(
|
||||
self, content: str, strict_env: bool, **env: str
|
||||
) -> subprocess.CompletedProcess:
|
||||
text = textwrap.dedent(
|
||||
f"""
|
||||
{'strict_env' if strict_env else ''}
|
||||
source {NIX_DIRENV}
|
||||
{content}
|
||||
""",
|
||||
)
|
||||
(self.path / ".envrc").write_text(text.strip())
|
||||
return self.direnv_run("allow", **env)
|
||||
|
||||
def assert_direnv_var(self, name: str, **env: str) -> subprocess.CompletedProcess:
|
||||
result = self.direnv_var(name, **env)
|
||||
assert result.stdout == "OK"
|
||||
assert "renewed cache" in result.stderr
|
||||
return result
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pytest_plugins = [
|
||||
"direnv_project",
|
||||
"root",
|
||||
]
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import shutil
|
||||
import textwrap
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Iterator
|
||||
|
||||
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, strict_env: bool) -> None:
|
||||
text = textwrap.dedent(
|
||||
f"""
|
||||
{'strict_env' if strict_env else ''}
|
||||
source {self.nix_direnv}
|
||||
{content}
|
||||
"""
|
||||
)
|
||||
self.envrc.write_text(text)
|
||||
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)
|
||||
shutil.copyfile(project_root / "flake.nix", dir / "flake.nix")
|
||||
shutil.copyfile(project_root / "flake.lock", dir / "flake.lock")
|
||||
nix_direnv = project_root / "direnvrc"
|
||||
|
||||
c = DirenvProject(Path(dir), nix_direnv)
|
||||
try:
|
||||
yield c
|
||||
finally:
|
||||
pass
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, List, Optional, Union
|
||||
|
||||
_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,
|
||||
env: Optional[dict[str, str]] = 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, env=env
|
||||
)
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
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
|
||||
117
tests/test_gc.py
117
tests/test_gc.py
|
|
@ -1,92 +1,65 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from direnv_project import DirenvProject
|
||||
from procs import run
|
||||
|
||||
from .case import TestCase
|
||||
|
||||
|
||||
def common_test(direnv_project: DirenvProject) -> None:
|
||||
run(["nix-collect-garbage"])
|
||||
class TestGc(TestCase):
|
||||
def common_test(self) -> None:
|
||||
result = self.direnv_exec("hello")
|
||||
assert "renewed cache" in result.stderr
|
||||
assert "Executing shellHook." in result.stderr
|
||||
|
||||
testenv = str(direnv_project.dir)
|
||||
self.nix_run("store", "gc")
|
||||
|
||||
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
|
||||
result = self.direnv_exec("hello")
|
||||
assert "using cached dev shell" in result.stderr
|
||||
assert "Executing shellHook." in result.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 out2.returncode == 0
|
||||
assert "using cached dev shell" in out2.stderr
|
||||
assert "Executing shellHook." in out2.stderr
|
||||
|
||||
|
||||
def common_test_clean(direnv_project: DirenvProject) -> None:
|
||||
testenv = str(direnv_project.dir)
|
||||
|
||||
out3 = run(
|
||||
["direnv", "exec", testenv, "hello"],
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
cwd=direnv_project.dir,
|
||||
)
|
||||
sys.stderr.write(out3.stderr)
|
||||
|
||||
files = [
|
||||
path for path in (direnv_project.dir / ".direnv").iterdir() if path.is_file()
|
||||
]
|
||||
def common_test_clean(self) -> None:
|
||||
self.direnv_exec("hello")
|
||||
files = [path for path in (self.path / ".direnv").iterdir() if path.is_file()]
|
||||
rcs = [f for f in files if f.match("*.rc")]
|
||||
profiles = [f for f in files if not f.match("*.rc")]
|
||||
if len(rcs) != 1 or len(profiles) != 1:
|
||||
print(files)
|
||||
assert len(rcs) == 1
|
||||
assert len(profiles) == 1
|
||||
assert len(rcs) == 1, files
|
||||
assert len(profiles) == 1, files
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_use_nix(self, strict_env: bool) -> None:
|
||||
self.setup_envrc("use nix", strict_env=strict_env)
|
||||
self.common_test()
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_use_nix(direnv_project: DirenvProject, strict_env: bool) -> None:
|
||||
direnv_project.setup_envrc("use nix", strict_env=strict_env)
|
||||
common_test(direnv_project)
|
||||
|
||||
direnv_project.setup_envrc(
|
||||
self.setup_envrc(
|
||||
"use nix --argstr shellHook 'echo Executing hijacked shellHook.'",
|
||||
strict_env=strict_env,
|
||||
)
|
||||
common_test_clean(direnv_project)
|
||||
self.common_test_clean()
|
||||
|
||||
def _parse_inputs(self, inputs: dict) -> list:
|
||||
paths = [inputs["path"]]
|
||||
for xinput in inputs["inputs"].values():
|
||||
paths.extend(self._parse_inputs(xinput))
|
||||
return paths
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_use_flake(direnv_project: DirenvProject, strict_env: bool) -> None:
|
||||
direnv_project.setup_envrc("use flake", strict_env=strict_env)
|
||||
common_test(direnv_project)
|
||||
inputs = list((direnv_project.dir / ".direnv/flake-inputs").iterdir())
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_use_flake(self, strict_env: bool) -> None:
|
||||
self.setup_envrc("use flake", strict_env=strict_env)
|
||||
self.common_test()
|
||||
inputs = list((self.path / ".direnv/flake-inputs").iterdir())
|
||||
flake_inputs = self._parse_inputs(
|
||||
json.loads(
|
||||
self.nix_run(
|
||||
"flake", "archive", "--json", "--no-write-lock-file"
|
||||
).stdout
|
||||
)
|
||||
)
|
||||
# should only contain our flake-utils flake
|
||||
if len(inputs) != 4:
|
||||
run(["nix", "flake", "archive", "--json"], cwd=direnv_project.dir)
|
||||
print(inputs)
|
||||
assert len(inputs) == 4
|
||||
assert len(inputs) == len(flake_inputs)
|
||||
for symlink in inputs:
|
||||
assert symlink.is_dir()
|
||||
|
||||
direnv_project.setup_envrc("use flake --impure", strict_env=strict_env)
|
||||
common_test_clean(direnv_project)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
self.setup_envrc("use flake --impure", strict_env=strict_env)
|
||||
self.common_test_clean()
|
||||
|
|
|
|||
|
|
@ -1,67 +1,35 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from direnv_project import DirenvProject
|
||||
from procs import run
|
||||
|
||||
from .case import TestCase
|
||||
|
||||
|
||||
def direnv_exec(
|
||||
direnv_project: DirenvProject, cmd: str, env: Optional[dict[str, str]] = None
|
||||
) -> None:
|
||||
args = ["direnv", "exec", str(direnv_project.dir), "sh", "-c", cmd]
|
||||
print("$ " + " ".join(args))
|
||||
out = run(
|
||||
args,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
check=False,
|
||||
cwd=direnv_project.dir,
|
||||
env=env,
|
||||
class TestUseNix(TestCase):
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_attrs(self, strict_env: bool) -> None:
|
||||
self.setup_envrc("use nix -A subshell", strict_env=strict_env)
|
||||
self.assert_direnv_var("THIS_IS_A_SUBSHELL")
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_with_nix_path(self, strict_env: bool) -> None:
|
||||
if (nix_path := os.environ.get("NIX_PATH")) is None:
|
||||
pytest.skip("no parent NIX_PATH")
|
||||
else:
|
||||
self.setup_envrc(
|
||||
"use nix --argstr someArg OK", strict_env=strict_env, NIX_PATH=nix_path
|
||||
)
|
||||
sys.stdout.write(out.stdout)
|
||||
sys.stderr.write(out.stderr)
|
||||
assert out.returncode == 0
|
||||
assert "OK\n" == out.stdout
|
||||
assert "renewed cache" in out.stderr
|
||||
self.assert_direnv_var("SHOULD_BE_SET", NIX_PATH=nix_path)
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_args(self, strict_env: bool) -> None:
|
||||
self.setup_envrc("use nix --argstr someArg OK", strict_env=strict_env)
|
||||
self.assert_direnv_var("SHOULD_BE_SET")
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_attrs(direnv_project: DirenvProject, strict_env: bool) -> None:
|
||||
direnv_project.setup_envrc("use nix -A subshell", strict_env=strict_env)
|
||||
direnv_exec(direnv_project, "echo $THIS_IS_A_SUBSHELL")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_no_nix_path(direnv_project: DirenvProject, strict_env: bool) -> None:
|
||||
direnv_project.setup_envrc("use nix --argstr someArg OK", strict_env=strict_env)
|
||||
env = os.environ.copy()
|
||||
del env["NIX_PATH"]
|
||||
direnv_exec(direnv_project, "echo $SHOULD_BE_SET", env=env)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_args(direnv_project: DirenvProject, strict_env: bool) -> None:
|
||||
direnv_project.setup_envrc("use nix --argstr someArg OK", strict_env=strict_env)
|
||||
direnv_exec(direnv_project, "echo $SHOULD_BE_SET")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_no_files(direnv_project: DirenvProject, strict_env: bool) -> None:
|
||||
direnv_project.setup_envrc("use nix -p hello", strict_env=strict_env)
|
||||
out = run(
|
||||
["direnv", "status"],
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
check=False,
|
||||
cwd=direnv_project.dir,
|
||||
)
|
||||
assert out.returncode == 0
|
||||
assert 'Loaded watch: "."' not in out.stdout
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@pytest.mark.parametrize("strict_env", [False, True])
|
||||
def test_no_files(self, strict_env: bool) -> None:
|
||||
self.setup_envrc("use nix -p hello", strict_env=strict_env)
|
||||
result = self.direnv_run("status")
|
||||
assert 'Loaded watch: "."' not in result.stdout
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
source ../../direnvrc
|
||||
use nix
|
||||
61
tests/testenv/flake.lock
generated
Normal file
61
tests/testenv/flake.lock
generated
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1700856099,
|
||||
"narHash": "sha256-RnEA7iJ36Ay9jI0WwP+/y4zjEhmeN6Cjs9VOFBH7eVQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0bd59c54ef06bc34eca01e37d689f5e46b3fe2f1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
description = "A very basic flake";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue