From 68e9b9a1e4221f9217e3b12d5d782988b2ebce56 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 9 Feb 2026 14:32:56 +0100 Subject: [PATCH] significantly improve purity --- default.nix | 64 +--------------------- modules-package.nix | 61 +++++++++++++++++++++ modules/tests-common.nix | 4 -- modules/tests-main.py | 76 -------------------------- modules/tests.nix | 52 ------------------ shell.nix | 15 +++-- tests/basic/configuration-package.nix | 2 +- tests/basic/configuration.nix | 24 ++++---- tests/basic/hardware-configuration.nix | 7 +++ tests/basic/test-script.py | 12 +++- tests/basic/test.nix | 19 ++++--- tests/default.nix | 33 +++++++++-- 12 files changed, 139 insertions(+), 230 deletions(-) create mode 100644 modules-package.nix delete mode 100644 modules/tests-common.nix delete mode 100644 modules/tests-main.py delete mode 100644 modules/tests.nix create mode 100644 tests/basic/hardware-configuration.nix diff --git a/default.nix b/default.nix index b7a3bef..bdfa4ef 100644 --- a/default.nix +++ b/default.nix @@ -5,71 +5,13 @@ in import src { inherit pkgs; }, }: let inherit (qpkgs) lib; - - dynix = qpkgs.callPackage ./package.nix { }; - + dynix = qpkgs.callPackage ./modules-package.nix { }; byStdenv = lib.mapAttrs (stdenvName: stdenv: let withStdenv = dynix.override { inherit stdenv; }; dynix' = withStdenv.overrideAttrs (prev: { pname = "${prev.pname}-${stdenvName}"; }); in dynix') qpkgs.validStdenvs; - - evalNixos = import (pkgs.path + "/nixos"); - - doChange = { - option, - value, - }: - assert lib.isList option; - assert lib.all lib.isString option; - let - nixosBefore = evalNixos { - configuration = ./configuration.nix; - }; - dynamicBefore = nixosBefore.config.dynamicism.finalSettings; - - nixosAfter = evalNixos { - configuration = { ... }: { - imports = [ - ./configuration.nix - (lib.setAttrByPath option (lib.mkOverride (-999) value)) - ]; - }; - }; - - withActivationScripts = evalNixos { - configuration = ({ ... }: { - imports = [ ./configuration.nix ]; - config.environment.systemPackages = [ nixosAfter.config.dynamicism.for.gotosocial.activate ]; - }); - }; - in { - inherit nixosBefore nixosAfter withActivationScripts; - }; - -in dynix.overrideAttrs (final: prev: let - self = final.finalPackage; -in lib.recursiveUpdate prev { - passthru = { - ts = let - scope = pkgs.callPackage ./modules/tests.nix { }; - in scope.packages scope; - - dync = self.nixos.config.dynamicism; - dyno = self.nixos.options.dynamicism; - gotosocial = self.nixos.options.dynamicism.for.valueMeta.attrs.gotosocial.configuration; - - inherit byStdenv; - nixos = evalNixos { - configuration = ./configuration.nix; - }; - c = self.nixos; - nixos-vm = self.nixos.config.system.build.vm; - doChange = builtins.seq self.nixos.config.dynamicism doChange; - withVox = self.doChange { - option = lib.splitString "." "services.gotosocial.settings.application-name"; - value = "Vox is an asshole"; - }; - }; +in dynix.overrideAttrs (prev: lib.recursiveUpdate prev { + passthru = { inherit byStdenv; }; }) diff --git a/modules-package.nix b/modules-package.nix new file mode 100644 index 0000000..b36b809 --- /dev/null +++ b/modules-package.nix @@ -0,0 +1,61 @@ +{ + lib, + stdenvNoCC, + callPackage, +}: let + stdenv = stdenvNoCC; +in stdenv.mkDerivation (self: { + name = "dynix-modules"; + + strictDeps = true; + __structuredAttrs = true; + + outputs = [ "out" "modules" ]; + + src = lib.fileset.toSource { + root = ./modules/dynamicism; + fileset = lib.fileset.unions [ + ./modules/dynamicism + ]; + }; + + phases = [ "unpackPhase" "patchPhase" "installPhase "]; + + installPhase = lib.dedent '' + mkdir -vp "$out" + cp -rv * "$out/" + + #mkdir -vp "$modules/share/nix/modules/dynix" + #cp --reflink=auto -rv * "$modules/share/nix/modules/dynix/" + + mkdir -vp "$modules/share/nixos/modules/dynix" + cp --reflink=auto -rv * "$modules/share/nixos/modules/dynix/" + ''; + + passthru.mkDevShell = { + path, + mkShell, + python3Packages, + }: let + mkShell' = mkShell.override { inherit stdenv; }; + pyEnv = python3Packages.python.withPackages (p: [ + p.beartype + ]); + in mkShell' { + name = "devshell-for-${self.finalPackage.name}"; + packages = [ pyEnv ]; + env.PYTHONPATH = [ + "${pyEnv}/${pyEnv.sitePackages}" + # Cursed. + "${path}/nixos/lib/test-driver/src" + ] |> lib.concatStringsSep ":"; + }; + + passthru.tests = lib.fix (callPackage ./tests { + dynix = self.finalPackage; + }).packages; + + meta = { + outputsToInstall = [ "modules" ]; + }; +}) diff --git a/modules/tests-common.nix b/modules/tests-common.nix deleted file mode 100644 index 322771e..0000000 --- a/modules/tests-common.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ pkgs, ... }: -{ - nix.package = pkgs.lixPackageSets.latest.lix; -} diff --git a/modules/tests-main.py b/modules/tests-main.py deleted file mode 100644 index dbd33a6..0000000 --- a/modules/tests-main.py +++ /dev/null @@ -1,76 +0,0 @@ -#import re -from pathlib import Path -from typing import cast, TYPE_CHECKING - -from test_driver.machine import Machine -from test_driver.errors import RequestedAssertionFailed - -DEFAULT_NIX = "@DEFAULT_NIX@" -CONFIGURATION_NIX = "@CONFIGURATION_NIX@" -DYNAMICISM = "@DYNAMICISM@" - -if TYPE_CHECKING: - global machine - machine = cast(Machine, ...) - -def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str: - output = "" - for command in commands: - with machine.nested(f"must succeed: {command}"): - (status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout) - if status != 0: - machine.log(f"output: {out}") - raise RequestedAssertionFailed( - f"command `{command}` failed (exit code {status})", - ) - output += out - - return output - -def get_config_file() -> Path: - machine.wait_for_unit("gotosocial.service") - gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID")) - print(f"{gotosocial_pid=}") - - cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline") - cmdline_args = cmdline.split("\0") - - config_file_idx = cmdline_args.index("--config-path") + 1 - config_file = Path(cmdline_args[config_file_idx]) - - machine.log(f"copying from VM: {config_file=}") - machine.copy_from_vm(config_file.as_posix()) - - return machine.out_dir / config_file.name - - -machine.wait_for_unit("default.target") -assert "lix" in run_log(machine, "nix --version").lower() - -print(f"{CONFIGURATION_NIX=}") -machine.succeed("mkdir -vp /etc/nixos") -machine.copy_from_host(CONFIGURATION_NIX, "/etc/nixos") -machine.copy_from_host(DYNAMICISM, "/etc/nixos") - -run_log(machine, f"nix build --log-format multiline-with-logs --impure -E 'import {{ configuration = {CONFIGURATION_NIX}; }}'") -run_log(machine, f"nixos-rebuild switch --file {CONFIGURATION_NIX} --verbose --print-build-logs") - -config_file_local = get_config_file() -machine.log(f"opening copied file: {config_file_local=}") -with open(config_file_local, "r") as f: - text = f.read() - lines = text.splitlines() - application_name = next(line for line in lines if line.startswith("application-name:")) - assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}" - -print(f"{DEFAULT_NIX=}") - -run_log(machine, "eza -lah --color=always --group-directories-first --tree /etc/") - -#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value") -#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value") - -#service_text = machine.succeed("systemctl show gotosocial.service") -#service_props = dict(line.split("=", maxsplit=1) for line in service_text.splitlines()) -#exec_start = service_props['ExecStart'] -#print(f"{exec_start=}") diff --git a/modules/tests.nix b/modules/tests.nix deleted file mode 100644 index 917c7bd..0000000 --- a/modules/tests.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - pkgs, - lib, - testers, -}: - let - inherit (testers) runNixOSTest; -in lib.makeScope lib.callPackageWith (self: { - main = runNixOSTest { - name = "nixos-test-dynamicism-main"; - - defaults = { pkgs, ... }: { - imports = [ ./dynamicism ]; - - nix = { - package = pkgs.lixPackageSets.latest.lix; - settings.experimental-features = [ "nix-command" ]; - nixPath = [ "nixpkgs=${pkgs.path}" ]; - }; - - environment.shellAliases = { - ls = "eza --long --header --group --group-directories-first --classify --binary"; - }; - environment.systemPackages = with pkgs; [ - eza - fd - ripgrep - ]; - }; - - nodes.machine = { name, ... }: { - #services.gotosocial = { - # enable = true; - # setupPostgresqlDB = true; - # settings = { - # application-name = "gotosocial-for-${name}"; - # host = "${name}.local"; - # }; - #}; - # - #dynamicism.for.gotosocial.enable = true; - }; - - # What's a little IFD between friends? - testScript = pkgs.replaceVars ./tests-main.py { - DEFAULT_NIX = ../default.nix; - CONFIGURATION_NIX = ./tests-configuration.nix; - DYNAMICISM = ./dynamicism; - } - |> builtins.readFile; - }; -}) diff --git a/shell.nix b/shell.nix index c73c766..3506e2d 100644 --- a/shell.nix +++ b/shell.nix @@ -1,5 +1,10 @@ { - pkgs ? import { }, + pkgs ? import { + config = { + checkMeta = true; + allowAliases = false; + }; + }, qpkgs ? let src = fetchTarball "https://github.com/Qyriad/nur-packages/archive/main.tar.gz"; in import src { inherit pkgs; }, @@ -12,12 +17,6 @@ byStdenv = lib.mapAttrs (lib.const mkDevShell) dynix.byStdenv; -in devShell.overrideAttrs (prev: lib.recursiveUpdate prev { +in devShell.overrideAttrs (prev: { passthru = { inherit byStdenv; }; - env.PYTHONPATH = [ - "${pkgs.python3Packages.beartype}/${pkgs.python3.sitePackages}" - ] |> lib.concatStringsSep ":"; - packages = prev.packages or [ ] ++ [ - pkgs.python3Packages.beartype - ]; }) diff --git a/tests/basic/configuration-package.nix b/tests/basic/configuration-package.nix index 4b50af6..e6bb010 100644 --- a/tests/basic/configuration-package.nix +++ b/tests/basic/configuration-package.nix @@ -5,5 +5,5 @@ set -euo pipefail mkdir -vp "$out/share/nixos" cp -rv ${./configuration.nix} "$out/share/nixos/configuration.nix" - cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism" + #cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism" '' diff --git a/tests/basic/configuration.nix b/tests/basic/configuration.nix index 06470e3..bc751ed 100644 --- a/tests/basic/configuration.nix +++ b/tests/basic/configuration.nix @@ -1,22 +1,18 @@ { pkgs, lib, config, modulesPath, ... }: let name = config.networking.hostName; - nixosLibPath = (modulesPath + "/../lib"); moduleList = import (modulesPath + "/module-list.nix"); - optionalPath = p: lib.optional (builtins.pathExists p) p; + dynixFromSearchPath = let + res = builtins.tryEval ; + in lib.optional res.success res.value; in -assert builtins.pathExists nixosLibPath; -builtins.seq lib -builtins.seq modulesPath -builtins.seq moduleList { imports = moduleList ++ [ - (modulesPath + "/testing/test-instrumentation.nix") + "${modulesPath}/testing/test-instrumentation.nix" + ./hardware-configuration.nix ] ++ lib.concatLists [ - (optionalPath ./hardware-configuration.nix) - (optionalPath ./dynamicism) - (optionalPath ../../modules/dynamicism) + dynixFromSearchPath ]; system.switch.enable = true; @@ -32,13 +28,18 @@ builtins.seq moduleList nix = { package = pkgs.lixPackageSets.latest.lix; - nixPath = [ "nixpkgs=${pkgs.path}" ]; + nixPath = [ + "nixpkgs=${pkgs.path}" + "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" + ]; settings = { experimental-features = [ "nix-command" "pipe-operator" ]; substituters = lib.mkForce [ ]; hashed-mirrors = null; connect-timeout = 1; + # For my debugging purposes. + show-trace = true; }; }; @@ -54,6 +55,7 @@ builtins.seq moduleList dynamicism.for.gotosocial.enable = true; environment.pathsToLink = [ "/share" ]; + environment.extraOutputsToInstall = [ "modules" ]; environment.variables = { "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; }; diff --git a/tests/basic/hardware-configuration.nix b/tests/basic/hardware-configuration.nix new file mode 100644 index 0000000..f036a72 --- /dev/null +++ b/tests/basic/hardware-configuration.nix @@ -0,0 +1,7 @@ +/** Dummy hardware configuration. + * Will be replaced with the real one in the test VM. + */ +{ ... }: +{ + +} diff --git a/tests/basic/test-script.py b/tests/basic/test-script.py index f507503..03974fc 100644 --- a/tests/basic/test-script.py +++ b/tests/basic/test-script.py @@ -52,11 +52,17 @@ def get_config_file() -> str: machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() +machine.log("INIT") -run_log(machine, "nixos-generate-config") +machine.succeed("nix profile install -vv $(realpath /run/current-system/sw/share/nixos/modules/dynix)") + +machine.succeed("nixos-generate-config") machine.succeed("mkdir -vp /etc/nixos") -machine.succeed("cp -rv /run/current-system/sw/share/nixos/* /etc/nixos/") -machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs -v --fallback >&2") +# Dereference is required since that configuration.nix is probably a symlink to the store. +machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/") + +machine.log("REBUILDING configuration inside VM") +machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") config_text = get_config_file() lines = config_text.splitlines() diff --git a/tests/basic/test.nix b/tests/basic/test.nix index 61110ad..4356685 100644 --- a/tests/basic/test.nix +++ b/tests/basic/test.nix @@ -1,22 +1,23 @@ -{ - pkgs, - lib, - config, - ... -}: +{ dynix, ... }: + { name = "nixos-test-dynamicism-main"; defaults = { ... }: { }; - #node.pkgsReadOnly = false; - extraPythonPackages = p: [ p.beartype ]; nodes.machine = { pkgs, config, ... }: { - imports = [ ./configuration.nix ]; + # NOTE: Anything in this `nodes.machine = ` module will not be included + # in the VM's NixOS configuration once it does `nixos-rebuild switch`, + # except for `./configuration.nix` which will be copied to `/etc/nixos/`. + # dynix will also be statefully installed to root's user profile. + imports = [ + ./configuration.nix + (toString dynix) + ]; system.includeBuildDependencies = true; system.switch.enable = true; diff --git a/tests/default.nix b/tests/default.nix index bc4beef..d17c3ac 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,8 +1,31 @@ { pkgs ? import { }, - lib ? pkgs.lib, -}: lib.makeScope lib.callPackageWith (self: let - inherit (pkgs.testers) runNixOSTest; -in { - basic = runNixOSTest ./basic/test.nix; + qpkgs ? let + src = fetchTree (builtins.parseFlakeRef "github:Qyriad/nur-packages"); + in import src { inherit pkgs; }, + lib ? qpkgs.lib, + dynix ? qpkgs.callPackage ../modules-package.nix { }, +}: let + + runDynixTest = testModule: pkgs.testers.runNixOSTest { + imports = [ testModule ]; + + # Note: these are arguments to the *test* modules. + # Not the NixOS modules for the NixOS configuration the test is testing. + # Wew. + _module.args = { inherit dynix; }; + + # Why is this argument called "extraBaseModule**s**" but take a single module argument... + extraBaseModules = { name, ... }: { + #imports = [ dynixInjectionModule ]; + config.environment.systemPackages = [ dynix ]; + # Just making something in this strict in `name`, + # which is only present as an argument for nodes and I don't want to + # confuse that with the test modules. + config.warnings = builtins.seq name [ ]; + }; + }; + +in lib.makeScope lib.callPackageWith (self: { + basic = runDynixTest ./basic/test.nix; })