|
47 | 47 | prevEvalTests.strongEvalTests |
48 | 48 | ); |
49 | 49 |
|
| 50 | + exhibitingInfiniteRecursion = false; |
| 51 | + exhibitInfiniteRecursion = evalTests.extendEvalTests |
| 52 | + (finalEvalTest: prevEvalTests: { exhibitingInfiniteRecursion = true; }); |
| 53 | + |
50 | 54 | pkg = system: name: derivation { |
51 | 55 | name = name; |
52 | 56 | builder = "no-builder"; |
|
79 | 83 | sourceInfo.outPath = "/unknown_eval-tests_flake"; |
80 | 84 | result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; }; |
81 | 85 | in result; |
| 86 | + runEmptyTests = ok: |
| 87 | + assert evalTests.empty == evalTests.emptyResult; |
| 88 | + ok; |
| 89 | + emptyTestsResult = evalTests.runEmptyTests "ok"; |
| 90 | + |
| 91 | + tooEmpty = evalTests.callFlake { |
| 92 | + inputs.flake-parts = evalTests.flake-parts; |
| 93 | + inputs.self = { }; |
| 94 | + outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { |
| 95 | + }; |
| 96 | + }; |
| 97 | + # Shallow evaluation is successful… |
| 98 | + weakEvalTests.tooEmptyResultTried0.success = true; |
| 99 | + weakEvalTests.tooEmptyResultTried0.value = { }; |
| 100 | + weakEvalTests.tooEmptyResultTried0TestTried.success = true; |
| 101 | + weakEvalTests.tooEmptyResultTried0TestTried.value = false; |
| 102 | + # …including for flake outputs… |
| 103 | + strongEvalTests.tooEmptyResultTried0 = evalTests.weakEvalTests.tooEmptyResultTried0; |
| 104 | + strongEvalTests.tooEmptyResultTried0TestTried = evalTests.weakEvalTests.tooEmptyResultTried0TestTried; |
| 105 | + # …but any evaluations of attribute values (flake output values) are not. |
| 106 | + weakEvalTests.tooEmptyResultTried1.success = true; |
| 107 | + weakEvalTests.tooEmptyResultTried1.value = { |
| 108 | + apps = { }; |
| 109 | + checks = { }; |
| 110 | + devShells = { }; |
| 111 | + formatter = { }; |
| 112 | + legacyPackages = { }; |
| 113 | + nixosConfigurations = { }; |
| 114 | + nixosModules = { }; |
| 115 | + overlays = { }; |
| 116 | + packages = { }; |
| 117 | + }; |
| 118 | + weakEvalTests.tooEmptyResultTried1TestTried.success = false; |
| 119 | + weakEvalTests.tooEmptyResultTried1TestTried.value = false; |
| 120 | + strongEvalTests.tooEmptyResultTried1.success = true; |
| 121 | + strongEvalTests.tooEmptyResultTried1.value = let |
| 122 | + _type = "flake"; |
| 123 | + inputs.flake-parts = evalTests.flake-parts; |
| 124 | + inputs.self = { }; |
| 125 | + outputs = evalTests.weakEvalTests.tooEmptyResultTried1.value; |
| 126 | + sourceInfo.outPath = "/unknown_eval-tests_flake"; |
| 127 | + result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; }; |
| 128 | + in result; |
| 129 | + strongEvalTests.tooEmptyResultTried1TestTried.success = false; |
| 130 | + strongEvalTests.tooEmptyResultTried1TestTried.value = false; |
| 131 | + runTooEmptyTests = ok: |
| 132 | + let |
| 133 | + tooEmptyResultTried = builtins.tryEval evalTests.tooEmpty; |
| 134 | + tooEmptyResultTried0TestTried = builtins.tryEval (tooEmptyResultTried == evalTests.tooEmptyResultTried0); |
| 135 | + tooEmptyResultTried1TestTried = builtins.tryEval (tooEmptyResultTried == evalTests.tooEmptyResultTried1); |
| 136 | + in |
| 137 | + assert tooEmptyResultTried0TestTried == evalTests.tooEmptyResultTried0TestTried; |
| 138 | + assert tooEmptyResultTried1TestTried == evalTests.tooEmptyResultTried1TestTried; |
| 139 | + ok; |
| 140 | + tooEmptyTestsResult = evalTests.runTooEmptyTests "ok"; |
82 | 141 |
|
83 | 142 | example1 = evalTests.callFlake { |
84 | 143 | inputs.flake-parts = evalTests.flake-parts; |
|
112 | 171 | sourceInfo.outPath = "/unknown_eval-tests_flake"; |
113 | 172 | result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; }; |
114 | 173 | in result; |
| 174 | + runExample1Tests = ok: |
| 175 | + assert evalTests.example1 == evalTests.example1Result; |
| 176 | + ok; |
| 177 | + example1TestsResult = evalTests.runExample1Tests "ok"; |
| 178 | + |
| 179 | + # This test case is a fun one. In the REPL, try `exhibitInfiniteRecursion.*`. |
| 180 | + # In the case that `mkFlake` *isn't* called from a flake, `inputs.self` is |
| 181 | + # unlikely to refer to the result of the `mkFlake` evaluation. If |
| 182 | + # `inputs.self` isn't actually self-referential, evaluating attribute values |
| 183 | + # of `self` is not divergent. Evaluation of `self.outPath` is useful for |
| 184 | + # paths in documentation & error messages. However, if that evaluation occurs |
| 185 | + # in a `builtins.addErrorContext` message forced by an erroring `self`, both |
| 186 | + # `self` will never evaluate *and* `builtins.toString self.outPath` must |
| 187 | + # evaluate, causing Nix to instead throw an infinite recursion error. Even |
| 188 | + # just `inputs.self ? outPath` throws an infinite recursion error. |
| 189 | + # (`builtins.tryEval` can only catch errors created by `builtins.throw` or |
| 190 | + # `builtins.assert`, so evaluation is guarded with |
| 191 | + # `exhibitingInfiniteRecursion` here to keep `runTests` from diverging.) |
| 192 | + # In this particular case, `mkFlake` evaluates `self ? outPath` to know if the |
| 193 | + # default module location it provides should be generic or specific. As |
| 194 | + # explained, this evaluation is unsafe under an uncatchably divergent `self`. |
| 195 | + # Thus, `outPath` cannot be safely sourced from `self` at the top-level. |
| 196 | + # |
| 197 | + # When tests are exhibititing infinite recursion, the abnormally correct |
| 198 | + # `self` is provided. |
| 199 | + weakEvalTests.nonexistentOption = let result = evalTests.callFlake { |
| 200 | + inputs.flake-parts = evalTests.flake-parts; |
| 201 | + inputs.self = if !evalTests.exhibitingInfiniteRecursion then { } else result; |
| 202 | + outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { |
| 203 | + config.systems = [ ]; |
| 204 | + config.nonexistentOption = null; |
| 205 | + }; |
| 206 | + }; in result; |
| 207 | + # When using actual flakes, this test always diverges. Unless tests are |
| 208 | + # exhibiting infinite recursion, the flake is made equivalent to `empty`. |
| 209 | + strongEvalTests.nonexistentOption = evalTests.callFlake { |
| 210 | + inputs.flake-parts = evalTests.flake-parts; |
| 211 | + inputs.self = { }; |
| 212 | + outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } ({ |
| 213 | + config.systems = [ ]; |
| 214 | + } // (if !evalTests.exhibitingInfiniteRecursion then { } else { |
| 215 | + config.nonexistentOption = null; |
| 216 | + })); |
| 217 | + }; |
| 218 | + weakEvalTests.nonexistentOptionResultTried0.success = true; |
| 219 | + weakEvalTests.nonexistentOptionResultTried0.value = { }; |
| 220 | + weakEvalTests.nonexistentOptionResultTried0TestTried.success = true; |
| 221 | + weakEvalTests.nonexistentOptionResultTried0TestTried.value = false; |
| 222 | + strongEvalTests.nonexistentOptionResultTried0 = evalTests.weakEvalTests.nonexistentOptionResultTried0; |
| 223 | + strongEvalTests.nonexistentOptionResultTried0TestTried = evalTests.weakEvalTests.nonexistentOptionResultTried0TestTried; |
| 224 | + runNonexistentOptionTests = ok: |
| 225 | + let |
| 226 | + nonexistentOptionResultTried = builtins.tryEval evalTests.nonexistentOption; |
| 227 | + nonexistentOptionResultTried0TestTried = builtins.tryEval (nonexistentOptionResultTried == evalTests.nonexistentOptionResultTried0); |
| 228 | + in |
| 229 | + assert nonexistentOptionResultTried0TestTried == evalTests.nonexistentOptionResultTried0TestTried; |
| 230 | + ok; |
| 231 | + nonexistentOptionTestsResult = evalTests.runNonexistentOptionTests "ok"; |
115 | 232 |
|
116 | 233 | packagesNonStrictInDevShells = evalTests.callFlake { |
117 | 234 | inputs.flake-parts = evalTests.flake-parts; |
|
170 | 287 | imports = [ inputs.flakeModulesDeclare.flakeModules.default ]; |
171 | 288 | }; |
172 | 289 | }; |
| 290 | + runFlakeModulesImportTests = ok: |
| 291 | + assert evalTests.flakeModulesImport.test123 == "123test"; |
| 292 | + ok; |
| 293 | + flakeModulesImportTestsResult = evalTests.runFlakeModulesImportTests "ok"; |
173 | 294 |
|
174 | 295 | flakeModulesDisable = evalTests.callFlake { |
175 | 296 | inputs.flake-parts = evalTests.flake-parts; |
|
180 | 301 | disabledModules = [ inputs.flakeModulesDeclare.flakeModules.extra ]; |
181 | 302 | }; |
182 | 303 | }; |
| 304 | + runFlakeModulesDisableTests = ok: |
| 305 | + assert evalTests.flakeModulesDisable.test123 == "option123"; |
| 306 | + ok; |
| 307 | + flakeModulesDisableTestsResult = evalTests.runFlakeModulesDisableTests "ok"; |
183 | 308 |
|
184 | 309 | nixpkgsWithoutEasyOverlay = import evalTests.nixpkgs { |
185 | 310 | system = "x86_64-linux"; |
|
201 | 326 | config = { }; |
202 | 327 | }; |
203 | 328 |
|
| 329 | + tryEvalOutputs = outputs: builtins.seq (builtins.attrNames outputs) outputs; |
| 330 | + |
204 | 331 | runTests = ok: |
205 | 332 |
|
206 | | - assert evalTests.empty == evalTests.emptyResult; |
| 333 | + assert evalTests.runEmptyTests true; |
207 | 334 |
|
208 | | - assert evalTests.example1 == evalTests.example1Result; |
| 335 | + assert evalTests.runTooEmptyTests true; |
| 336 | + |
| 337 | + assert evalTests.runExample1Tests true; |
| 338 | + |
| 339 | + assert evalTests.runNonexistentOptionTests true; |
209 | 340 |
|
210 | 341 | # - exported package becomes part of overlay. |
211 | 342 | # - perSystem is invoked for the right system, when system is non-memoized |
|
223 | 354 | # - `hello_new` shows that the `final` wiring works |
224 | 355 | assert evalTests.nixpkgsWithEasyOverlay.hello_new == evalTests.nixpkgsWithEasyOverlay.hello; |
225 | 356 |
|
226 | | - assert evalTests.flakeModulesImport.test123 == "123test"; |
| 357 | + assert evalTests.runFlakeModulesImportTests true; |
227 | 358 |
|
228 | | - assert evalTests.flakeModulesDisable.test123 == "option123"; |
| 359 | + assert evalTests.runFlakeModulesDisableTests true; |
229 | 360 |
|
230 | 361 | assert evalTests.packagesNonStrictInDevShells.packages.a.default == evalTests.pkg "a" "hello"; |
231 | 362 |
|
|
0 commit comments