Improve version testing logic and add relevant tests

This commit is contained in:
fidgetingbits 2025-07-19 19:00:53 +08:00
parent add4b907c0
commit 445dc9ffc6
No known key found for this signature in database
14 changed files with 62 additions and 10 deletions

View file

View file

@ -0,0 +1,18 @@
from pathlib import Path
import pytest
pytest_plugins = [
"tests.direnv_project",
"tests.root",
]
@pytest.fixture(autouse=True)
def _cleanenv(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
# so direnv doesn't touch $HOME
monkeypatch.setenv("HOME", str(tmp_path / "home"))
# so direnv allow state writes under tmp HOME
monkeypatch.delenv("XDG_DATA_HOME", raising=False)
# so direnv does not pick up user customization
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)

View file

@ -0,0 +1,48 @@
import shutil
import textwrap
from collections.abc import Iterator
from dataclasses import dataclass
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
from .procs import run
@dataclass
class DirenvProject:
directory: Path
nix_direnv: Path
@property
def envrc(self) -> Path:
return self.directory / ".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.directory)
@pytest.fixture
def direnv_project(test_root: Path, project_root: Path) -> Iterator[DirenvProject]:
"""
Setups a direnv test project
"""
with TemporaryDirectory() as _dir:
directory = Path(_dir) / "proj"
shutil.copytree(test_root / "testenv", directory)
nix_direnv = project_root / "direnvrc"
c = DirenvProject(Path(directory), nix_direnv)
try:
yield c
finally:
pass

View file

@ -0,0 +1,27 @@
import logging
import shlex
import subprocess
from pathlib import Path
from typing import IO, Any
_FILE = None | int | IO[Any]
_DIR = None | Path | str
log = logging.getLogger(__name__)
def run(
cmd: list[str],
text: bool = True,
check: bool = True,
cwd: _DIR = None,
stderr: _FILE = None,
stdout: _FILE = None,
env: dict[str, str] | None = None,
) -> subprocess.CompletedProcess:
if cwd is not None:
log.debug(f"cd {cwd}")
log.debug(f"$ {shlex.join(cmd)}")
return subprocess.run(
cmd, text=text, check=check, cwd=cwd, stderr=stderr, stdout=stdout, env=env
)

View file

@ -0,0 +1,22 @@
from pathlib import Path
import pytest
TEST_ROOT = Path(__file__).parent.resolve()
PROJECT_ROOT = TEST_ROOT.parents[2]
@pytest.fixture
def test_root() -> Path:
"""
Root directory of the tests
"""
return TEST_ROOT
@pytest.fixture
def project_root() -> Path:
"""
Root directory of the project
"""
return PROJECT_ROOT

View file

@ -0,0 +1,98 @@
import logging
import subprocess
import sys
import unittest
import pytest
from .direnv_project import DirenvProject
from .procs import run
log = logging.getLogger(__name__)
def common_test(direnv_project: DirenvProject) -> None:
run(["nix-collect-garbage"])
testenv = str(direnv_project.directory)
out1 = run(
["direnv", "exec", testenv, "hello"],
stderr=subprocess.PIPE,
check=False,
cwd=direnv_project.directory,
)
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.directory,
)
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.directory)
out3 = run(
["direnv", "exec", testenv, "hello"],
stderr=subprocess.PIPE,
check=False,
cwd=direnv_project.directory,
)
sys.stderr.write(out3.stderr)
files = [
path
for path in (direnv_project.directory / ".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:
log.debug(files)
assert len(rcs) == 1
assert len(profiles) == 1
@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(
"use nix --argstr shellHook 'echo Executing hijacked shellHook.'",
strict_env=strict_env,
)
common_test_clean(direnv_project)
@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.directory / ".direnv/flake-inputs").iterdir())
# should only contain our flake-utils flake
if len(inputs) != 4:
run(["nix", "flake", "archive", "--json"], cwd=direnv_project.directory)
log.debug(inputs)
assert len(inputs) == 4
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()

View file

@ -0,0 +1,71 @@
import logging
import os
import shlex
import subprocess
import sys
import unittest
import pytest
from .direnv_project import DirenvProject
from .procs import run
log = logging.getLogger(__name__)
def direnv_exec(
direnv_project: DirenvProject, cmd: str, env: dict[str, str] | None = None
) -> None:
args = ["direnv", "exec", str(direnv_project.directory), "sh", "-c", cmd]
log.debug(f"$ {shlex.join(args)}")
out = run(
args,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
check=False,
cwd=direnv_project.directory,
env=env,
)
sys.stdout.write(out.stdout)
sys.stderr.write(out.stderr)
assert out.returncode == 0
assert out.stdout == "OK\n"
assert "Renewed cache" in out.stderr
@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.directory,
)
assert out.returncode == 0
assert 'Loaded watch: "."' not in out.stdout
if __name__ == "__main__":
unittest.main()

61
tests/python/tests/testenv/flake.lock generated Normal file
View 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": 1701401302,
"narHash": "sha256-kfCOHzgtmHcgJwH7uagk8B+K1Qz58rN79eTLe55eGqA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "69a165d0fd2b08a78dbd2c98f6f860ceb2bbcd40",
"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
}

View file

@ -0,0 +1,12 @@
{
description = "A very basic flake";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
# deadnix: skip
outputs =
{ nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system: {
devShell = import ./shell.nix { pkgs = nixpkgs.legacyPackages.${system}; };
});
}

View file

@ -0,0 +1,17 @@
{
pkgs ? import (builtins.getFlake (toString ./.)).inputs.nixpkgs { },
someArg ? null,
shellHook ? ''
echo "Executing shellHook."
'',
}:
pkgs.mkShellNoCC {
inherit shellHook;
nativeBuildInputs = [ pkgs.hello ];
SHOULD_BE_SET = someArg;
passthru = {
subshell = pkgs.mkShellNoCC { THIS_IS_A_SUBSHELL = "OK"; };
};
}