From 445dc9ffc6adf156992b78a1a020ab14ef8ab00c Mon Sep 17 00:00:00 2001 From: fidgetingbits Date: Sat, 19 Jul 2025 19:00:53 +0800 Subject: [PATCH 1/2] Improve version testing logic and add relevant tests --- direnvrc | 6 ++-- flake.nix | 12 ++++++-- test-runner.nix | 18 ++++++++++-- tests/bash/test_versions.bats | 32 +++++++++++++++++++++ tests/{ => python/tests}/__init__.py | 0 tests/{ => python/tests}/conftest.py | 0 tests/{ => python/tests}/direnv_project.py | 0 tests/{ => python/tests}/procs.py | 0 tests/{ => python/tests}/root.py | 4 +-- tests/{ => python/tests}/test_gc.py | 0 tests/{ => python/tests}/test_use_nix.py | 0 tests/{ => python/tests}/testenv/flake.lock | 0 tests/{ => python/tests}/testenv/flake.nix | 0 tests/{ => python/tests}/testenv/shell.nix | 0 14 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 tests/bash/test_versions.bats rename tests/{ => python/tests}/__init__.py (100%) rename tests/{ => python/tests}/conftest.py (100%) rename tests/{ => python/tests}/direnv_project.py (100%) rename tests/{ => python/tests}/procs.py (100%) rename tests/{ => python/tests}/root.py (80%) rename tests/{ => python/tests}/test_gc.py (100%) rename tests/{ => python/tests}/test_use_nix.py (100%) rename tests/{ => python/tests}/testenv/flake.lock (100%) rename tests/{ => python/tests}/testenv/flake.nix (100%) rename tests/{ => python/tests}/testenv/shell.nix (100%) diff --git a/direnvrc b/direnvrc index 4dd7a32..cad58f7 100644 --- a/direnvrc +++ b/direnvrc @@ -37,10 +37,10 @@ _nix() { } _require_version() { - local cmd=$1 version=$2 required=$3 + local cmd=$1 raw_version=$2 version=${2%%[^0-9.]*} required=$3 if ! printf "%s\n" "$required" "$version" | LC_ALL=C sort -c -V 2>/dev/null; then _nix_direnv_error \ - "minimum required $(basename "$cmd") version is $required (installed: $version)" + "minimum required $(basename "$cmd") version is $required (installed: $raw_version)" return 1 fi } @@ -52,7 +52,7 @@ _require_cmd_version() { return 1 fi version=$($cmd --version) - [[ $version =~ ([0-9]+\.[0-9]+\.[0-9]+) ]] + [[ $version =~ ([0-9]+\.[0-9]+(\.[0-9]+)?) ]] _require_version "$cmd" "${BASH_REMATCH[1]}" "$required" } diff --git a/flake.nix b/flake.nix index 96f00b4..8e844d5 100644 --- a/flake.nix +++ b/flake.nix @@ -34,11 +34,17 @@ ... }: { - packages = { + packages = rec { nix-direnv = pkgs.callPackage ./default.nix { }; default = config.packages.nix-direnv; - test-runner-stable = pkgs.callPackage ./test-runner.nix { nixVersion = "stable"; }; - test-runner-latest = pkgs.callPackage ./test-runner.nix { nixVersion = "latest"; }; + test-runner-stable = pkgs.callPackage ./test-runner.nix { + nixVersion = "stable"; + inherit nix-direnv; + }; + test-runner-latest = pkgs.callPackage ./test-runner.nix { + nixVersion = "latest"; + inherit nix-direnv; + }; }; devShells.default = pkgs.callPackage ./shell.nix { diff --git a/test-runner.nix b/test-runner.nix index 5ff22da..8de1ab9 100644 --- a/test-runner.nix +++ b/test-runner.nix @@ -5,9 +5,18 @@ lib, coreutils, gnugrep, + bats, nixVersions, nixVersion, + nix-direnv, + fetchurl, }: +let + direnv-stdlib = fetchurl { + url = "https://raw.githubusercontent.com/direnv/direnv/refs/tags/v2.37.0/stdlib.sh"; + hash = "sha256-MMM04OXhqS/rRSuv8uh7CD70Z7CaGT63EtL/3LC08qM="; + }; +in writeShellScriptBin "test-runner-${nixVersion}" '' set -e export PATH=${ @@ -18,7 +27,12 @@ writeShellScriptBin "test-runner-${nixVersion}" '' gnugrep ] } + export DIRENV_STDLIB=${direnv-stdlib} + export DIRENVRC="${nix-direnv}/share/nix-direnv/direnvrc" - echo run unittest - ${lib.getExe' python3.pkgs.pytest "pytest"} . + echo run python unittest + ${lib.getExe' python3.pkgs.pytest "pytest"} tests/python/ + + echo run bash unittest + ${lib.getExe' bats "bats"} -x --verbose-run tests/bash/ '' diff --git a/tests/bash/test_versions.bats b/tests/bash/test_versions.bats new file mode 100644 index 0000000..f679479 --- /dev/null +++ b/tests/bash/test_versions.bats @@ -0,0 +1,32 @@ +load "$DIRENV_STDLIB" +load "$DIRENVRC" + +@test "test _require_version with valid versions" { + # args: cmd version minimum_required + run _require_version "test-cmd" "2.5" "2.4" + [ "$status" -eq 0 ] + run _require_version "test-cmd" "2.5" "2.4.1" + [ "$status" -eq 0 ] + run _require_version "test-cmd" "2.4.1" "2.4" + [ "$status" -eq 0 ] + run _require_version "test-cmd" "2.4" "2.4.1" + [ "$status" -eq 1 ] + run _require_version "test-cmd" "2.31pre20250712_b1245123" "2.4" + [ "$status" -eq 0 ] +} + + +test_cmd1() { + echo "1.2" +} + +test_cmd2() { + echo "1.2.3" +} + +@test "test _require_cmd_version with valid versions" { + run _require_cmd_version "test_cmd1" "1.1" + [ "$status" -eq 0 ] + run _require_cmd_version "test_cmd2" "1.1.1" + [ "$status" -eq 0 ] +} diff --git a/tests/__init__.py b/tests/python/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to tests/python/tests/__init__.py diff --git a/tests/conftest.py b/tests/python/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to tests/python/tests/conftest.py diff --git a/tests/direnv_project.py b/tests/python/tests/direnv_project.py similarity index 100% rename from tests/direnv_project.py rename to tests/python/tests/direnv_project.py diff --git a/tests/procs.py b/tests/python/tests/procs.py similarity index 100% rename from tests/procs.py rename to tests/python/tests/procs.py diff --git a/tests/root.py b/tests/python/tests/root.py similarity index 80% rename from tests/root.py rename to tests/python/tests/root.py index b3f0914..6c71e08 100644 --- a/tests/root.py +++ b/tests/python/tests/root.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest TEST_ROOT = Path(__file__).parent.resolve() -PROJECT_ROOT = TEST_ROOT.parent +PROJECT_ROOT = TEST_ROOT.parents[2] @pytest.fixture @@ -17,6 +17,6 @@ def test_root() -> Path: @pytest.fixture def project_root() -> Path: """ - Root directory of the tests + Root directory of the project """ return PROJECT_ROOT diff --git a/tests/test_gc.py b/tests/python/tests/test_gc.py similarity index 100% rename from tests/test_gc.py rename to tests/python/tests/test_gc.py diff --git a/tests/test_use_nix.py b/tests/python/tests/test_use_nix.py similarity index 100% rename from tests/test_use_nix.py rename to tests/python/tests/test_use_nix.py diff --git a/tests/testenv/flake.lock b/tests/python/tests/testenv/flake.lock similarity index 100% rename from tests/testenv/flake.lock rename to tests/python/tests/testenv/flake.lock diff --git a/tests/testenv/flake.nix b/tests/python/tests/testenv/flake.nix similarity index 100% rename from tests/testenv/flake.nix rename to tests/python/tests/testenv/flake.nix diff --git a/tests/testenv/shell.nix b/tests/python/tests/testenv/shell.nix similarity index 100% rename from tests/testenv/shell.nix rename to tests/python/tests/testenv/shell.nix From c59b60e7477924d8348f75ff7c836be96c0e9fe7 Mon Sep 17 00:00:00 2001 From: Bryan Bennett Date: Sat, 9 Aug 2025 08:47:06 -0400 Subject: [PATCH 2/2] Switch to bats over pytest This removes python entirely from the stack. Yet to run the tests on macOS, but they're buildable by way of nix wrapping. Worst case scenario, you cannot run them from the devshell but `nix run .#test-runner-` should work okay. --- .envrc | 6 +- flake.nix | 23 ++--- shell.nix | 36 +++++--- test-runner.nix | 38 -------- tests/bash/test_versions.bats | 32 ------- tests/default.nix | 54 ++++++++++++ tests/nix/bats-assert.nix | 23 +++++ tests/nix/bats-support.nix | 24 +++++ tests/python/tests/__init__.py | 0 tests/python/tests/conftest.py | 18 ---- tests/python/tests/direnv_project.py | 48 ---------- tests/python/tests/procs.py | 27 ------ tests/python/tests/root.py | 22 ----- tests/python/tests/test_gc.py | 98 --------------------- tests/python/tests/test_use_nix.py | 71 --------------- tests/test_gc.bats | 73 +++++++++++++++ tests/test_use_nix.bats | 51 +++++++++++ tests/test_versions.bats | 32 +++++++ tests/{python/tests => }/testenv/flake.lock | 0 tests/{python/tests => }/testenv/flake.nix | 0 tests/{python/tests => }/testenv/shell.nix | 0 tests/util.bash | 38 ++++++++ treefmt.nix | 15 ++-- 23 files changed, 344 insertions(+), 385 deletions(-) delete mode 100644 test-runner.nix delete mode 100644 tests/bash/test_versions.bats create mode 100644 tests/default.nix create mode 100644 tests/nix/bats-assert.nix create mode 100644 tests/nix/bats-support.nix delete mode 100644 tests/python/tests/__init__.py delete mode 100644 tests/python/tests/conftest.py delete mode 100644 tests/python/tests/direnv_project.py delete mode 100644 tests/python/tests/procs.py delete mode 100644 tests/python/tests/root.py delete mode 100644 tests/python/tests/test_gc.py delete mode 100644 tests/python/tests/test_use_nix.py create mode 100644 tests/test_gc.bats create mode 100644 tests/test_use_nix.bats create mode 100644 tests/test_versions.bats rename tests/{python/tests => }/testenv/flake.lock (100%) rename tests/{python/tests => }/testenv/flake.nix (100%) rename tests/{python/tests => }/testenv/shell.nix (100%) create mode 100644 tests/util.bash diff --git a/.envrc b/.envrc index e66afc9..1639dfc 100644 --- a/.envrc +++ b/.envrc @@ -1,5 +1,9 @@ # shellcheck shell=bash strict_env source ./direnvrc -watch_file direnvrc ./*.nix + +watch_file direnvrc +# shellcheck disable=SC2046 +watch_file $(find . -name "*.nix") + use flake diff --git a/flake.nix b/flake.nix index 8e844d5..5d969cc 100644 --- a/flake.nix +++ b/flake.nix @@ -33,25 +33,18 @@ self', ... }: + let + nix-direnv = pkgs.callPackage ./default.nix { }; + test_pkgs = pkgs.lib.callPackagesWith pkgs ./tests { inherit nix-direnv; }; + in { - packages = rec { - nix-direnv = pkgs.callPackage ./default.nix { }; - default = config.packages.nix-direnv; - test-runner-stable = pkgs.callPackage ./test-runner.nix { - nixVersion = "stable"; - inherit nix-direnv; - }; - test-runner-latest = pkgs.callPackage ./test-runner.nix { - nixVersion = "latest"; - inherit nix-direnv; - }; + packages = test_pkgs // { + inherit nix-direnv; + default = nix-direnv; }; devShells.default = pkgs.callPackage ./shell.nix { - packages = [ - config.treefmt.build.wrapper - pkgs.shellcheck - ]; + treefmt = config.treefmt.build.wrapper; }; checks = diff --git a/shell.nix b/shell.nix index da9bc2d..409fd63 100644 --- a/shell.nix +++ b/shell.nix @@ -1,14 +1,30 @@ { pkgs ? import { }, - packages ? [ ], + treefmt ? null, + nix-direnv ? (pkgs.callPackage ./default.nix { }), + test_pkgs ? (pkgs.lib.callPackagesWith pkgs ./tests { inherit nix-direnv; }), }: - -with pkgs; -mkShell { - packages = packages ++ [ - python3.pkgs.pytest - python3.pkgs.mypy - ruff - direnv - ]; +let + inherit (pkgs) lib; +in +pkgs.mkShell { + DIRENV_STDLIB = "${test_pkgs.direnv-stdlib}"; + DIRENVRC = "${nix-direnv}/share/nix-direnv/direnvrc"; + BATS_LIB_PATH = lib.strings.makeSearchPath "" ( + with test_pkgs; + [ + bats-support + bats-assert + ] + ); + packages = + (builtins.attrValues { + inherit (pkgs) + bats + direnv + shellcheck + ; + }) + ++ (builtins.attrValues (lib.attrsets.filterAttrs (name: _val: name != "direnv-stdlib") test_pkgs)) + ++ lib.optionals (treefmt != null) [ treefmt ]; } diff --git a/test-runner.nix b/test-runner.nix deleted file mode 100644 index 8de1ab9..0000000 --- a/test-runner.nix +++ /dev/null @@ -1,38 +0,0 @@ -{ - writeShellScriptBin, - direnv, - python3, - lib, - coreutils, - gnugrep, - bats, - nixVersions, - nixVersion, - nix-direnv, - fetchurl, -}: -let - direnv-stdlib = fetchurl { - url = "https://raw.githubusercontent.com/direnv/direnv/refs/tags/v2.37.0/stdlib.sh"; - hash = "sha256-MMM04OXhqS/rRSuv8uh7CD70Z7CaGT63EtL/3LC08qM="; - }; -in -writeShellScriptBin "test-runner-${nixVersion}" '' - set -e - export PATH=${ - lib.makeBinPath [ - direnv - nixVersions.${nixVersion} - coreutils - gnugrep - ] - } - export DIRENV_STDLIB=${direnv-stdlib} - export DIRENVRC="${nix-direnv}/share/nix-direnv/direnvrc" - - echo run python unittest - ${lib.getExe' python3.pkgs.pytest "pytest"} tests/python/ - - echo run bash unittest - ${lib.getExe' bats "bats"} -x --verbose-run tests/bash/ -'' diff --git a/tests/bash/test_versions.bats b/tests/bash/test_versions.bats deleted file mode 100644 index f679479..0000000 --- a/tests/bash/test_versions.bats +++ /dev/null @@ -1,32 +0,0 @@ -load "$DIRENV_STDLIB" -load "$DIRENVRC" - -@test "test _require_version with valid versions" { - # args: cmd version minimum_required - run _require_version "test-cmd" "2.5" "2.4" - [ "$status" -eq 0 ] - run _require_version "test-cmd" "2.5" "2.4.1" - [ "$status" -eq 0 ] - run _require_version "test-cmd" "2.4.1" "2.4" - [ "$status" -eq 0 ] - run _require_version "test-cmd" "2.4" "2.4.1" - [ "$status" -eq 1 ] - run _require_version "test-cmd" "2.31pre20250712_b1245123" "2.4" - [ "$status" -eq 0 ] -} - - -test_cmd1() { - echo "1.2" -} - -test_cmd2() { - echo "1.2.3" -} - -@test "test _require_cmd_version with valid versions" { - run _require_cmd_version "test_cmd1" "1.1" - [ "$status" -eq 0 ] - run _require_cmd_version "test_cmd2" "1.1.1" - [ "$status" -eq 0 ] -} diff --git a/tests/default.nix b/tests/default.nix new file mode 100644 index 0000000..77a7607 --- /dev/null +++ b/tests/default.nix @@ -0,0 +1,54 @@ +{ + bash, + bats, + callPackage, + coreutils, + direnv, + fetchurl, + findutils, + gnugrep, + lib, + nix-direnv, + nixVersions, + writeShellScriptBin, +}: +let + direnv-stdlib = fetchurl { + url = "https://raw.githubusercontent.com/direnv/direnv/refs/tags/v2.37.1/stdlib.sh"; + hash = "sha256-MMM04OXhqS/rRSuv8uh7CD70Z7CaGT63EtL/3LC08qM="; + }; + bats-support = callPackage ./nix/bats-support.nix { }; + bats-assert = callPackage ./nix/bats-assert.nix { }; + mkTestRunner = + nixVersion: + writeShellScriptBin "test-runner-${nixVersion}" '' + set -e + export PATH=${ + lib.makeBinPath [ + bash + direnv + nixVersions.${nixVersion} + coreutils + findutils + gnugrep + ] + } + export DIRENV_STDLIB=${direnv-stdlib} + export DIRENVRC="${nix-direnv}/share/nix-direnv/direnvrc" + export BATS_LIB_PATH="${bats-support}:${bats-assert}" + + echo run unittest + ${lib.getExe' bats "bats"} -x --verbose-run tests/ + ''; + test-runner-stable = mkTestRunner "stable"; + test-runner-latest = mkTestRunner "latest"; +in +{ + inherit + bats-support + bats-assert + direnv-stdlib + test-runner-stable + test-runner-latest + ; +} diff --git a/tests/nix/bats-assert.nix b/tests/nix/bats-assert.nix new file mode 100644 index 0000000..3f509e6 --- /dev/null +++ b/tests/nix/bats-assert.nix @@ -0,0 +1,23 @@ +{ fetchFromGitHub, stdenv }: +stdenv.mkDerivation { + name = "bats-assert"; + version = "2.1.0+"; + src = fetchFromGitHub { + owner = "bats-core"; + repo = "bats-assert"; + rev = "912a98804efd34f24d5eae1bf97ee622ca770e9"; # master 8/7/2025 + hash = "sha256-gp52V4mAiT+Lod2rvEMLhi0Y7AdQQTFCHcNgb8JEKXE="; + }; + + dontBuild = true; + installPhase = '' + + # This looks funny + # but they mean that you can use bats' built-in `bats_load_library` easily + # when setting $BATS_LIB_PATH to the string of the derivation. + + mkdir -p $out/bats-assert; + cp -r src $out/bats-assert/ + cp load.bash $out/bats-assert + ''; +} diff --git a/tests/nix/bats-support.nix b/tests/nix/bats-support.nix new file mode 100644 index 0000000..081f2a8 --- /dev/null +++ b/tests/nix/bats-support.nix @@ -0,0 +1,24 @@ +{ stdenv, fetchFromGitHub }: + +stdenv.mkDerivation { + name = "bats-support"; + version = "3.0+"; + src = fetchFromGitHub { + owner = "bats-core"; + repo = "bats-support"; + rev = "0ad082d4590108684c68975ca517a90459f05cd0"; + hash = "sha256-hkPAn12gQudboL9pDpQZhtaMhqyyj885tti4Gx/aun4="; + }; + + dontBuild = true; + installPhase = '' + + # This looks funny + # but they mean that you can use bats' built-in `bats_load_library` easily + # when setting $BATS_LIB_PATH to the string of the derivation. + + mkdir -p $out/bats-support; + cp -r src $out/bats-support/ + cp load.bash $out/bats-support/ + ''; +} diff --git a/tests/python/tests/__init__.py b/tests/python/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/python/tests/conftest.py b/tests/python/tests/conftest.py deleted file mode 100644 index b951b45..0000000 --- a/tests/python/tests/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -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) diff --git a/tests/python/tests/direnv_project.py b/tests/python/tests/direnv_project.py deleted file mode 100644 index 85528b8..0000000 --- a/tests/python/tests/direnv_project.py +++ /dev/null @@ -1,48 +0,0 @@ -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 diff --git a/tests/python/tests/procs.py b/tests/python/tests/procs.py deleted file mode 100644 index f9d80ba..0000000 --- a/tests/python/tests/procs.py +++ /dev/null @@ -1,27 +0,0 @@ -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 - ) diff --git a/tests/python/tests/root.py b/tests/python/tests/root.py deleted file mode 100644 index 6c71e08..0000000 --- a/tests/python/tests/root.py +++ /dev/null @@ -1,22 +0,0 @@ -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 diff --git a/tests/python/tests/test_gc.py b/tests/python/tests/test_gc.py deleted file mode 100644 index b739dee..0000000 --- a/tests/python/tests/test_gc.py +++ /dev/null @@ -1,98 +0,0 @@ -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() diff --git a/tests/python/tests/test_use_nix.py b/tests/python/tests/test_use_nix.py deleted file mode 100644 index 32120c7..0000000 --- a/tests/python/tests/test_use_nix.py +++ /dev/null @@ -1,71 +0,0 @@ -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() diff --git a/tests/test_gc.bats b/tests/test_gc.bats new file mode 100644 index 0000000..29d8cc6 --- /dev/null +++ b/tests/test_gc.bats @@ -0,0 +1,73 @@ +# -*- mode: bash-ts -*- + +# test initialization ==================== +function setup { + load "util" + + _common_setup +} + +function teardown { + _common_teardown +} + +# helpers ================================= +function assert_run_output { + run_in_direnv hello + assert_output -p "Hello, world" + assert_stderr -p "Executing shellHook" +} + +function assert_gcroot { + profile_path=$(find "$TESTDIR/.direnv" -type l | head -n 1) + run bats_pipe find /nix/var/nix/gcroots/auto/ -type l -printf "%l\n" \| grep -q "$profile_path" + assert_success +} + +function assert_use_nix_layout_dir_shape { + paths=("$TESTDIR"/.direnv/*) + chomped_paths=("${paths[@]#$TESTDIR/.direnv/}") + assert_equal "${#chomped_paths[@]}" "3" + assert_regex "$(printf "%s " "${chomped_paths[@]}")" "bin nix-profile.+ nix-profile-.+\.rc" +} + +function assert_use_flake_layout_dir_shape { + paths=("$TESTDIR"/.direnv/flake-inputs/*) + chomped_inputs_paths=("${paths[@]#$TESTDIR/.direnv/flake-inputs/}") + # four inputs, so four "...-source" outputs + assert_regex "$(printf "%s " "${chomped_inputs_paths[@]}")" "(.+-source[ ]?){4}" + + paths=("$TESTDIR"/.direnv/*) + chomped_paths=("${paths[@]#$TESTDIR/.direnv/}") + assert_equal "${#chomped_paths[@]}" "4" + assert_regex "$(printf "%s " "${chomped_paths[@]}")" "bin flake-inputs flake-profile-.+ flake-profile-.+\.rc" +} + +# tests =================================== +function use_nix_no_strict { # @test + write_envrc "use nix" + assert_run_output + assert_gcroot + assert_use_nix_layout_dir_shape +} + +function use_nix_strict { # @test + write_envrc "strict_env\nuse nix" + assert_run_output + assert_gcroot + assert_use_nix_layout_dir_shape +} + +function use_flake_no_strict { # @test + write_envrc "use flake" + assert_run_output + assert_gcroot + assert_use_flake_layout_dir_shape +} + +function use_flake_strict { # @test + write_envrc "strict_env\nuse flake" + assert_run_output + assert_gcroot + assert_use_flake_layout_dir_shape +} diff --git a/tests/test_use_nix.bats b/tests/test_use_nix.bats new file mode 100644 index 0000000..164fadc --- /dev/null +++ b/tests/test_use_nix.bats @@ -0,0 +1,51 @@ +# -*- mode: bash-ts -*- + +function setup { + load "util" + + _common_setup +} + +function teardown { + _common_teardown +} + +function use_nix_attrs_strict { # @test + write_envrc "strict_env\nuse nix -A subshell" + # shellcheck disable=SC2016 + run_in_direnv 'echo "subshell: $THIS_IS_A_SUBSHELL"' + assert_output -e "subshell: OK$" +} + +function use_nix_attrs_no_strict { # @test + write_envrc "use nix -A subshell" + # shellcheck disable=SC2016 + run_in_direnv 'echo "subshell: $THIS_IS_A_SUBSHELL"' + assert_output -e "subshell: OK$" +} + +function use_nix_no_nix_path_strict { # @test + unset NIX_PATH + write_envrc "strict_env\nuse nix --argstr someArg OK" + # shellcheck disable=SC2016 + run_in_direnv 'echo "someArg: $SHOULD_BE_SET"' + assert_output -e "someArg: OK$" +} + +function use_nix_no_nix_path_no_strict { # @test + unset NIX_PATH + write_envrc "use nix --argstr someArg OK" + # shellcheck disable=SC2016 + run_in_direnv 'echo "someArg: $SHOULD_BE_SET"' + assert_output -e "someArg: OK$" +} + +function use_nix_no_files { # @test + write_envrc "use nix -p hello" + ( + cd "$TESTDIR" || exit 1 + run --separate-stderr direnv status + assert_success + refute_output -p 'Loaded watch: "."' + ) +} diff --git a/tests/test_versions.bats b/tests/test_versions.bats new file mode 100644 index 0000000..2324540 --- /dev/null +++ b/tests/test_versions.bats @@ -0,0 +1,32 @@ +# -*- mode: bash-ts -*- + +function setup { + bats_load_library bats-support + bats_load_library bats-assert + + load "$DIRENV_STDLIB" + load "$DIRENVRC" +} + +function _require_version_with_valid_versions { # @test + # args: cmd version minimum_required + run _require_version "test-cmd" "2.5" "2.4" + assert_success + run _require_version "test-cmd" "2.5" "2.4.1" + assert_success + run _require_version "test-cmd" "2.4.1" "2.4" + assert_success + run _require_version "test-cmd" "2.4" "2.4.1" + assert_failure + run _require_version "test-cmd" "2.31pre20250712_b1245123" "2.4" + assert_success +} + +function _require_cmd_version_with_valid_versions { # @test + run _require_cmd_version "bash" "1.0" + assert_success + run _require_cmd_version "bash" "100.0" + assert_failure + run _require_cmd_version "bash" "1.2.3" + assert_success +} diff --git a/tests/python/tests/testenv/flake.lock b/tests/testenv/flake.lock similarity index 100% rename from tests/python/tests/testenv/flake.lock rename to tests/testenv/flake.lock diff --git a/tests/python/tests/testenv/flake.nix b/tests/testenv/flake.nix similarity index 100% rename from tests/python/tests/testenv/flake.nix rename to tests/testenv/flake.nix diff --git a/tests/python/tests/testenv/shell.nix b/tests/testenv/shell.nix similarity index 100% rename from tests/python/tests/testenv/shell.nix rename to tests/testenv/shell.nix diff --git a/tests/util.bash b/tests/util.bash new file mode 100644 index 0000000..d616bdd --- /dev/null +++ b/tests/util.bash @@ -0,0 +1,38 @@ +function _common_setup { + shopt -s globstar + bats_require_minimum_version 1.5.0 + bats_load_library bats-support + bats_load_library bats-assert + + TESTDIR= + TESTDIR=$(mktemp -d -t nix-direnv.XXXXXX) + export TESTDIR + export DIRENV_LOG_FORMAT="direnv: %s" + + # Set up nix to be able to find your user's nix.conf if run locally + export NIX_USER_CONF_FILES="$HOME/.config/nix/nix.conf" + + export HOME=$TESTDIR/home + unset XDG_DATA_HOME + unset XDG_CONFIG_HOME + + cp "$BATS_TEST_DIRNAME"/testenv/* "$TESTDIR/" +} + +function _common_teardown { + rm -rf "$TESTDIR" +} + +function write_envrc { + echo "source $DIRENVRC" >"$TESTDIR/.envrc" + echo -e "\n$*" >>"$TESTDIR/.envrc" + direnv allow "$TESTDIR" +} + +function run_in_direnv { + run --separate-stderr direnv exec "$TESTDIR" sh -c "$@" + assert_success + run direnv exec "$TESTDIR" sh -c "$@" + assert_success + assert_stderr -p "Renewed cache" +} diff --git a/treefmt.nix b/treefmt.nix index 415c205..44ac600 100644 --- a/treefmt.nix +++ b/treefmt.nix @@ -12,9 +12,6 @@ programs = { deadnix.enable = true; deno.enable = true; - mypy.enable = true; - ruff.check = true; - ruff.format = true; nixfmt.enable = true; nixfmt.package = pkgs.nixfmt-rfc-style; shellcheck.enable = true; @@ -24,8 +21,16 @@ }; settings.formatter = { - shellcheck.includes = [ "direnvrc" ]; - shfmt.includes = [ "direnvrc" ]; + shellcheck.includes = [ + "direnvrc" + "tests/*.bash" + "tests/*.bats" + ]; + shfmt.includes = [ + "direnvrc" + "tests/*.bash" + "tests/*.bats" + ]; }; }; };