From 19c0120754f904c3b0caeb65b3b19d13c4500926 Mon Sep 17 00:00:00 2001 From: SomeoneSerge Date: Thu, 1 Aug 2024 17:30:15 +0000 Subject: nixosTests: init A PoC that only runs --dry-run, no evanix --- flake.nix | 8 ++ nixos/tests/README.md | 14 +++ nixos/tests/all-tests.nix | 68 +++++++++++++ nixos/tests/dsl.nix | 247 ++++++++++++++++++++++++++++++++++++++++++++++ nixos/tests/scope-fun.nix | 32 ++++++ 5 files changed, 369 insertions(+) create mode 100644 nixos/tests/README.md create mode 100644 nixos/tests/all-tests.nix create mode 100644 nixos/tests/dsl.nix create mode 100644 nixos/tests/scope-fun.nix diff --git a/flake.nix b/flake.nix index 837785f..8421959 100644 --- a/flake.nix +++ b/flake.nix @@ -63,5 +63,13 @@ }); } ); + legacyPackages = forAllSystems ( + { pkgs, ... }: + { + nixosTests = pkgs.callPackage ./nixos/tests/all-tests.nix { + nixos-lib = import (nixpkgs + "/nixos/lib") { }; + }; + } + ); }; } diff --git a/nixos/tests/README.md b/nixos/tests/README.md new file mode 100644 index 0000000..e568e57 --- /dev/null +++ b/nixos/tests/README.md @@ -0,0 +1,14 @@ +Synthetic integration tests for "real" nix stores and substituters + +Usage +--- + +```console +$ nix build .#nixosTests.diamond-unbuilt-2 +``` + +Development +--- + +The `.#nixosTests` attrset is defined in [`all-tests.nix`](./all-tests.nix). +In [dsl.nix](./dsl.nix) we define the helper for generating NixOS tests from DAGs. diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix new file mode 100644 index 0000000..17a4332 --- /dev/null +++ b/nixos/tests/all-tests.nix @@ -0,0 +1,68 @@ +{ + callPackage, + nixos-lib, + pkgs, +}: + +let + dsl = ./dsl.nix; + diamond.dag = { + nodes.a = { }; + nodes.b.inputs.a = { }; # b->a + nodes.c.inputs.a = { }; # c->a + nodes.d.inputs.b = { }; # d->b + nodes.d.inputs.c = { }; # d->c + }; +in +builtins.mapAttrs + ( + name: value: + nixos-lib.runTest ( + { + inherit name; + hostPkgs = pkgs; + testScript = '' + start_all() + substituter.wait_for_unit("nix-serve.service") + builder.succeed("dag-test") + ''; + } + // value + ) + ) + { + diamond-unbuilt-0 = { + imports = [ + { + dag.needBuilds = 0; + dag.needDownloads = 0; + } + diamond + dsl + ]; + }; + diamond-unbuilt-2 = { + imports = [ + { + dag.nodes.a.cache = "remote"; + dag.nodes.b.cache = "remote"; + dag.nodes.d.request = true; + dag.needBuilds = 2; + dag.needDownloads = 2; + } + diamond + dsl + ]; + }; + diamond-unbuilt-4 = { + imports = [ + { + dag.nodes.d.request = true; + dag.needBuilds = 4; + dag.needDownloads = 0; + } + diamond + dsl + ]; + }; + } diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix new file mode 100644 index 0000000..d11a231 --- /dev/null +++ b/nixos/tests/dsl.nix @@ -0,0 +1,247 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + Dependency = + { name, ... }: + { + options.name = lib.mkOption { + type = lib.types.str; + default = name; + }; + options.runtime = lib.mkEnableOption "Keep a reference in the output store path to retain a runtime dependency"; + }; + Node = ( + { name, ... }: + { + options.name = lib.mkOption { + type = lib.types.str; + default = name; + }; + options.request = lib.mkEnableOption "Whether to mark the node for building"; + options.needed = lib.mkOption { + type = with lib.types; nullOr bool; + default = null; + description = "Whether the node must be built to satisfy all requests (either a requested node or a transitive dependency)"; + }; + options.chosen = lib.mkOption { + type = with lib.types; nullOr bool; + default = null; + description = "Whether the node is included in the build plan (i.t. it's `needed` and fitted into budget)"; + }; + options.cache = lib.mkOption { + type = lib.types.enum [ + "none" + "remote" + "local" + ]; + description = '' + Whether the dependency is pre-built and available in the local /nix/store ("local"), can be substituted ("remote"), or has to be built ("none") + ''; + default = "none"; + }; + options.inputs = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule Dependency); + default = { }; + }; + } + ); + Nodes = lib.types.attrsOf (lib.types.submodule Node); + scope-fun = import ./scope-fun.nix { + inherit lib; + inherit (config.dag) nodes; + }; +in +{ + options.dag = { + nodes = lib.mkOption { + type = Nodes; + description = "Derivation DAG, including cache status and references."; + }; + needBuilds = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + description = "How many builds are required to satisfy all targets. Null disables the test"; + }; + needDownloads = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + description = "How many downloads are required to satisfy all targets. Null disables the test"; + }; + choseBuilds = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + description = "How many builds we expect evanix to choose to satisfy the maximum number of targets within the given budget. Null disables the test"; + }; + choseDownloads = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + description = "How many downloads we expect evanix to choose to satisfy the maximum number of targets within the given budget. Null disables the test"; + }; + allowBuilds = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + description = "How many builds evanix is allowed to choose. Null means no constraint"; + }; + allowDownloads = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + description = "How many downloads evanix is allowed to choose. Null means no constraint"; + }; + }; + + config.nodes.builder = + { pkgs, ... }: + let + scope = pkgs.lib.makeScope pkgs.newScope scope-fun; + configJson = (pkgs.formats.json { }).generate "nix-dag.json" config.dag; + expressions = pkgs.writeText "guest-scope.nix" '' + let + pkgs = import ${pkgs.path} { }; + config = builtins.fromJSON (builtins.readFile ${configJson}); + in + pkgs.lib.makeScope pkgs.newScope (pkgs.callPackage ${./scope-fun.nix} { inherit (pkgs) lib; inherit (config) nodes; }) + ''; + tester = pkgs.writers.writePython3Bin "dag-test" { } '' + # flake8: noqa + + import json + import re + import subprocess + import sys + + with open("${configJson}", "r") as f: + config = json.load(f) + + nodes = config["nodes"] + print(f"config={config}", file=sys.stderr) + + + def path_to_name(path: str) -> str: + return re.sub(r"^[ ]*${builtins.storeDir}/[a-z0-9]*-([a-zA-Z0-9_-]+)(\.drv)?", r"\1", path) + + def parse_dry_run(output): + to_fetch = [ ] + to_build = [ ] + + bin = "undefined" + for line in output.split("\n"): + + if not line: + continue + + if re.match("^.*will be built:$", line): + bin = "to_build" + continue + elif re.match("^.*will be fetched.*:$", line): + bin = "to_fetch" + continue + + if not re.match("^[ ]*${builtins.storeDir}", line): + print(f"Skipping line: {line}", file=sys.stderr) + continue + + line = path_to_name(line) + + if bin == "to_build": + to_build.append(line) + elif bin == "to_fetch": + to_fetch.append(line) + else: + raise RuntimeError("nix-build --dry-run produced invalid output", line) + return to_fetch, to_build + + drv_to_schedule = {} + for name, node in nodes.items(): + p = subprocess.run(["nix-build", "${expressions}", "--dry-run", "--show-trace", "-A", name], check=True, stderr=subprocess.PIPE) + output = p.stderr.decode("utf-8") + to_fetch, to_build = parse_dry_run(output) + drv_to_schedule[name] = (to_fetch, to_build) + + drv_to_action = {} + for (to_fetch, to_build) in drv_to_schedule.values(): + for dep in to_fetch: + name = path_to_name(dep) + if name not in drv_to_action: + drv_to_action[name] = "fetch" + assert drv_to_action[name] == "fetch", f"Conflicting plan for {dep}" + for dep in to_build: + name = path_to_name(dep) + if name not in drv_to_action: + drv_to_action[name] = "build" + assert drv_to_action[name] == "build", f"Conflicting plan for {dep}" + + print(f"Schedule: {drv_to_action}", file=sys.stderr) + print(f"Per-derivation schedules: {drv_to_schedule}", file=sys.stderr) + + for name, node in nodes.items(): + error_msg = f"Wrong plan for {name}" + if node["cache"] == "local": + assert name not in drv_to_action, error_msg + elif node["cache"] == "remote": + assert drv_to_action.get(name, None) == "fetch", error_msg + elif node["cache"] == "unbuilt": + assert drv_to_action.get(name, None) == "build", error_msg + + need_dls, need_builds = set(), set() + for name, node in nodes.items(): + if node["request"]: + need_dls.update(drv_to_schedule[name][0]) + need_builds.update(drv_to_schedule[name][1]) + + if (expected_need_dls := config.get("needDownloads", None)) is not None: + assert len(need_dls) == expected_need_dls, f"{len(need_dls)} != {expected_need_dls}; building {need_dls}" + print("Verified `needDownloads`", file=sys.stderr) + + if (expected_need_builds := config.get("needBuilds", None)) is not None: + assert len(need_builds) == expected_need_builds, f"{len(need_builds)} != {expected_need_builds}; building {need_builds}" + print("Verified `needBuilds`", file=sys.stderr) + ''; + in + { + system.extraDependencies = + lib.pipe config.dag.nodes [ + builtins.attrValues + (builtins.filter ({ cache, ... }: cache == "local")) + (builtins.map ({ name, ... }: scope.${name})) + ] + ++ [ + expressions + pkgs.path + (pkgs.runCommand "any-run-command" { } "").inputDerivation + ]; + networking.hostName = "builder"; + networking.domain = "evanix-tests.local"; + nix.settings.substituters = lib.mkForce [ "http://substituter" ]; + systemd.tmpfiles.settings."10-expressions" = { + "/run/dag-test/nix-dag-test.json"."L+".argument = "${configJson}"; + "/run/dag-test/scope.nix"."L+".argument = "${expressions}"; + }; + environment.systemPackages = [ tester ]; + }; + config.nodes.substituter = + { pkgs, ... }: + let + scope = pkgs.lib.makeScope pkgs.newScope scope-fun; + in + { + system.extraDependencies = lib.pipe config.dag.nodes [ + builtins.attrValues + (builtins.filter ({ cache, ... }: cache == "remote")) + (builtins.map ({ name, ... }: scope.${name})) + ]; + services.nix-serve.enable = true; + services.nix-serve.port = 80; + services.nix-serve.openFirewall = true; + + # Allow listening on 80 + systemd.services.nix-serve.serviceConfig.User = lib.mkForce "root"; + networking.hostName = "substituter"; + + networking.domain = "evanix-tests.local"; + }; +} diff --git a/nixos/tests/scope-fun.nix b/nixos/tests/scope-fun.nix new file mode 100644 index 0000000..9dc0cf0 --- /dev/null +++ b/nixos/tests/scope-fun.nix @@ -0,0 +1,32 @@ +{ lib, nodes }: +assert builtins.isAttrs nodes; +self: +let + mkBuildInputs = + propagated: + lib.flip lib.pipe [ + builtins.attrValues + (builtins.filter ({ runtime, ... }: (propagated && runtime) || (!propagated && !runtime))) + (map ({ name, ... }: self.${name})) + ]; +in +builtins.mapAttrs ( + name: node: + assert builtins.isString name; + assert builtins.isAttrs node; + let + inherit (node) inputs; + in + self.callPackage ( + { runCommand }: + runCommand name + { + buildInputs = mkBuildInputs false inputs; + propagatedBuildInputs = mkBuildInputs true inputs; + } + '' + mkdir $out + echo ${name} > $out/name + '' + ) { } +) nodes -- cgit v1.2.3 From 97a78ba6b20cbd60c63b69fcaa4f60e12e46bedb Mon Sep 17 00:00:00 2001 From: SomeoneSerge Date: Thu, 8 Aug 2024 21:16:24 +0000 Subject: flake.nix: add .#checks --- .github/workflows/build.yml | 14 ++++++++++++++ flake.nix | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4285978..936d055 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,3 +22,17 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@v7 - name: Build some-pkgs run: nix run github:Mic92/nix-fast-build -- --skip-cached --no-nom --flake ".#packages" + nix-flake-check: + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v13 + - uses: DeterminateSystems/magic-nix-cache-action@v7 + - name: Build the checks + run: nix run github:Mic92/nix-fast-build -- --skip-cached --no-nom --flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)" diff --git a/flake.nix b/flake.nix index 8421959..e86dc6b 100644 --- a/flake.nix +++ b/flake.nix @@ -71,5 +71,24 @@ }; } ); + checks = forAllSystems ( + { system, pkgs, ... }: + let + inherit (pkgs.lib) + filterAttrs + isDerivation + mapAttrs' + nameValuePair + pipe + ; + in + pipe self.legacyPackages.${system}.nixosTests [ + (filterAttrs (_: p: isDerivation p)) + (mapAttrs' (name: nameValuePair "nixosTests-${name}")) + ] + // { + inherit (self.packages.${system}) evanix evanix-py; + } + ); }; } -- cgit v1.2.3 From 9dfb301b243c0ce4e97b74434debfd73590dbf47 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Sun, 11 Aug 2024 07:41:42 +0530 Subject: nixosTests: drop unused args --- nixos/tests/all-tests.nix | 1 - nixos/tests/dsl.nix | 1 - 2 files changed, 2 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 17a4332..701994b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1,5 +1,4 @@ { - callPackage, nixos-lib, pkgs, }: diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index d11a231..41f0a4c 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -1,7 +1,6 @@ { lib, config, - pkgs, ... }: -- cgit v1.2.3 From 51528320d9ab23956d5d6492c38b5ef3899c2226 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Sun, 11 Aug 2024 16:41:33 +0530 Subject: nixosTests/all-tests: always import dsl --- nixos/tests/all-tests.nix | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 701994b..e255fbe 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -27,6 +27,7 @@ builtins.mapAttrs ''; } // value + // { imports = value.imports ++ [ dsl ]; } ) ) { @@ -37,7 +38,6 @@ builtins.mapAttrs dag.needDownloads = 0; } diamond - dsl ]; }; diamond-unbuilt-2 = { @@ -50,7 +50,6 @@ builtins.mapAttrs dag.needDownloads = 2; } diamond - dsl ]; }; diamond-unbuilt-4 = { @@ -61,7 +60,6 @@ builtins.mapAttrs dag.needDownloads = 0; } diamond - dsl ]; }; } -- cgit v1.2.3 From 89ac91f70a9788b7a39b1b130108b005ed354a7d Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Sun, 11 Aug 2024 17:12:44 +0530 Subject: nixosTests/sunset: init --- nixos/tests/all-tests.nix | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index e255fbe..7f7c6be 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -12,6 +12,45 @@ let nodes.d.inputs.b = { }; # d->b nodes.d.inputs.c = { }; # d->c }; + + # A B C D E + # \ | / | | + # U V W X + sunset.dag = { + nodes = let + abcInputs = { + u = {}; + v = {}; + }; + in { + a = { + request = true; + inputs = abcInputs; + }; + b = { + request = true; + inputs = abcInputs; + }; + c = { + request = true; + inputs = abcInputs; + }; + + d = { + request = true; + inputs.w = {}; + }; + e = { + request = true; + inputs.x = {}; + }; + + u = {}; + v = {}; + w = {}; + x = {}; + }; + }; in builtins.mapAttrs ( -- cgit v1.2.3 From f6f682a8a9e6d7c5be41bdd087dcdaff8bb20836 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Sun, 11 Aug 2024 18:58:05 +0530 Subject: flake/devShell: add nixfmt-rfc-style --- flake.nix | 1 + nixos/tests/all-tests.nix | 73 ++++++++++++++++++++++++----------------------- nixos/tests/dsl.nix | 6 +--- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/flake.nix b/flake.nix index e86dc6b..4bf73f8 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,7 @@ inputsFrom = [ self.packages.${system}.evanix ]; packages = with pkgs; [ + nixfmt-rfc-style gdb ccls valgrind diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 7f7c6be..e1e64ce 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1,7 +1,4 @@ -{ - nixos-lib, - pkgs, -}: +{ nixos-lib, pkgs }: let dsl = ./dsl.nix; @@ -17,39 +14,41 @@ let # \ | / | | # U V W X sunset.dag = { - nodes = let - abcInputs = { - u = {}; - v = {}; - }; - in { - a = { - request = true; - inputs = abcInputs; - }; - b = { - request = true; - inputs = abcInputs; - }; - c = { - request = true; - inputs = abcInputs; - }; + nodes = + let + abcInputs = { + u = { }; + v = { }; + }; + in + { + a = { + request = true; + inputs = abcInputs; + }; + b = { + request = true; + inputs = abcInputs; + }; + c = { + request = true; + inputs = abcInputs; + }; - d = { - request = true; - inputs.w = {}; - }; - e = { - request = true; - inputs.x = {}; - }; + d = { + request = true; + inputs.w = { }; + }; + e = { + request = true; + inputs.x = { }; + }; - u = {}; - v = {}; - w = {}; - x = {}; - }; + u = { }; + v = { }; + w = { }; + x = { }; + }; }; in builtins.mapAttrs @@ -66,7 +65,9 @@ builtins.mapAttrs ''; } // value - // { imports = value.imports ++ [ dsl ]; } + // { + imports = value.imports ++ [ dsl ]; + } ) ) { diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index 41f0a4c..4297484 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -1,8 +1,4 @@ -{ - lib, - config, - ... -}: +{ lib, config, ... }: let Dependency = -- cgit v1.2.3 From 1465c27806b9c9632eb937b6c8089397340b6d44 Mon Sep 17 00:00:00 2001 From: SomeoneSerge Date: Sun, 11 Aug 2024 17:01:19 +0000 Subject: nixosTests: runCommand.inputDerivatino: explain why --- nixos/tests/dsl.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index 4297484..daadd35 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -207,6 +207,8 @@ in ++ [ expressions pkgs.path + + # Cache runCommand's dependencies such as runtimeShell (pkgs.runCommand "any-run-command" { } "").inputDerivation ]; networking.hostName = "builder"; -- cgit v1.2.3 From f09e70de5cfb853081ff97f676376871935bd956 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Mon, 12 Aug 2024 22:49:51 +0530 Subject: nixosTests/sunset-unbuilt-0: init --- nixos/tests/all-tests.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index e1e64ce..b0373da 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -102,4 +102,18 @@ builtins.mapAttrs diamond ]; }; + + sunset-unbuilt-0 = { + imports = [ + { + # all builds + dag.needBuilds = 9; + # all builds allowed + dag.allowBuilds = 5; + # chosen builds requested + dag.choseBuilds = 3; + } + sunset + ]; + }; } -- cgit v1.2.3 From b31a7597c14189faa8f8da99305b964d3cb86de0 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Mon, 12 Aug 2024 22:56:20 +0530 Subject: nixosTests/dsl: change option names --- nixos/tests/dsl.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index daadd35..341207c 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -18,12 +18,12 @@ let default = name; }; options.request = lib.mkEnableOption "Whether to mark the node for building"; - options.needed = lib.mkOption { + options.assertNeeded = lib.mkOption { type = with lib.types; nullOr bool; default = null; description = "Whether the node must be built to satisfy all requests (either a requested node or a transitive dependency)"; }; - options.chosen = lib.mkOption { + options.assertChosen = lib.mkOption { type = with lib.types; nullOr bool; default = null; description = "Whether the node is included in the build plan (i.t. it's `needed` and fitted into budget)"; -- cgit v1.2.3 From ca46628491546f2e7e846c114e748bae7b527595 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Mon, 12 Aug 2024 23:17:33 +0530 Subject: nixosTests/dsl: make sure cache value is valid --- nixos/tests/dsl.nix | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index 341207c..4d59bcb 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -30,14 +30,14 @@ let }; options.cache = lib.mkOption { type = lib.types.enum [ - "none" + "unbuilt" "remote" "local" ]; description = '' Whether the dependency is pre-built and available in the local /nix/store ("local"), can be substituted ("remote"), or has to be built ("none") ''; - default = "none"; + default = "unbuilt"; }; options.inputs = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule Dependency); @@ -175,12 +175,15 @@ in for name, node in nodes.items(): error_msg = f"Wrong plan for {name}" + action = drv_to_action.get(name, None) if node["cache"] == "local": - assert name not in drv_to_action, error_msg + assert action is None, error_msg elif node["cache"] == "remote": - assert drv_to_action.get(name, None) == "fetch", error_msg + assert action == "fetch", error_msg elif node["cache"] == "unbuilt": - assert drv_to_action.get(name, None) == "build", error_msg + assert action == "build", error_msg + else: + raise AssertionError('cache is not in [ "local", "remote", "unbuilt" ]') need_dls, need_builds = set(), set() for name, node in nodes.items(): -- cgit v1.2.3 From 5ad39d3d40ca0ee8ea1196b9b3dd25ba4aa38232 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Tue, 13 Aug 2024 19:30:07 +0530 Subject: nixosTests/dsl: verify choseBuilds --- nixos/tests/dsl.nix | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index 4d59bcb..b22c521 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -92,6 +92,8 @@ in config.nodes.builder = { pkgs, ... }: let + evanixPkg = pkgs.callPackage ../../package.nix { }; + scope = pkgs.lib.makeScope pkgs.newScope scope-fun; configJson = (pkgs.formats.json { }).generate "nix-dag.json" config.dag; expressions = pkgs.writeText "guest-scope.nix" '' @@ -101,6 +103,15 @@ in in pkgs.lib.makeScope pkgs.newScope (pkgs.callPackage ${./scope-fun.nix} { inherit (pkgs) lib; inherit (config) nodes; }) ''; + requestExpressions = pkgs.writeText "guest-request-scope.nix" '' + let + pkgs = import /nix/store/r365xbb4lp3h3vzahr97aysrzd4dysis-yqy82fn77fy3rv7lpwa9m11w3a2nnqg5-source { }; + config = builtins.fromJSON (builtins.readFile /nix/store/4jai0ds3f1kswspwdib1ggxwyikv1jhr-nix-dag.json); + testPkgs = import ${expressions}; + in + pkgs.lib.attrsets.filterAttrs (name: node: !(config.nodes ? ''${name}) || config.nodes.''${name}.request ) testPkgs + ''; + tester = pkgs.writers.writePython3Bin "dag-test" { } '' # flake8: noqa @@ -119,6 +130,18 @@ in def path_to_name(path: str) -> str: return re.sub(r"^[ ]*${builtins.storeDir}/[a-z0-9]*-([a-zA-Z0-9_-]+)(\.drv)?", r"\1", path) + def parse_evanix_dry_run(output): + to_build = [ ] + + for line in output.split("\n"): + if not re.match("nix-build --out-link .*$", line): + continue + + drv = re.sub(r"^nix-build --out-link result-([a-zA-Z0-9_-]+).*$", r"\1", line) + to_build.append(drv) + + return to_build + def parse_dry_run(output): to_fetch = [ ] to_build = [ ] @@ -198,6 +221,13 @@ in if (expected_need_builds := config.get("needBuilds", None)) is not None: assert len(need_builds) == expected_need_builds, f"{len(need_builds)} != {expected_need_builds}; building {need_builds}" print("Verified `needBuilds`", file=sys.stderr) + + if config.get("allowBuilds", None) is not None and config.get("choseBuilds", None) is not None: + p = subprocess.run(["evanix", "${requestExpressions}", "--dry-run", "--solver=highs", "--max-build", str(config["allowBuilds"])], check=True, stdout=subprocess.PIPE) + output = p.stdout.decode("utf-8") + evanix_builds = parse_evanix_dry_run(output) + assert len(evanix_builds) == config["choseBuilds"], f"len({evanix_builds}) != choseBuilds" + print("Verified `choseBuilds`", file=sys.stderr) ''; in { @@ -209,6 +239,7 @@ in ] ++ [ expressions + requestExpressions pkgs.path # Cache runCommand's dependencies such as runtimeShell @@ -220,8 +251,13 @@ in systemd.tmpfiles.settings."10-expressions" = { "/run/dag-test/nix-dag-test.json"."L+".argument = "${configJson}"; "/run/dag-test/scope.nix"."L+".argument = "${expressions}"; + "/run/dag-test/requestScope.nix"."L+".argument = "${requestExpressions}"; }; - environment.systemPackages = [ tester ]; + + environment.systemPackages = [ + tester + evanixPkg + ]; }; config.nodes.substituter = { pkgs, ... }: -- cgit v1.2.3 From e1cedc07a2bc987f6eac508cd99e92faa62da081 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Tue, 13 Aug 2024 21:52:59 +0530 Subject: nixosTests: drop nixos-lib --- flake.nix | 4 +--- nixos/tests/all-tests.nix | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 4bf73f8..bef6329 100644 --- a/flake.nix +++ b/flake.nix @@ -67,9 +67,7 @@ legacyPackages = forAllSystems ( { pkgs, ... }: { - nixosTests = pkgs.callPackage ./nixos/tests/all-tests.nix { - nixos-lib = import (nixpkgs + "/nixos/lib") { }; - }; + nixosTests = import ./nixos/tests/all-tests.nix pkgs; } ); checks = forAllSystems ( diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index b0373da..68342d6 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1,4 +1,4 @@ -{ nixos-lib, pkgs }: +pkgs: let dsl = ./dsl.nix; @@ -54,10 +54,9 @@ in builtins.mapAttrs ( name: value: - nixos-lib.runTest ( + pkgs.testers.runNixOSTest ( { inherit name; - hostPkgs = pkgs; testScript = '' start_all() substituter.wait_for_unit("nix-serve.service") -- cgit v1.2.3 From b79e15cb07e8540ccc037f9cb80d3082366e7560 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Wed, 14 Aug 2024 07:19:03 +0530 Subject: nixosTests/dsl: verify assertChosen --- flake.nix | 2 +- nixos/tests/all-tests.nix | 24 ++++++++++++++++-------- nixos/tests/dsl.nix | 31 ++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/flake.nix b/flake.nix index bef6329..10314f9 100644 --- a/flake.nix +++ b/flake.nix @@ -67,7 +67,7 @@ legacyPackages = forAllSystems ( { pkgs, ... }: { - nixosTests = import ./nixos/tests/all-tests.nix pkgs; + nixosTests = pkgs.callPackage ./nixos/tests/all-tests.nix { }; } ); checks = forAllSystems ( diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 68342d6..158c025 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1,4 +1,4 @@ -pkgs: +{ testers }: let dsl = ./dsl.nix; @@ -54,7 +54,7 @@ in builtins.mapAttrs ( name: value: - pkgs.testers.runNixOSTest ( + testers.runNixOSTest ( { inherit name; testScript = '' @@ -105,12 +105,20 @@ builtins.mapAttrs sunset-unbuilt-0 = { imports = [ { - # all builds - dag.needBuilds = 9; - # all builds allowed - dag.allowBuilds = 5; - # chosen builds requested - dag.choseBuilds = 3; + dag = { + # all builds + needBuilds = 9; + # all builds allowed + allowBuilds = 5; + # chosen builds requested + choseBuilds = 3; + + nodes = { + a.assertChosen = true; + b.assertChosen = true; + c.assertChosen = true; + }; + }; } sunset ]; diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index b22c521..63fcfae 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -105,11 +105,11 @@ in ''; requestExpressions = pkgs.writeText "guest-request-scope.nix" '' let - pkgs = import /nix/store/r365xbb4lp3h3vzahr97aysrzd4dysis-yqy82fn77fy3rv7lpwa9m11w3a2nnqg5-source { }; - config = builtins.fromJSON (builtins.readFile /nix/store/4jai0ds3f1kswspwdib1ggxwyikv1jhr-nix-dag.json); + pkgs = import ${pkgs.path} { }; + config = builtins.fromJSON (builtins.readFile ${configJson}); testPkgs = import ${expressions}; in - pkgs.lib.attrsets.filterAttrs (name: node: !(config.nodes ? ''${name}) || config.nodes.''${name}.request ) testPkgs + pkgs.lib.attrsets.filterAttrs (name: node: !(config.nodes ? ''${name}) || (config.nodes.''${name} ? request && config.nodes.''${name}.request)) testPkgs ''; tester = pkgs.writers.writePython3Bin "dag-test" { } '' @@ -222,12 +222,27 @@ in assert len(need_builds) == expected_need_builds, f"{len(need_builds)} != {expected_need_builds}; building {need_builds}" print("Verified `needBuilds`", file=sys.stderr) - if config.get("allowBuilds", None) is not None and config.get("choseBuilds", None) is not None: - p = subprocess.run(["evanix", "${requestExpressions}", "--dry-run", "--solver=highs", "--max-build", str(config["allowBuilds"])], check=True, stdout=subprocess.PIPE) - output = p.stdout.decode("utf-8") - evanix_builds = parse_evanix_dry_run(output) + evanix_args = ["evanix", "${requestExpressions}", "--dry-run", "--close-unused-fd", "false"] + if config.get("allowBuilds", None) is not None: + evanix_args.extend(["--solver=highs", "--max-build", str(config["allowBuilds"])]) + + evanix = subprocess.run(evanix_args, check=True, stdout=subprocess.PIPE) + evanix_output = evanix.stdout.decode("utf-8") + evanix_builds = parse_evanix_dry_run(evanix_output) + + if config.get("choseBuilds", None) is not None: assert len(evanix_builds) == config["choseBuilds"], f"len({evanix_builds}) != choseBuilds" print("Verified `choseBuilds`", file=sys.stderr) + + hadAssertChosen = False + for name, node in nodes.items(): + if "assertChosen" not in node or not node["assertChosen"]: + continue + else: + hadAssertChosen = True + assert name in evanix_builds, f"{name}.assertChosen failed" + if hadAssertChosen: + print("Verified `assertChosen`", file=sys.stderr) ''; in { @@ -238,8 +253,6 @@ in (builtins.map ({ name, ... }: scope.${name})) ] ++ [ - expressions - requestExpressions pkgs.path # Cache runCommand's dependencies such as runtimeShell -- cgit v1.2.3 From b2e4a13e03fef3fa13f7f766f19c6b1432380582 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Wed, 14 Aug 2024 07:41:50 +0530 Subject: nixosTests/dsl: clean up --- nixos/tests/dsl.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index 63fcfae..eb9f38c 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -92,7 +92,7 @@ in config.nodes.builder = { pkgs, ... }: let - evanixPkg = pkgs.callPackage ../../package.nix { }; + evanix = pkgs.callPackage ../../package.nix { }; scope = pkgs.lib.makeScope pkgs.newScope scope-fun; configJson = (pkgs.formats.json { }).generate "nix-dag.json" config.dag; @@ -269,7 +269,7 @@ in environment.systemPackages = [ tester - evanixPkg + evanix ]; }; config.nodes.substituter = -- cgit v1.2.3 From 6b11c34c05ea3d4988617901a12ec1b773fbb6f6 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Wed, 14 Aug 2024 09:41:32 +0530 Subject: nixosTests/dsl: verify assertNeeded --- nixos/tests/dsl.nix | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index eb9f38c..b0dd006 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -173,11 +173,13 @@ in raise RuntimeError("nix-build --dry-run produced invalid output", line) return to_fetch, to_build + nix_build_needed = set() drv_to_schedule = {} for name, node in nodes.items(): p = subprocess.run(["nix-build", "${expressions}", "--dry-run", "--show-trace", "-A", name], check=True, stderr=subprocess.PIPE) output = p.stderr.decode("utf-8") to_fetch, to_build = parse_dry_run(output) + nix_build_needed.update(to_build) drv_to_schedule[name] = (to_fetch, to_build) drv_to_action = {} @@ -222,27 +224,38 @@ in assert len(need_builds) == expected_need_builds, f"{len(need_builds)} != {expected_need_builds}; building {need_builds}" print("Verified `needBuilds`", file=sys.stderr) + assertNeededNodes = set() + for name, node in nodes.items(): + if "assertNeeded" in node and node["assertNeeded"]: + assertNeededNodes.add(name) + if assertNeededNodes: + for name in assertNeededNodes: + assert name in nix_build_needed, f"{name}.assertNeeded failed" + print("Verified `assertNeededNodes`", file=sys.stderr) + + assertChosenNodes = set() + for name, node in nodes.items(): + if "assertChosen" in node and node["assertChosen"]: + assertChosenNodes.add(name) + evanix_args = ["evanix", "${requestExpressions}", "--dry-run", "--close-unused-fd", "false"] if config.get("allowBuilds", None) is not None: evanix_args.extend(["--solver=highs", "--max-build", str(config["allowBuilds"])]) - evanix = subprocess.run(evanix_args, check=True, stdout=subprocess.PIPE) - evanix_output = evanix.stdout.decode("utf-8") - evanix_builds = parse_evanix_dry_run(evanix_output) + if assertChosenNodes or config.get("choseBuilds", None) is not None: + evanix = subprocess.run(evanix_args, check=True, stdout=subprocess.PIPE) + evanix_output = evanix.stdout.decode("utf-8") + evanix_builds = parse_evanix_dry_run(evanix_output) if config.get("choseBuilds", None) is not None: assert len(evanix_builds) == config["choseBuilds"], f"len({evanix_builds}) != choseBuilds" print("Verified `choseBuilds`", file=sys.stderr) - hadAssertChosen = False - for name, node in nodes.items(): - if "assertChosen" not in node or not node["assertChosen"]: - continue - else: - hadAssertChosen = True - assert name in evanix_builds, f"{name}.assertChosen failed" - if hadAssertChosen: - print("Verified `assertChosen`", file=sys.stderr) + if assertChosenNodes: + for name in assertChosenNodes: + assert name in evanix_builds and name in nix_build_needed, f"{name}.assertChosen failed" + print("Verified `assertChosenNodes`", file=sys.stderr) + ''; in { -- cgit v1.2.3 From 57caa680338dbabeae0db3bd8f097e0328a45d30 Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Wed, 14 Aug 2024 13:07:01 +0530 Subject: nixosTests/sunset-unbuilt-0: set assertNeeded --- nixos/tests/all-tests.nix | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 158c025..f03fc20 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -114,9 +114,25 @@ builtins.mapAttrs choseBuilds = 3; nodes = { - a.assertChosen = true; - b.assertChosen = true; - c.assertChosen = true; + a = { + assertChosen = true; + assertNeeded = true; + }; + b = { + assertChosen = true; + assertNeeded = true; + }; + c = { + assertChosen = true; + assertNeeded = true; + }; + + d.assertNeeded = true; + e.assertNeeded = true; + u.assertNeeded = true; + v.assertNeeded = true; + w.assertNeeded = true; + x.assertNeeded = true; }; }; } -- cgit v1.2.3 From e183d71dfc4b0e4b8eaec7c8c919b6f9632df961 Mon Sep 17 00:00:00 2001 From: Someone Serge Date: Thu, 15 Aug 2024 02:45:57 +0000 Subject: nixosTests: make options hierarchical --- flake.nix | 2 + nixos/tests/all-tests.nix | 66 ++++++++++++++--------------- nixos/tests/dsl.nix | 106 +++++++++++++++++++++++++++------------------- 3 files changed, 96 insertions(+), 78 deletions(-) diff --git a/flake.nix b/flake.nix index 10314f9..5014f4d 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,8 @@ clang-tools # clang-format flamegraph linuxKernel.packages.linux_6_6.perf + hyperfine + nix-eval-jobs ]; shellHook = '' diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f03fc20..c6b66f1 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -23,24 +23,24 @@ let in { a = { - request = true; + goal = true; inputs = abcInputs; }; b = { - request = true; + goal = true; inputs = abcInputs; }; c = { - request = true; + goal = true; inputs = abcInputs; }; d = { - request = true; + goal = true; inputs.w = { }; }; e = { - request = true; + goal = true; inputs.x = { }; }; @@ -73,8 +73,8 @@ builtins.mapAttrs diamond-unbuilt-0 = { imports = [ { - dag.needBuilds = 0; - dag.needDownloads = 0; + dag.test.unconstrained.builds = 0; + dag.test.unconstrained.downloads = 0; } diamond ]; @@ -84,9 +84,9 @@ builtins.mapAttrs { dag.nodes.a.cache = "remote"; dag.nodes.b.cache = "remote"; - dag.nodes.d.request = true; - dag.needBuilds = 2; - dag.needDownloads = 2; + dag.nodes.d.goal = true; + dag.test.unconstrained.builds = 2; + dag.test.unconstrained.downloads = 2; } diamond ]; @@ -94,9 +94,9 @@ builtins.mapAttrs diamond-unbuilt-4 = { imports = [ { - dag.nodes.d.request = true; - dag.needBuilds = 4; - dag.needDownloads = 0; + dag.nodes.d.goal = true; + dag.test.unconstrained.builds = 4; + dag.test.unconstrained.downloads = 0; } diamond ]; @@ -106,33 +106,31 @@ builtins.mapAttrs imports = [ { dag = { - # all builds - needBuilds = 9; - # all builds allowed - allowBuilds = 5; - # chosen builds requested - choseBuilds = 3; + test.unconstrained.builds = 9; + + constraints.builds = 5; + test.constrained.builds = 3; nodes = { - a = { - assertChosen = true; - assertNeeded = true; + a.test = { + chosen = true; + needed = true; }; - b = { - assertChosen = true; - assertNeeded = true; + b.test = { + chosen = true; + needed = true; }; - c = { - assertChosen = true; - assertNeeded = true; + c.test = { + chosen = true; + needed = true; }; - d.assertNeeded = true; - e.assertNeeded = true; - u.assertNeeded = true; - v.assertNeeded = true; - w.assertNeeded = true; - x.assertNeeded = true; + d.test.needed = true; + e.test.needed = true; + u.test.needed = true; + v.test.needed = true; + w.test.needed = true; + x.test.needed = true; }; }; } diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index b0dd006..acf15c0 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -17,13 +17,13 @@ let type = lib.types.str; default = name; }; - options.request = lib.mkEnableOption "Whether to mark the node for building"; - options.assertNeeded = lib.mkOption { + options.goal = lib.mkEnableOption ''Mark for building (node is a "goal", "target")''; + options.test.needed = lib.mkOption { type = with lib.types; nullOr bool; default = null; - description = "Whether the node must be built to satisfy all requests (either a requested node or a transitive dependency)"; + description = "Verify `nix-build --dry-run` reports node as any of to-be built or to-be fetched"; }; - options.assertChosen = lib.mkOption { + options.test.chosen = lib.mkOption { type = with lib.types; nullOr bool; default = null; description = "Whether the node is included in the build plan (i.t. it's `needed` and fitted into budget)"; @@ -57,32 +57,32 @@ in type = Nodes; description = "Derivation DAG, including cache status and references."; }; - needBuilds = lib.mkOption { + test.unconstrained.builds = lib.mkOption { type = with lib.types; nullOr int; default = null; description = "How many builds are required to satisfy all targets. Null disables the test"; }; - needDownloads = lib.mkOption { + test.unconstrained.downloads = lib.mkOption { type = with lib.types; nullOr int; default = null; description = "How many downloads are required to satisfy all targets. Null disables the test"; }; - choseBuilds = lib.mkOption { + test.constrained.builds = lib.mkOption { type = with lib.types; nullOr int; default = null; description = "How many builds we expect evanix to choose to satisfy the maximum number of targets within the given budget. Null disables the test"; }; - choseDownloads = lib.mkOption { + test.constrained.downloads = lib.mkOption { type = with lib.types; nullOr int; default = null; description = "How many downloads we expect evanix to choose to satisfy the maximum number of targets within the given budget. Null disables the test"; }; - allowBuilds = lib.mkOption { + constraints.builds = lib.mkOption { type = with lib.types; nullOr int; default = null; description = "How many builds evanix is allowed to choose. Null means no constraint"; }; - allowDownloads = lib.mkOption { + constraints.downloads = lib.mkOption { type = with lib.types; nullOr int; default = null; description = "How many downloads evanix is allowed to choose. Null means no constraint"; @@ -105,11 +105,16 @@ in ''; requestExpressions = pkgs.writeText "guest-request-scope.nix" '' let + inherit (pkgs) lib; pkgs = import ${pkgs.path} { }; config = builtins.fromJSON (builtins.readFile ${configJson}); - testPkgs = import ${expressions}; + all = import ${expressions}; + subset = lib.pipe all [ + (lib.filterAttrs (name: node: lib.isDerivation node)) + (lib.filterAttrs (name: node: config.nodes.''${name}.goal)) + ]; in - pkgs.lib.attrsets.filterAttrs (name: node: !(config.nodes ? ''${name}) || (config.nodes.''${name} ? request && config.nodes.''${name}.request)) testPkgs + subset ''; tester = pkgs.writers.writePython3Bin "dag-test" { } '' @@ -120,9 +125,11 @@ in import subprocess import sys + with open("${configJson}", "r") as f: config = json.load(f) + nodes = config["nodes"] print(f"config={config}", file=sys.stderr) @@ -173,13 +180,11 @@ in raise RuntimeError("nix-build --dry-run produced invalid output", line) return to_fetch, to_build - nix_build_needed = set() drv_to_schedule = {} for name, node in nodes.items(): p = subprocess.run(["nix-build", "${expressions}", "--dry-run", "--show-trace", "-A", name], check=True, stderr=subprocess.PIPE) output = p.stderr.decode("utf-8") to_fetch, to_build = parse_dry_run(output) - nix_build_needed.update(to_build) drv_to_schedule[name] = (to_fetch, to_build) drv_to_action = {} @@ -210,52 +215,65 @@ in else: raise AssertionError('cache is not in [ "local", "remote", "unbuilt" ]') - need_dls, need_builds = set(), set() + need_builds: set[str] = set() + need_dls: set[str] = set() for name, node in nodes.items(): - if node["request"]: + if node["goal"]: need_dls.update(drv_to_schedule[name][0]) need_builds.update(drv_to_schedule[name][1]) - if (expected_need_dls := config.get("needDownloads", None)) is not None: + if (expected_need_dls := config["test"]["unconstrained"]["downloads"]) is not None: assert len(need_dls) == expected_need_dls, f"{len(need_dls)} != {expected_need_dls}; building {need_dls}" print("Verified `needDownloads`", file=sys.stderr) - if (expected_need_builds := config.get("needBuilds", None)) is not None: + if (expected_need_builds := config["test"]["unconstrained"]["builds"]) is not None: assert len(need_builds) == expected_need_builds, f"{len(need_builds)} != {expected_need_builds}; building {need_builds}" print("Verified `needBuilds`", file=sys.stderr) - assertNeededNodes = set() - for name, node in nodes.items(): - if "assertNeeded" in node and node["assertNeeded"]: - assertNeededNodes.add(name) - if assertNeededNodes: - for name in assertNeededNodes: - assert name in nix_build_needed, f"{name}.assertNeeded failed" - print("Verified `assertNeededNodes`", file=sys.stderr) - - assertChosenNodes = set() for name, node in nodes.items(): - if "assertChosen" in node and node["assertChosen"]: - assertChosenNodes.add(name) + if node["test"]["needed"]: + assert name in need_builds or name in need_dls, f"{name}.test.needed violated" - evanix_args = ["evanix", "${requestExpressions}", "--dry-run", "--close-unused-fd", "false"] - if config.get("allowBuilds", None) is not None: - evanix_args.extend(["--solver=highs", "--max-build", str(config["allowBuilds"])]) - if assertChosenNodes or config.get("choseBuilds", None) is not None: - evanix = subprocess.run(evanix_args, check=True, stdout=subprocess.PIPE) - evanix_output = evanix.stdout.decode("utf-8") - evanix_builds = parse_evanix_dry_run(evanix_output) + evanix_args = ["evanix", "${requestExpressions}", "--dry-run", "--close-unused-fd", "false"] + if (allow_builds := config["constraints"]["builds"]) is not None: + evanix_args.extend(["--solver=highs", "--max-build", str(allow_builds)]) - if config.get("choseBuilds", None) is not None: - assert len(evanix_builds) == config["choseBuilds"], f"len({evanix_builds}) != choseBuilds" - print("Verified `choseBuilds`", file=sys.stderr) + expect_chosen_nodes = set(name for name, node in nodes.items() if node["test"]["chosen"]) + expect_n_chosen_builds = config["test"]["constrained"]["builds"] + expect_n_chosen_downloads = config["test"]["constrained"]["downloads"] - if assertChosenNodes: - for name in assertChosenNodes: - assert name in evanix_builds and name in nix_build_needed, f"{name}.assertChosen failed" - print("Verified `assertChosenNodes`", file=sys.stderr) + # TODO: Add option + if expect_n_chosen_downloads is not None and expect_n_chosen_builds is not None: + expect_n_chosen_nodes = expect_n_chosen_builds + expect_n_chosen_downloads + else: + expect_n_chosen_nodes = None + if expect_chosen_nodes or expect_n_chosen_nodes is not None: + evanix = subprocess.run(evanix_args, check=True, stdout=subprocess.PIPE) + evanix_output = evanix.stdout.decode("utf-8") + evanix_choices = parse_evanix_dry_run(evanix_output) + else: + evanix_choices = set() + + evanix_builds, evanix_downloads = [], [] + for choice in evanix_choices: + if drv_to_action[choice] == "build": + evanix_builds.append(choice) + elif drv_to_action[choice] == "fetch": + evanix_downloads.append(choice) + + if expect_n_chosen_nodes is not None: + assert len(evanix_choices) == expect_n_chosen_nodes, f"len({evanix_builds}) != choseNodes" + print("Verified `choseNodes`", file=sys.stderr) + + if expect_chosen_nodes: + for name in expect_chosen_nodes: + assert name in evanix_choices, f"{name}.test.chosen failed; choices: {evanix_choices}" + print("Verified `expect_chosen_nodes`", file=sys.stderr) + + assert expect_n_chosen_builds is None or len(evanix_builds) == expect_n_chosen_builds, f"{expect_n_chosen_builds=} {len(evanix_builds)=}" + assert expect_n_chosen_downloads is None or len(evanix_downloads) == expect_n_chosen_downloads, f"{expect_n_chosen_downloads=} {len(evanix_downloads)=}" ''; in { -- cgit v1.2.3 From b895c640d549e7a58127615862d05a5044ac8555 Mon Sep 17 00:00:00 2001 From: Someone Serge Date: Fri, 16 Aug 2024 15:28:07 +0000 Subject: nixosTests: sunset-unbuilt: rename to avoid confusion --- nixos/tests/all-tests.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index c6b66f1..7002237 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -102,7 +102,7 @@ builtins.mapAttrs ]; }; - sunset-unbuilt-0 = { + sunset-unbuilt-9 = { imports = [ { dag = { -- cgit v1.2.3 From b5e749c4d620b37b3b52b3ffdb0c69ec3d96fb6e Mon Sep 17 00:00:00 2001 From: Someone Serge Date: Fri, 16 Aug 2024 15:28:28 +0000 Subject: nixosTests: sunset-unbuilt: refactor the dag --- nixos/tests/all-tests.nix | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 7002237..f9e2df5 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1,4 +1,4 @@ -{ testers }: +{ lib, testers }: let dsl = ./dsl.nix; @@ -16,33 +16,17 @@ let sunset.dag = { nodes = let - abcInputs = { - u = { }; - v = { }; + goalDependsOn = inputs: { + goal = true; + inputs = lib.genAttrs inputs (_: { }); }; in { - a = { - goal = true; - inputs = abcInputs; - }; - b = { - goal = true; - inputs = abcInputs; - }; - c = { - goal = true; - inputs = abcInputs; - }; - - d = { - goal = true; - inputs.w = { }; - }; - e = { - goal = true; - inputs.x = { }; - }; + a = goalDependsOn [ "u" "v" ]; + b = goalDependsOn [ "u" "v" ]; + c = goalDependsOn [ "u" "v" ]; + d = goalDependsOn [ "w" ]; + e = goalDependsOn [ "x" ]; u = { }; v = { }; -- cgit v1.2.3 From ac770cfbb032d36b6132d407381cc5d65c5f011e Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Fri, 16 Aug 2024 21:34:28 +0530 Subject: nixosTests/dsl: clean up --- nixos/tests/dsl.nix | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nixos/tests/dsl.nix b/nixos/tests/dsl.nix index acf15c0..d2a4420 100644 --- a/nixos/tests/dsl.nix +++ b/nixos/tests/dsl.nix @@ -96,19 +96,19 @@ in scope = pkgs.lib.makeScope pkgs.newScope scope-fun; configJson = (pkgs.formats.json { }).generate "nix-dag.json" config.dag; - expressions = pkgs.writeText "guest-scope.nix" '' + allPackages = pkgs.writeText "guest-scope.nix" '' let pkgs = import ${pkgs.path} { }; config = builtins.fromJSON (builtins.readFile ${configJson}); in pkgs.lib.makeScope pkgs.newScope (pkgs.callPackage ${./scope-fun.nix} { inherit (pkgs) lib; inherit (config) nodes; }) ''; - requestExpressions = pkgs.writeText "guest-request-scope.nix" '' + targets = pkgs.writeText "guest-request-scope.nix" '' let inherit (pkgs) lib; pkgs = import ${pkgs.path} { }; config = builtins.fromJSON (builtins.readFile ${configJson}); - all = import ${expressions}; + all = import ${allPackages}; subset = lib.pipe all [ (lib.filterAttrs (name: node: lib.isDerivation node)) (lib.filterAttrs (name: node: config.nodes.''${name}.goal)) @@ -182,7 +182,7 @@ in drv_to_schedule = {} for name, node in nodes.items(): - p = subprocess.run(["nix-build", "${expressions}", "--dry-run", "--show-trace", "-A", name], check=True, stderr=subprocess.PIPE) + p = subprocess.run(["nix-build", "${allPackages}", "--dry-run", "--show-trace", "-A", name], check=True, stderr=subprocess.PIPE) output = p.stderr.decode("utf-8") to_fetch, to_build = parse_dry_run(output) drv_to_schedule[name] = (to_fetch, to_build) @@ -235,7 +235,7 @@ in assert name in need_builds or name in need_dls, f"{name}.test.needed violated" - evanix_args = ["evanix", "${requestExpressions}", "--dry-run", "--close-unused-fd", "false"] + evanix_args = ["evanix", "${targets}", "--dry-run", "--close-unused-fd", "false"] if (allow_builds := config["constraints"]["builds"]) is not None: evanix_args.extend(["--solver=highs", "--max-build", str(allow_builds)]) @@ -294,8 +294,8 @@ in nix.settings.substituters = lib.mkForce [ "http://substituter" ]; systemd.tmpfiles.settings."10-expressions" = { "/run/dag-test/nix-dag-test.json"."L+".argument = "${configJson}"; - "/run/dag-test/scope.nix"."L+".argument = "${expressions}"; - "/run/dag-test/requestScope.nix"."L+".argument = "${requestExpressions}"; + "/run/dag-test/all-packages.nix"."L+".argument = "${allPackages}"; + "/run/dag-test/targets.nix"."L+".argument = "${targets}"; }; environment.systemPackages = [ -- cgit v1.2.3 From 838f0e7effc9f49afd7585997b16ca5531def2e0 Mon Sep 17 00:00:00 2001 From: SomeoneSerge Date: Fri, 16 Aug 2024 16:26:07 +0000 Subject: nixosTests: refactor the way common template and per-instance args are handled --- nixos/tests/all-tests.nix | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f9e2df5..adeaf65 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -41,16 +41,14 @@ builtins.mapAttrs testers.runNixOSTest ( { inherit name; + imports = value.imports ++ [ dsl ]; testScript = '' start_all() substituter.wait_for_unit("nix-serve.service") builder.succeed("dag-test") ''; } - // value - // { - imports = value.imports ++ [ dsl ]; - } + // builtins.removeAttrs value [ "imports" ] ) ) { -- cgit v1.2.3 From 59b1063ec5174bc0848f4bea37c8b603e23aa967 Mon Sep 17 00:00:00 2001 From: SomeoneSerge Date: Fri, 16 Aug 2024 16:28:09 +0000 Subject: nixosTests: allow extending testScript without overriding it --- nixos/tests/all-tests.nix | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index adeaf65..b79b198 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -42,13 +42,20 @@ builtins.mapAttrs { inherit name; imports = value.imports ++ [ dsl ]; - testScript = '' - start_all() - substituter.wait_for_unit("nix-serve.service") - builder.succeed("dag-test") - ''; + testScript = + value.testScriptPre or "" + + '' + start_all() + substituter.wait_for_unit("nix-serve.service") + builder.succeed("dag-test") + '' + + value.testScriptPost or ""; } - // builtins.removeAttrs value [ "imports" ] + // builtins.removeAttrs value [ + "imports" + "testScriptPre" + "testScriptPost" + ] ) ) { -- cgit v1.2.3