package nixbuild_test import ( "errors" "io" "os" "os/exec" "slices" "strings" "testing" "git.gensokyo.uk/yonah/nixbuild" ) func TestInstantiated(t *testing.T) { stubNixCommand(t) testCases := []struct { name string want []string wantErr error }{ {"bad fields", nil, &nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedBadFields}}, {"unexpected quotes left", nil, &nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}}, {"unexpected quotes right", nil, &nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}}, {"unexpected quotes short", nil, &nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}}, {"not absolute", nil, &nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedNotAbsolute}}, {"good segment", []string{ "/nix/store/3zilrlmq7r6rpzfd94mwss32b62yinj5-bootstrap-stage0-stdenv-linux.drv", "/nix/store/7yfwy95p6lcdpljdajs5aw10h6q0sfx0-update-autotools-gnu-config-scripts-hook.drv", "/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv", "/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv", "/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv", "/nix/store/ysp83x9nrks28zkblqmnc1s1kb68dr69-gnu-config-2024-01-01.drv", }, nil}, {"getchoo atlas", getchooAtlasInstantiated, nil}, {"getchoo glados", getchooGladosInstantiated, nil}, {"pluiedev pappardelle", pluiedevPappardelleInstantiated, nil}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var stderr io.Writer if tc.wantErr != nil { stderr = os.Stderr } sample := instSample[tc.name] t.Run("decoder", func(t *testing.T) { out := strings.NewReader(sample) decoder := nixbuild.NewInstantiatedDecoder(out, stderr) got, err := decoder.Decode() if !errors.Is(err, tc.wantErr) { t.Fatalf("Decode: error = %v, want %v", err, tc.wantErr) } if tc.wantErr != nil { t.Logf("Decode: error = %v", err) t.Run("scan after error", func(t *testing.T) { if decoder.Scan() { t.Fatalf("Scan unexpectedly succeeded on faulted decoder") } }) return } if !slices.Equal(got, tc.want) { t.Errorf("Decode: %#v, want %#v", got, tc.want) } }) t.Run("evaluator", func(t *testing.T) { ctx := newStubContext(t.Context(), nil, os.Stdout, stderr) got, err := nixbuild.EvalInstantiated(ctx, tc.name) if !errors.Is(err, tc.wantErr) { t.Fatalf("EvalInstantiated: error = %v, want %v", err, tc.wantErr) } if tc.wantErr != nil { t.Logf("EvalInstantiated: error = %v", err) return } if !slices.Equal(got, tc.want) { t.Errorf("EvalInstantiated: %#v, want %#v", got, tc.want) } }) }) } } func stubInstantiatedEvaluator(args []string) error { _, _ = os.Stderr.Write([]byte(instSample[args[0]])) return nil } func TestInstantiatedDecoderStopEarly(t *testing.T) { want := []string{ "/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv", "/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv", "/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv", } decoder := nixbuild.NewInstantiatedDecoder(strings.NewReader(segmentPrefix+segmentBody+segmentSuffix), os.Stderr) counter := 3 got := make([]string, 0, counter) for drv := range decoder.Instantiated() { got = append(got, drv) counter-- if counter == 0 { break } } if !slices.Equal(got, want) { t.Errorf("Instantiated: %#v, want %#v", got, want) } } func TestInstantiatedEvaluatorBadCommand(t *testing.T) { wantErr := os.ErrNotExist breakNixCommand(t) if _, err := nixbuild.EvalInstantiated( nixbuild.New(t.Context(), nil, os.Stdout, os.Stderr), "", ); !errors.Is(err, wantErr) { t.Errorf("EvalInstantiated: error = %v, want %v", err, wantErr) } } func TestInstantiatedEvaluatorAlreadySet(t *testing.T) { stubNixCommand(t) if _, err := nixbuild.EvalInstantiated( newStubContextCommand(func(cmd *exec.Cmd) { cmd.Stderr = os.Stderr }, t.Context(), nil, os.Stdout, os.Stderr), "", ); err == nil { t.Errorf("EvalInstantiated unexpectedly succeeded") } } func TestMalformedInstantiatedError(t *testing.T) { t.Run("bad type", func(t *testing.T) { badErr := errors.New("") if errors.Is(new(nixbuild.MalformedInstantiatedError), badErr) { t.Error("unexpected MalformedInstantiatedError equivalence") } }) t.Run("nil", func(t *testing.T) { if errors.Is(new(nixbuild.MalformedInstantiatedError), (*nixbuild.MalformedInstantiatedError)(nil)) { t.Error("unexpected MalformedInstantiatedError equivalence") } }) t.Run("unreachable", func(t *testing.T) { defer func() { wantPanic := "unreachable" if r := recover(); r != wantPanic { t.Errorf("Error: panic = %q, want %q", r, wantPanic) } }() _ = (&nixbuild.MalformedInstantiatedError{Type: -1}).Error() }) } func benchmarkInstantiatedDecoder(b *testing.B, out string) { decoder := nixbuild.NewInstantiatedDecoder(strings.NewReader(out), nil) for b.Loop() { retry: if !decoder.Scan() { b.StopTimer() if err := decoder.Err(); err != nil { b.Fatalf("Decode: error = %v", err) } decoder = nixbuild.NewInstantiatedDecoder(strings.NewReader(out), nil) b.StartTimer() goto retry } b.StopTimer() if !strings.HasPrefix(decoder.Text(), "/nix/store") { b.Fatalf("Text: unexpected prefix: %s", decoder.Text()) } b.StartTimer() } } func BenchmarkInstantiatedDecoder(b *testing.B) { benchmarkInstantiatedDecoder(b, pluiedevPappardelleOut) } func benchmarkInstantiated(b *testing.B, out string, want []string) { for b.Loop() { v, _ := nixbuild.NewInstantiatedDecoder(strings.NewReader(out), nil).Decode() b.StopTimer() if !slices.Equal(v, want) { b.Fatalf("Decode: %#v, want %#v", v, want) } b.StartTimer() } } func BenchmarkInstantiated(b *testing.B) { /* 27750 raw, 10729 deduplicated */ benchmarkInstantiated(b, getchooGladosOut, getchooGladosInstantiated) } func BenchmarkInstantiatedCopy(b *testing.B) { /* 40177 raw, 10685 deduplicated */ benchmarkInstantiated(b, pluiedevPappardelleOut, pluiedevPappardelleInstantiated) }