package nixbuild_test import ( "context" "errors" "fmt" "io" "os" "os/exec" "slices" "strings" "syscall" "testing" "git.gensokyo.uk/yonah/nixbuild" "hakurei.app/command" ) const ( runAsNixStub = "TEST_RUN_AS_NIX_STUB" ) var ( stubExtraArgs = []string{ "-test.run=TestNixStub", "--", } ) // stubNixCommand causes all nix command invocations to invoke the stub for the current test. func stubNixCommand(t *testing.T) { n := nixbuild.Nix nixbuild.Nix = os.Args[0] t.Cleanup(func() { nixbuild.Nix = n }) cur, ok := os.LookupEnv(runAsNixStub) if err := os.Setenv(runAsNixStub, "1"); err != nil { t.Fatalf("cannot setenv: %v", err) } t.Cleanup(func() { if !ok { if err := os.Unsetenv(runAsNixStub); err != nil { t.Fatalf("cannot unsetenv: %v", err) } return } if err := os.Setenv(runAsNixStub, cur); err != nil { t.Fatalf("cannot setenv: %v", err) } }) } // newStubContext creates a context for use with the nix command stub. func newStubContext(ctx context.Context, extraArgs []string, stdout, stderr io.Writer) nixbuild.Context { return nixbuild.New(ctx, append(stubExtraArgs, extraArgs...), stdout, stderr) } type stubContextCommand struct { f func(*exec.Cmd) nixbuild.Context } func (s *stubContextCommand) Nix(ctx context.Context, arg ...string) *exec.Cmd { cmd := s.Context.Nix(ctx, arg...) if s.f != nil { s.f(cmd) } return cmd } // newStubContext creates a context for use with the nix command stub with a function injected into [nixbuild.Context.Nix]. func newStubContextCommand(f func(*exec.Cmd), ctx context.Context, extraArgs []string, stdout, stderr io.Writer) nixbuild.Context { return &stubContextCommand{f, newStubContext(ctx, extraArgs, stdout, stderr)} } // breakNixCommand makes all nix invocations fail for the current test. func breakNixCommand(t *testing.T) { n := nixbuild.Nix nixbuild.Nix = "/proc/nonexistent" t.Cleanup(func() { nixbuild.Nix = n }) } type stubCommandInitFunc func(c command.Command) var stubCommandInit []stubCommandInitFunc // this test mocks the nix command func TestNixStub(t *testing.T) { if os.Getenv(runAsNixStub) != "1" { return } var ( flagExtraExperimentalFeatures string ) c := command.New(os.Stderr, t.Logf, "nix", func(args []string) error { if flagExtraExperimentalFeatures != nixbuild.ExperimentalFeaturesFlakes { t.Fatalf("%s: %q, want %q", nixbuild.ExtraExperimentalFeatures, flagExtraExperimentalFeatures, nixbuild.ExperimentalFeaturesFlakes) return syscall.ENOTRECOVERABLE } return nil }). Flag(&flagExtraExperimentalFeatures, trimFlagName(nixbuild.ExtraExperimentalFeatures), command.StringFlag(""), fmt.Sprintf("expects exactly %q", nixbuild.ExperimentalFeaturesFlakes)) c.Command("true", command.UsageInternal, func([]string) error { return nil }) for _, f := range stubCommandInit { f(c) } c.MustParse(os.Args[len(stubExtraArgs)+1:], func(err error) { if err != nil { t.Fatal(err.Error()) } }) } // checkStdin checks whether entries read from r is equivalent to want. func checkStdin(r io.Reader, want ...string) error { if got, err := nixbuild.ReadStdin(r); err != nil { return err } else if !slices.Equal(got, want) { return errors.New(fmt.Sprintf("got build %#v, want %#v", got, want)) } return nil } func trimFlagName(n string) string { return strings.TrimPrefix(n, "--") } // errorWriter unconditionally returns a non-nil error type errorWriter struct{} func (errorWriter) Write([]byte) (int, error) { return 0, syscall.EIO }