summaryrefslogtreecommitdiff
path: root/pkgs/test/buildenv.nix
blob: f47abe2464461affa8576244d745193807aa64c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
{
  lib,
  pkgs,
  stdenvNoCC,
}:

let
  inherit (pkgs) buildEnv;

  testingThrow = expr: {
    expr = (builtins.tryEval (builtins.seq expr "didn't throw"));
    expected = {
      success = false;
      value = false;
    };
  };

  tests-name = {
    testNameFromNameArg = {
      expr =
        (buildEnv {
          name = "test-env";
          paths = [ ];
        }).name;
      expected = "test-env";
    };

    testNameFromPnameVersion = {
      expr =
        (buildEnv {
          pname = "test-env";
          version = "1.0";
          paths = [ ];
        }).name;
      expected = "test-env-1.0";
    };
  };

  tests-passthru-paths = {
    testPathsInPassthru = {
      expr =
        let
          env = buildEnv {
            name = "test-env";
            paths = [ pkgs.hello ];
          };
        in
        builtins.length env.paths > 0;
      expected = true;
    };

    testPassthruPathsOverridable = {
      expr =
        let
          env = buildEnv {
            name = "test-env";
            paths = [ pkgs.hello ];
          };
          overridden = env.overrideAttrs {
            passthru.paths = [ pkgs.figlet ];
          };
        in
        builtins.length overridden.paths == 1;
      expected = true;
    };
  };

  tests-finalAttrs = {
    testFinalAttrsSelfReference = {
      expr =
        let
          env = buildEnv (finalAttrs: {
            name = "test-env";
            paths = [ ];
            passthru.description = "An env named ${finalAttrs.name}";
          });
        in
        env.description;
      expected = "An env named test-env";
    };
  };

  tests-overrideAttrs =
    let
      base = buildEnv {
        name = "test-env";
        paths = [ pkgs.hello ];
        passthru.custom = "original";
      };
      overridden = base.overrideAttrs (
        finalAttrs: prev: {
          passthru = prev.passthru // {
            custom = "modified";
          };
        }
      );
    in
    {
      testOverrideAttrsChangesPassthru = {
        expr = overridden.custom;
        expected = "modified";
      };

      testOverrideAttrsPreservesName = {
        expr = overridden.name;
        expected = "test-env";
      };

      testOverrideAttrsAffectsDrv = {
        expr =
          let
            withPostBuild = base.overrideAttrs { postBuild = "echo overridden"; };
          in
          base.drvPath != withPostBuild.drvPath;
        expected = true;
      };
    };

  tests-passthru-merging =
    let
      env = buildEnv {
        name = "test-env";
        paths = [ pkgs.hello ];
        derivationArgs.passthru.fromDerivationArgs = "a";
        passthru.fromPassthru = "b";
      };
    in
    {
      testPassthruMergingAutoPathsPresent = {
        expr = env ? paths;
        expected = true;
      };

      testPassthruMergingDerivationArgs = {
        expr = env.fromDerivationArgs;
        expected = "a";
      };

      testPassthruMergingDirectPassthru = {
        expr = env.fromPassthru;
        expected = "b";
      };

      # Direct passthru takes precedence over derivationArgs.passthru
      testPassthruMergingPrecedence = {
        expr =
          let
            env' = buildEnv {
              name = "test-env";
              paths = [ ];
              derivationArgs.passthru.key = "from-derivationArgs";
              passthru.key = "from-passthru";
            };
          in
          env'.key;
        expected = "from-passthru";
      };
    };

  tests-derivationArgs =
    let
      env = buildEnv {
        name = "test-env";
        paths = [ ];
        derivationArgs.allowSubstitutes = true;
      };
    in
    {
      # derivationArgs.allowSubstitutes overrides the default (false)
      testDerivationArgsForwarded = {
        expr = env.allowSubstitutes;
        expected = true;
      };

      # Backward compat: top-level nativeBuildInputs still works
      testCompatNativeBuildInputs = {
        expr =
          let
            env' = buildEnv {
              name = "test-env";
              paths = [ ];
              nativeBuildInputs = [ pkgs.hello ];
            };
          in
          builtins.length env'.nativeBuildInputs > 0;
        expected = true;
      };
    };

  # Build tests: derivations that build a buildEnv and verify its output.
  # These are exposed via passthru.buildTests and checked in buildCommand.
  buildTests = {
    basic-symlinking =
      pkgs.runCommand "test-buildenv-basic-symlinking"
        {
          testEnv = buildEnv {
            name = "test-env";
            paths = [ pkgs.hello ];
          };
        }
        ''
          # With a single package, buildEnv symlinks the directory itself
          test -L "$testEnv/bin" || { echo "FAIL: $testEnv/bin is not a symlink"; exit 1; }

          # The symlink should point into the store
          target=$(readlink "$testEnv/bin")
          case "$target" in
            /nix/store/*) ;;
            *) echo "FAIL: symlink target '$target' is not a store path"; exit 1 ;;
          esac

          # The binary should be accessible and executable through the symlink
          test -x "$testEnv/bin/hello" || { echo "FAIL: hello binary not executable"; exit 1; }
          "$testEnv/bin/hello" > /dev/null || { echo "FAIL: hello binary did not run"; exit 1; }

          touch $out
        '';

    pathsToLink =
      pkgs.runCommand "test-buildenv-pathsToLink"
        {
          testEnv = buildEnv {
            name = "test-env";
            paths = [ pkgs.hello ];
            pathsToLink = [ "/bin" ];
          };
        }
        ''
          # /bin should exist
          test -d "$testEnv/bin" || { echo "FAIL: $testEnv/bin missing"; exit 1; }

          # Other directories from hello (like /share) should NOT exist
          test ! -e "$testEnv/share" || { echo "FAIL: $testEnv/share should not exist with pathsToLink = [\"/bin\"]"; exit 1; }

          touch $out
        '';

    extraPrefix =
      pkgs.runCommand "test-buildenv-extraPrefix"
        {
          testEnv = buildEnv {
            name = "test-env";
            paths = [ pkgs.hello ];
            extraPrefix = "/myprefix";
          };
        }
        ''
          # Content should be under the extra prefix
          test -e "$testEnv/myprefix/bin/hello" || { echo "FAIL: $testEnv/myprefix/bin/hello missing"; exit 1; }
          test -x "$testEnv/myprefix/bin/hello" || { echo "FAIL: $testEnv/myprefix/bin/hello not executable"; exit 1; }

          # Content should NOT be at the top level
          test ! -e "$testEnv/bin" || { echo "FAIL: $testEnv/bin should not exist at top level with extraPrefix"; exit 1; }

          touch $out
        '';

    postBuild =
      pkgs.runCommand "test-buildenv-postBuild"
        {
          testEnv = buildEnv {
            name = "test-env";
            paths = [ ];
            postBuild = ''
              echo "postBuild was here" > $out/marker
            '';
          };
        }
        ''
          # postBuild should have created the marker file
          test -f "$testEnv/marker" || { echo "FAIL: $testEnv/marker missing; postBuild did not run"; exit 1; }
          content=$(cat "$testEnv/marker")
          test "$content" = "postBuild was here" || { echo "FAIL: marker content wrong: $content"; exit 1; }

          touch $out
        '';

    # buildEnv explicitly sets __structuredAttrs = true because builder.pl
    # reads all inputs from `$NIX_ATTRS_JSON_FILE`.
    # Verify the build succeeds even when derivationArgs tries to disable structuredAttrs.
    structuredAttrs-overridden =
      pkgs.runCommand "test-buildenv-structuredAttrs-overridden"
        {
          testEnv = buildEnv {
            name = "test-env-structuredAttrs";
            paths = [ pkgs.hello ];
            derivationArgs.__structuredAttrs = false;
          };
        }
        ''
          test -x "$testEnv/bin/hello" || { echo "FAIL: hello not present after structuredAttrs override"; exit 1; }
          touch $out
        '';

    ignoreCollisions =
      pkgs.runCommand "test-buildenv-ignoreCollisions"
        {
          # Two copies of hello with different priorities that collide
          testEnv = buildEnv {
            name = "test-env-ignore";
            paths = [
              pkgs.hello
              (lib.meta.setPrio 1 pkgs.hello)
            ];
            ignoreCollisions = true;
          };
        }
        ''
          # Should succeed because ignoreCollisions = true
          test -x "$testEnv/bin/hello" || { echo "FAIL: hello not present with ignoreCollisions"; exit 1; }

          touch $out
        '';
  };

  # buildEnv's builder.pl reads all inputs from `$NIX_ATTRS_JSON_FILE`,
  # which requires __structuredAttrs = true.
  # buildEnv explicitly forces __structuredAttrs = true.
  tests-structuredAttrs = {
    testStructuredAttrsExplicitlyFalse = {
      expr =
        (buildEnv {
          name = "test-env";
          paths = [ ];
        }).__structuredAttrs;
      expected = true;
    };

    testStructuredAttrsCantBeOverriddenViaDerivationArgs = {
      expr =
        (buildEnv {
          name = "test-env";
          paths = [ ];
          derivationArgs.__structuredAttrs = false;
        }).__structuredAttrs;
      expected = true;
    };
  };

  tests =
    tests-name
    // tests-passthru-paths
    // tests-finalAttrs
    // tests-overrideAttrs
    // tests-passthru-merging
    // tests-derivationArgs
    // tests-structuredAttrs;
in

stdenvNoCC.mkDerivation (finalAttrs: {
  __structuredAttrs = true;
  name = "test-buildenv";
  passthru = {
    inherit tests buildTests;
    failures = lib.runTests finalAttrs.passthru.tests;
  };
  testResults = lib.mapAttrs (_: test: test.expr == test.expected) finalAttrs.passthru.tests;
  buildCommand = ''
    touch $out
    for testName in "''${!testResults[@]}"; do
      if [[ -n "''${testResults[$testName]}" ]]; then
        echo "$testName success"
      else
        echo "$testName fail"
      fi
    done
  ''
  + lib.optionalString (lib.any (v: !v) (lib.attrValues finalAttrs.testResults)) ''
    {
      echo "ERROR: tests.buildenv: Encountering failed tests."
      for testName in "''${!testResults[@]}"; do
        if [[ -z "''${testResults[$testName]}" ]]; then
          echo "- $testName"
        fi
      done
      echo "To inspect the expected and actual result, "
      echo '  evaluate `tests.buildenv.tests.''${testName}`.'
    } >&2
    exit 1
  '';
})