exec: replace global state with interface
This is cleaner, and finally enables writing tests for the nix invoking functions.
This commit is contained in:
parent
aa4bbbc2fe
commit
69c6128ff5
34
build.go
34
build.go
@ -2,42 +2,28 @@ package nixbuild
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"iter"
|
||||
)
|
||||
|
||||
// Build builds all entries yielded by installables.
|
||||
func Build(ctx context.Context, installables iter.Seq[string]) error {
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
func Build(ctx Context, installables iter.Seq[string]) error {
|
||||
c, cancel := context.WithCancel(ctx.Unwrap())
|
||||
defer cancel()
|
||||
stdout, stderr := ctx.Streams()
|
||||
|
||||
cmd := Nix(c, CommandBuild,
|
||||
cmd := ctx.Nix(c, CommandBuild,
|
||||
FlagKeepGoing, FlagNoLink, FlagStdin)
|
||||
if Stdout != nil {
|
||||
cmd.Stdout = Stdout
|
||||
if stdout != nil {
|
||||
cmd.Stdout = stdout
|
||||
cmd.Args = append(cmd.Args, FlagPrintBuildLogs)
|
||||
} else {
|
||||
cmd.Args = append(cmd.Args, FlagQuiet)
|
||||
}
|
||||
if Stderr != nil {
|
||||
cmd.Stderr = Stderr
|
||||
if stderr != nil {
|
||||
cmd.Stderr = stderr
|
||||
cmd.Args = append(cmd.Args, FlagVerbose)
|
||||
}
|
||||
|
||||
ir, iw := io.Pipe()
|
||||
cmd.Stdin = ir
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := WriteStdin(iw, installables); err != nil {
|
||||
return errors.Join(err, cmd.Wait())
|
||||
}
|
||||
if err := iw.Close(); err != nil {
|
||||
return errors.Join(err, cmd.Wait())
|
||||
}
|
||||
|
||||
return cmd.Wait()
|
||||
_, err := ctx.WriteStdin(cmd, installables, nil)
|
||||
return err
|
||||
}
|
||||
|
93
build_test.go
Normal file
93
build_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
package nixbuild_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
"hakurei.app/command"
|
||||
)
|
||||
|
||||
func init() {
|
||||
stubCommandInit = append(stubCommandInit, func(c command.Command) {
|
||||
var (
|
||||
flagBuildKeepGoing bool
|
||||
flagBuildNoLink bool
|
||||
flagBuildStdin bool
|
||||
flagBuildQuiet bool
|
||||
flagBuildPBL bool
|
||||
flagBuildVerbose bool
|
||||
)
|
||||
c.NewCommand(nixbuild.CommandBuild, "emit samples for various `nix build` cases", func(args []string) error {
|
||||
switch {
|
||||
default:
|
||||
return os.ErrInvalid
|
||||
|
||||
case len(args) == 7 && slices.Equal(args[1:], []string{
|
||||
nixbuild.FlagOption, nixbuild.OptionEvalCache, nixbuild.ValueFalse,
|
||||
nixbuild.FlagDryRun, nixbuild.FlagPrintBuildLogs, nixbuild.FlagDebug,
|
||||
}): // InstantiatedEvaluator
|
||||
return stubInstantiatedEvaluator(args)
|
||||
|
||||
case flagBuildKeepGoing, flagBuildNoLink, flagBuildStdin: // Build
|
||||
switch {
|
||||
default:
|
||||
return syscall.ENOTRECOVERABLE
|
||||
|
||||
case flagBuildQuiet && !flagBuildPBL && !flagBuildVerbose:
|
||||
return checkStdin(os.Stdin, "quiet")
|
||||
|
||||
case !flagBuildQuiet && flagBuildPBL && !flagBuildVerbose:
|
||||
return checkStdin(os.Stdin, "logs")
|
||||
|
||||
case flagBuildQuiet && !flagBuildPBL && flagBuildVerbose:
|
||||
return checkStdin(os.Stdin, "quiet", "verbose")
|
||||
|
||||
case !flagBuildQuiet && flagBuildPBL && flagBuildVerbose:
|
||||
return checkStdin(os.Stdin, "logs", "verbose")
|
||||
}
|
||||
}
|
||||
}).
|
||||
Flag(&flagBuildKeepGoing, trimFlagName(nixbuild.FlagKeepGoing), command.BoolFlag(false), nixbuild.FlagKeepGoing).
|
||||
Flag(&flagBuildNoLink, trimFlagName(nixbuild.FlagNoLink), command.BoolFlag(false), nixbuild.FlagNoLink).
|
||||
Flag(&flagBuildStdin, trimFlagName(nixbuild.FlagStdin), command.BoolFlag(false), nixbuild.FlagStdin).
|
||||
Flag(&flagBuildQuiet, trimFlagName(nixbuild.FlagQuiet), command.BoolFlag(false), nixbuild.FlagQuiet).
|
||||
Flag(&flagBuildPBL, trimFlagName(nixbuild.FlagPrintBuildLogs), command.BoolFlag(false), nixbuild.FlagPrintBuildLogs).
|
||||
Flag(&flagBuildVerbose, trimFlagName(nixbuild.FlagVerbose), command.BoolFlag(false), nixbuild.FlagVerbose)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
stubNixCommand(t)
|
||||
check := func(stdout, stderr io.Writer, v ...string) {
|
||||
t.Run(strings.Join(v, " "), func(t *testing.T) {
|
||||
if err := nixbuild.Build(
|
||||
newStubContext(t.Context(), nil, stdout, stderr),
|
||||
slices.Values(v),
|
||||
); err != nil {
|
||||
t.Errorf("Build: error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
check(nil, nil, "quiet")
|
||||
check(os.Stdout, nil, "logs")
|
||||
check(nil, os.Stderr, "quiet", "verbose")
|
||||
check(os.Stdout, os.Stderr, "logs", "verbose")
|
||||
}
|
||||
|
||||
func TestBuildBadCommand(t *testing.T) {
|
||||
wantErr := os.ErrNotExist
|
||||
breakNixCommand(t)
|
||||
if err := nixbuild.Build(
|
||||
nixbuild.New(t.Context(), nil, nil, nil),
|
||||
nil,
|
||||
); !errors.Is(err, wantErr) {
|
||||
t.Errorf("Build: error = %v, want %v", err, wantErr)
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
@ -20,7 +21,9 @@ type commandHandlerError string
|
||||
func (c commandHandlerError) Error() string { return string(c) }
|
||||
|
||||
func main() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
var ctx nixbuild.Context
|
||||
|
||||
nixCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
var (
|
||||
@ -31,10 +34,13 @@ func main() {
|
||||
c := command.New(os.Stderr, log.Printf, "nixbuild", func(args []string) error {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("nixbuild: ")
|
||||
nixbuild.Stdout = os.Stdout
|
||||
|
||||
var stderr io.Writer
|
||||
if flagVerbose {
|
||||
nixbuild.Stderr = os.Stderr
|
||||
stderr = os.Stderr
|
||||
}
|
||||
ctx = nixbuild.New(nixCtx, nil, os.Stdout, stderr)
|
||||
|
||||
return nil
|
||||
}).
|
||||
Flag(&flagNixOS, "nixos", command.BoolFlag(false), "Interpret input as NixOS flake installable").
|
||||
|
22
context.go
Normal file
22
context.go
Normal file
@ -0,0 +1,22 @@
|
||||
package nixbuild
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"iter"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Context holds configuration and environment information for interacting with nix.
|
||||
type Context interface {
|
||||
// Streams returns the stdout and stderr writers held by this [Context].
|
||||
Streams() (stdout, stderr io.Writer)
|
||||
|
||||
// Nix returns the [exec.Cmd] struct to execute a nix command.
|
||||
Nix(ctx context.Context, arg ...string) *exec.Cmd
|
||||
// WriteStdin calls [WriteStdin] for [exec.Cmd]. The function f points to is called if [WriteStdin] succeeds.
|
||||
WriteStdin(cmd *exec.Cmd, installables iter.Seq[string], f func() error) (int, error)
|
||||
|
||||
// Unwrap returns the stored [context.Context]
|
||||
Unwrap() context.Context
|
||||
}
|
@ -3,8 +3,6 @@ package nixbuild
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"iter"
|
||||
)
|
||||
|
||||
@ -48,41 +46,24 @@ type (
|
||||
)
|
||||
|
||||
// DerivationShow returns a [DerivationMap] describing all entries yielded by installables.
|
||||
func DerivationShow(ctx context.Context, installables iter.Seq[string]) (DerivationMap, error) {
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
func DerivationShow(ctx Context, installables iter.Seq[string]) (DerivationMap, error) {
|
||||
c, cancel := context.WithCancel(ctx.Unwrap())
|
||||
defer cancel()
|
||||
_, stderr := ctx.Streams()
|
||||
var decoder *json.Decoder
|
||||
|
||||
cmd := Nix(c, CommandDerivation, CommandDerivationShow,
|
||||
cmd := ctx.Nix(c, CommandDerivation, CommandDerivationShow,
|
||||
FlagStdin)
|
||||
ir, iw := io.Pipe()
|
||||
cmd.Stdin = ir
|
||||
or, ow := io.Pipe()
|
||||
cmd.Stdout = ow
|
||||
if Stderr != nil {
|
||||
cmd.Stderr = Stderr
|
||||
}
|
||||
cmd.Cancel = func() error { _ = ow.Close(); return cmd.Process.Kill() }
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
if r, err := cmd.StdoutPipe(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
decoder = json.NewDecoder(r)
|
||||
}
|
||||
if stderr != nil {
|
||||
cmd.Stderr = stderr
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
var v DerivationMap
|
||||
go func() { done <- json.NewDecoder(or).Decode(&v) }()
|
||||
|
||||
if _, err := WriteStdin(iw, installables); err != nil {
|
||||
return nil, errors.Join(err, cmd.Wait())
|
||||
}
|
||||
if err := iw.Close(); err != nil {
|
||||
return nil, errors.Join(err, cmd.Wait())
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
// deferred cancel closes pipe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := <-done
|
||||
_, err := ctx.WriteStdin(cmd, installables, func() error { return decoder.Decode(&v) })
|
||||
return v, err
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package nixbuild_test
|
||||
|
||||
import _ "embed"
|
||||
|
||||
var (
|
||||
//go:embed testdata/derivation/show_getchoo_atlas.json
|
||||
getchooAtlasShow []byte
|
||||
|
||||
//go:embed testdata/derivation/collect_getchoo_atlas
|
||||
getchooAtlasCollective string
|
||||
)
|
@ -1,11 +0,0 @@
|
||||
package nixbuild_test
|
||||
|
||||
import _ "embed"
|
||||
|
||||
var (
|
||||
//go:embed testdata/derivation/show_getchoo_glados.json
|
||||
getchooGladosShow []byte
|
||||
|
||||
//go:embed testdata/derivation/collect_getchoo_glados
|
||||
getchooGladosCollective string
|
||||
)
|
@ -1,11 +0,0 @@
|
||||
package nixbuild_test
|
||||
|
||||
import _ "embed"
|
||||
|
||||
var (
|
||||
//go:embed testdata/derivation/show_pluiedev_pappardelle.json
|
||||
pluiedevPappardelleShow []byte
|
||||
|
||||
//go:embed testdata/derivation/collect_pluiedev_pappardelle
|
||||
pluiedevPappardelleCollective string
|
||||
)
|
@ -2,35 +2,111 @@ package nixbuild_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
"hakurei.app/command"
|
||||
)
|
||||
|
||||
func TestCollectFromDerivations(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
show []byte
|
||||
want string
|
||||
}{
|
||||
{"getchoo atlas", getchooAtlasShow, getchooAtlasCollective},
|
||||
{"getchoo glados", getchooGladosShow, getchooGladosCollective},
|
||||
{"pluiedev pappardelle", pluiedevPappardelleShow, pluiedevPappardelleCollective},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var derivations nixbuild.DerivationMap
|
||||
if err := json.Unmarshal(tc.show, &derivations); err != nil {
|
||||
t.Fatalf("cannot unmarshal test data: %v", err)
|
||||
func init() {
|
||||
stubCommandInit = append(stubCommandInit, func(c command.Command) {
|
||||
c.NewCommand(nixbuild.CommandDerivation, "emit samples for various `nix derivation` cases", func(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
got := nixbuild.CollectFromDerivations(derivations)
|
||||
want := strings.Split(strings.TrimSpace(tc.want), "\n")
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("CollectFromDerivations:\n%s, want\n%s",
|
||||
strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||
|
||||
switch args[0] {
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
|
||||
case nixbuild.CommandDerivationShow:
|
||||
if len(args) != 2 {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
|
||||
case nixbuild.FlagStdin:
|
||||
var testName string
|
||||
if want, err := nixbuild.ReadStdin(os.Stdin); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for n, w := range instWant {
|
||||
if slices.Equal(w, want) {
|
||||
testName = n
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if testName == "" {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
}
|
||||
if sample, ok := drvShow[testName]; !ok {
|
||||
return syscall.ENOSYS
|
||||
} else {
|
||||
_, err := os.Stdout.Write(sample)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDerivationShow(t *testing.T) {
|
||||
stubNixCommand(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
}{
|
||||
{"getchoo atlas"},
|
||||
{"getchoo glados"},
|
||||
{"pluiedev pappardelle"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
got, err := nixbuild.DerivationShow(
|
||||
newStubContext(t.Context(), nil, os.Stdout, os.Stderr),
|
||||
slices.Values(instWant[tc.name]),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("DerivationShow: error = %v", err)
|
||||
}
|
||||
|
||||
var want nixbuild.DerivationMap
|
||||
if err = json.Unmarshal(drvShow[tc.name], &want); err != nil {
|
||||
t.Fatalf("cannot unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("DerivationShow: %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivationShowAlreadySet(t *testing.T) {
|
||||
stubNixCommand(t)
|
||||
if _, err := nixbuild.DerivationShow(
|
||||
newStubContextCommand(func(cmd *exec.Cmd) { cmd.Stdout = os.Stdout }, t.Context(), nil, os.Stdout, os.Stderr),
|
||||
nil,
|
||||
); err == nil {
|
||||
t.Errorf("DerivationShow unexpectedly succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkShowUnmarshal(b *testing.B) {
|
||||
var v nixbuild.DerivationMap
|
||||
data := drvShow["pluiedev pappardelle"]
|
||||
for b.Loop() {
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
b.Fatalf("Unmarshal: error = %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
68
exec.go
Normal file
68
exec.go
Normal file
@ -0,0 +1,68 @@
|
||||
package nixbuild
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"iter"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Nix is the name of the nix program.
|
||||
var Nix = "nix"
|
||||
|
||||
type nix struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
extra []string
|
||||
|
||||
stdout, stderr io.Writer
|
||||
}
|
||||
|
||||
func (n *nix) Unwrap() context.Context { return n.ctx }
|
||||
func (n *nix) Streams() (stdout, stderr io.Writer) { return n.stdout, n.stderr }
|
||||
|
||||
const (
|
||||
ExtraExperimentalFeatures = "--extra-experimental-features"
|
||||
ExperimentalFeaturesFlakes = "nix-command flakes"
|
||||
)
|
||||
|
||||
/*
|
||||
New returns a new [Context].
|
||||
|
||||
A non-nil stderr implies verbose.
|
||||
|
||||
Streams will not be connected for commands outputting JSON.
|
||||
*/
|
||||
func New(ctx context.Context, extraArgs []string, stdout, stderr io.Writer) Context {
|
||||
return &nix{
|
||||
name: Nix,
|
||||
ctx: ctx,
|
||||
// since flakes are supposedly experimental
|
||||
extra: append(extraArgs, ExtraExperimentalFeatures, ExperimentalFeaturesFlakes),
|
||||
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nix) Nix(ctx context.Context, arg ...string) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, n.name, append(n.extra, arg...)...)
|
||||
}
|
||||
|
||||
func (n *nix) WriteStdin(cmd *exec.Cmd, installables iter.Seq[string], f func() error) (int, error) {
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = cmd.Start(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count, writeErr := WriteStdin(w, installables)
|
||||
closeErr := w.Close()
|
||||
var fErr error
|
||||
if f != nil && writeErr == nil && closeErr == nil {
|
||||
fErr = f()
|
||||
}
|
||||
return count, errors.Join(writeErr, closeErr, fErr, cmd.Wait())
|
||||
}
|
37
exec_test.go
Normal file
37
exec_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package nixbuild_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
)
|
||||
|
||||
func TestNixWriteStdin(t *testing.T) {
|
||||
ctx := nixbuild.New(t.Context(), nil, os.Stdout, os.Stderr)
|
||||
|
||||
t.Run("already set", func(t *testing.T) {
|
||||
cmd := exec.CommandContext(t.Context(), "/proc/nonexistent")
|
||||
cmd.Stdin = os.Stdin
|
||||
if _, err := ctx.WriteStdin(cmd, nil, nil); err == nil {
|
||||
t.Fatal("WriteStdinCommand unexpectedly succeeded")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("f returns error", func(t *testing.T) {
|
||||
stubNixCommand(t)
|
||||
ctx := newStubContext(t.Context(), nil, os.Stdout, os.Stderr)
|
||||
cmd := ctx.Nix(t.Context(), "true")
|
||||
if _, err := ctx.WriteStdin(
|
||||
cmd,
|
||||
slices.Values(make([]string, 0)),
|
||||
func() error { return syscall.ENOSYS },
|
||||
); !errors.Is(err, syscall.ENOSYS) {
|
||||
t.Fatalf("WriteStdinCommand: error = %v, want %v", err, syscall.ENOSYS)
|
||||
}
|
||||
})
|
||||
}
|
39
format.go
39
format.go
@ -1,10 +1,10 @@
|
||||
package nixbuild
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bufio"
|
||||
"io"
|
||||
"iter"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -73,21 +73,6 @@ const (
|
||||
ValueFalse = "false"
|
||||
)
|
||||
|
||||
const (
|
||||
nix = "nix"
|
||||
|
||||
nixExtraExperimentalFeatures = "--extra-experimental-features"
|
||||
nixExperimentalFeaturesFlakes = "nix-command flakes"
|
||||
)
|
||||
|
||||
// since flakes are supposedly experimental
|
||||
var nixEnableFlakes = []string{nixExtraExperimentalFeatures, nixExperimentalFeaturesFlakes}
|
||||
|
||||
// Nix returns the [exec.Cmd] struct to execute a nix command.
|
||||
func Nix(ctx context.Context, arg ...string) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, nix, append(nixEnableFlakes, arg...)...)
|
||||
}
|
||||
|
||||
const (
|
||||
nixosSuffix0 = "#nixosConfigurations."
|
||||
nixosSuffix1 = ".config.system.build.toplevel"
|
||||
@ -112,3 +97,23 @@ func WriteStdin(w io.Writer, installables iter.Seq[string]) (int, error) {
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
const readStdinInitialCap = 1 << 10
|
||||
|
||||
// ReadStdin is the inverse of [WriteStdin].
|
||||
func ReadStdin(r io.Reader) ([]string, error) {
|
||||
collective := make([]string, 0, readStdinInitialCap)
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
if len(collective) == cap(collective) {
|
||||
// grow the buffer exponentially to minimise copies
|
||||
collective = slices.Grow(collective, cap(collective))
|
||||
}
|
||||
p := scanner.Text()
|
||||
if strings.HasSuffix(p, ".drv^*") {
|
||||
p = p[:len(p)-2]
|
||||
}
|
||||
collective = append(collective, p)
|
||||
}
|
||||
return collective, scanner.Err()
|
||||
}
|
||||
|
@ -1,32 +1,60 @@
|
||||
package nixbuild_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
)
|
||||
|
||||
func TestNix(t *testing.T) {
|
||||
func TestStdin(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
arg []string
|
||||
want []string
|
||||
col []string
|
||||
raw string
|
||||
}{
|
||||
{"build", []string{"build"},
|
||||
[]string{"nix", "--extra-experimental-features", "nix-command flakes", "build"}},
|
||||
{"build workflow", []string{"build", `--out-link "result"`, "--print-out-paths", "--print-build-logs"},
|
||||
[]string{"nix", "--extra-experimental-features", "nix-command flakes", "build", `--out-link "result"`, "--print-out-paths", "--print-build-logs"}},
|
||||
{"getchoo atlas", getchooAtlasCollective, getchooAtlasStdin},
|
||||
{"getchoo glados", getchooGladosCollective, getchooGladosStdin},
|
||||
{"pluiedev pappardelle", pluiedevPappardelleCollective, pluiedevPappardelleStdin},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := nixbuild.Nix(t.Context(), tc.arg...)
|
||||
if !slices.Equal(got.Args, tc.want) {
|
||||
t.Errorf("Nix: %#v, want %#v",
|
||||
got.Args, tc.want)
|
||||
}
|
||||
t.Run("write", func(t *testing.T) {
|
||||
w := new(strings.Builder)
|
||||
|
||||
if _, err := nixbuild.WriteStdin(w, slices.Values(tc.col)); err != nil {
|
||||
t.Fatalf("cannot write: %v", err)
|
||||
}
|
||||
if w.String() != tc.raw {
|
||||
t.Errorf("WriteStdin:\n%s\nwant:\n%s", w.String(), tc.raw)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("read", func(t *testing.T) {
|
||||
r := strings.NewReader(tc.raw)
|
||||
got, err := nixbuild.ReadStdin(r)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read: %v", err)
|
||||
}
|
||||
if !slices.Equal(got, tc.col) {
|
||||
t.Errorf("ReadStdin: %#v, want %#v", got, tc.col)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("write error", func(t *testing.T) {
|
||||
n, err := nixbuild.WriteStdin(errorWriter{}, slices.Values([]string{"/nix/store"}))
|
||||
if n != 0 {
|
||||
panic("unreachable")
|
||||
}
|
||||
if !errors.Is(err, syscall.EIO) {
|
||||
t.Errorf("WriteStdin: error = %v, want %v", err, syscall.EIO)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInstallable(t *testing.T) {
|
||||
|
113
instantiated.go
113
instantiated.go
@ -19,7 +19,7 @@ const (
|
||||
instantiatedFields = 2
|
||||
|
||||
// a reasonable starting buffer capacity: pretty close to average of test cases
|
||||
instantiatedInitialCap = 1 << 13
|
||||
instantiatedInitialCap = 1 << 15
|
||||
)
|
||||
|
||||
const (
|
||||
@ -60,6 +60,8 @@ func (m *MalformedInstantiatedError) Is(err error) bool {
|
||||
// InstantiatedDecoder interprets the verbose output of `nix build` and collects instantiated derivations.
|
||||
type InstantiatedDecoder struct {
|
||||
err error
|
||||
pt func(string)
|
||||
drv string
|
||||
scanner *bufio.Scanner
|
||||
}
|
||||
|
||||
@ -67,44 +69,65 @@ type InstantiatedDecoder struct {
|
||||
func (d *InstantiatedDecoder) Err() error { return errors.Join(d.err, d.scanner.Err()) }
|
||||
|
||||
// NewInstantiatedDecoder returns a [InstantiatedDecoder] struct for collecting derivations from r.
|
||||
func NewInstantiatedDecoder(r io.Reader) *InstantiatedDecoder {
|
||||
return &InstantiatedDecoder{scanner: bufio.NewScanner(r)}
|
||||
// Skipped lines are written to w.
|
||||
func NewInstantiatedDecoder(r io.Reader, w io.Writer) *InstantiatedDecoder {
|
||||
passthrough := func(string) {}
|
||||
if w != nil {
|
||||
passthrough = func(v string) { _, _ = w.Write([]byte(v + "\n")) }
|
||||
}
|
||||
return &InstantiatedDecoder{scanner: bufio.NewScanner(r), pt: passthrough}
|
||||
}
|
||||
|
||||
// Scan advances the decoder to the next instantiated path, skipping all unrelated lines in-between.
|
||||
func (d *InstantiatedDecoder) Scan() bool {
|
||||
if d.err != nil {
|
||||
return false
|
||||
}
|
||||
rescan:
|
||||
if !d.scanner.Scan() {
|
||||
return false
|
||||
}
|
||||
|
||||
v := d.scanner.Text()
|
||||
if !strings.HasPrefix(v, instantiatedPrefix) {
|
||||
// write skipped lines to stderr
|
||||
d.pt(v)
|
||||
goto rescan
|
||||
}
|
||||
|
||||
fields := strings.SplitN(v, instantiatedSeparator, instantiatedFields)
|
||||
if len(fields) != instantiatedFields {
|
||||
// no more than a single -> is expected
|
||||
d.err = &MalformedInstantiatedError{v, InstantiatedBadFields}
|
||||
return false
|
||||
}
|
||||
|
||||
// very basic validation here: the output format is not fully understood
|
||||
if len(fields[1]) < 3 || fields[1][0] != '\'' || fields[1][len(fields[1])-1] != '\'' {
|
||||
d.err = &MalformedInstantiatedError{v, InstantiatedUnexpectedQuotes}
|
||||
return false
|
||||
}
|
||||
drv := fields[1][1 : len(fields[1])-1]
|
||||
|
||||
if !path.IsAbs(drv) {
|
||||
d.err = &MalformedInstantiatedError{v, InstantiatedNotAbsolute}
|
||||
return false
|
||||
}
|
||||
|
||||
d.drv = drv
|
||||
return true
|
||||
}
|
||||
|
||||
// Text returns the most recent instantiated path generated by a call to [InstantiatedDecoder.Scan]
|
||||
// as a newly allocated string holding its bytes.
|
||||
func (d *InstantiatedDecoder) Text() string { return d.drv }
|
||||
|
||||
// Instantiated returns a non-reusable iterator over instantiated derivations collected from [io.Reader].
|
||||
func (d *InstantiatedDecoder) Instantiated() iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
for d.scanner.Scan() {
|
||||
v := d.scanner.Text()
|
||||
if !strings.HasPrefix(v, instantiatedPrefix) {
|
||||
// write skipped lines to stderr
|
||||
if Stderr != nil {
|
||||
_, _ = Stderr.Write([]byte(v + "\n"))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.SplitN(v, instantiatedSeparator, instantiatedFields)
|
||||
if len(fields) != instantiatedFields {
|
||||
// no more than a single -> is expected
|
||||
d.err = &MalformedInstantiatedError{v, InstantiatedBadFields}
|
||||
break
|
||||
}
|
||||
|
||||
// very basic validation here: the output format is not fully understood
|
||||
if len(fields[1]) < 3 || fields[1][0] != '\'' || fields[1][len(fields[1])-1] != '\'' {
|
||||
d.err = &MalformedInstantiatedError{v, InstantiatedUnexpectedQuotes}
|
||||
break
|
||||
}
|
||||
drv := fields[1][1 : len(fields[1])-1]
|
||||
|
||||
if !path.IsAbs(drv) {
|
||||
d.err = &MalformedInstantiatedError{v, InstantiatedNotAbsolute}
|
||||
break
|
||||
}
|
||||
|
||||
if !yield(drv) {
|
||||
break
|
||||
for d.Scan() {
|
||||
if !yield(d.Text()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,10 +170,10 @@ func (e *InstantiatedEvaluator) Err() error {
|
||||
}
|
||||
|
||||
// NewInstantiatedEvaluator initialises an [InstantiatedEvaluator] struct and its underlying nix process.
|
||||
func NewInstantiatedEvaluator(ctx context.Context, installable string) (*InstantiatedEvaluator, error) {
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
func NewInstantiatedEvaluator(ctx Context, installable string) (*InstantiatedEvaluator, error) {
|
||||
c, cancel := context.WithCancel(ctx.Unwrap())
|
||||
e := &InstantiatedEvaluator{
|
||||
cmd: Nix(c, CommandBuild, installable,
|
||||
cmd: ctx.Nix(c, CommandBuild, installable,
|
||||
// 'instantiated' messages are only emitted when actually evaluating something
|
||||
FlagOption, OptionEvalCache, ValueFalse,
|
||||
// do not actually build anything
|
||||
@ -160,12 +183,16 @@ func NewInstantiatedEvaluator(ctx context.Context, installable string) (*Instant
|
||||
),
|
||||
}
|
||||
|
||||
e.cmd.Stdout = Stdout
|
||||
stdout, stderr := ctx.Streams()
|
||||
e.cmd.Stdout = stdout
|
||||
|
||||
// verbose output ends up on stderr in the current nix implementation
|
||||
er, ew := io.Pipe()
|
||||
e.cmd.Stderr = ew
|
||||
e.InstantiatedDecoder = NewInstantiatedDecoder(er)
|
||||
if r, err := e.cmd.StderrPipe(); err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
} else {
|
||||
e.InstantiatedDecoder = NewInstantiatedDecoder(r, stderr)
|
||||
}
|
||||
|
||||
if err := e.cmd.Start(); err != nil {
|
||||
cancel()
|
||||
@ -173,13 +200,13 @@ func NewInstantiatedEvaluator(ctx context.Context, installable string) (*Instant
|
||||
return nil, err
|
||||
}
|
||||
e.waitMu.Lock()
|
||||
go func() { e.waitErr = e.cmd.Wait(); cancel(); _ = ew.Close(); e.waitMu.Unlock() }()
|
||||
go func() { e.waitErr = e.cmd.Wait(); cancel(); e.waitMu.Unlock() }()
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// EvalInstantiated calls the underlying [InstantiatedDecoder.Decode] of a new [InstantiatedEvaluator].
|
||||
func EvalInstantiated(ctx context.Context, installable string) ([]string, error) {
|
||||
func EvalInstantiated(ctx Context, installable string) ([]string, error) {
|
||||
evaluator, err := NewInstantiatedEvaluator(ctx, installable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,8 +2,9 @@ package nixbuild_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -11,24 +12,21 @@ import (
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
)
|
||||
|
||||
func TestDecodeInstantiated(t *testing.T) {
|
||||
func TestInstantiated(t *testing.T) {
|
||||
stubNixCommand(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
out string
|
||||
want []string
|
||||
wantErr error
|
||||
}{
|
||||
{"bad fields", segmentPrefix + `instantiated 'config.sub-948ae97' ` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedBadFields}},
|
||||
{"unexpected quotes left", segmentPrefix + `instantiated 'config.sub-948ae97' -> /n'` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}},
|
||||
{"unexpected quotes right", segmentPrefix + `instantiated 'config.sub-948ae97' -> '/n` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}},
|
||||
{"unexpected quotes short", segmentPrefix + `instantiated 'config.sub-948ae97' -> ''` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedUnexpectedQuotes}},
|
||||
{"not absolute", segmentPrefix + `instantiated 'config.sub-948ae97' -> ' '` + segmentSuffix, nil,
|
||||
&nixbuild.MalformedInstantiatedError{Type: nixbuild.InstantiatedNotAbsolute}},
|
||||
{"good segment", segmentPrefix + segmentBody + segmentSuffix, []string{
|
||||
{"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",
|
||||
@ -37,125 +35,175 @@ func TestDecodeInstantiated(t *testing.T) {
|
||||
"/nix/store/ysp83x9nrks28zkblqmnc1s1kb68dr69-gnu-config-2024-01-01.drv",
|
||||
}, nil},
|
||||
|
||||
{"getchoo atlas", getchooAtlasOut, getchooAtlasInstantiated, nil},
|
||||
{"getchoo glados", getchooGladosOut, getchooGladosInstantiated, nil},
|
||||
{"pluiedev pappardelle", pluiedevPappardelleOut, pluiedevPappardelleInstantiated, 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 {
|
||||
w := nixbuild.Stderr
|
||||
nixbuild.Stderr = os.Stderr
|
||||
t.Cleanup(func() { nixbuild.Stderr = w })
|
||||
stderr = os.Stderr
|
||||
}
|
||||
sample := instSample[tc.name]
|
||||
|
||||
stderr := strings.NewReader(tc.out)
|
||||
got, err := nixbuild.NewInstantiatedDecoder(stderr).Decode()
|
||||
if !errors.Is(err, tc.wantErr) {
|
||||
t.Fatalf("DecodeInstantiated: error = %v, want %v", err, tc.wantErr)
|
||||
}
|
||||
if tc.wantErr != nil {
|
||||
t.Logf("DecodeInstantiated: error = %v", err)
|
||||
return
|
||||
}
|
||||
if !slices.Equal(got, tc.want) {
|
||||
t.Errorf("DecodeInstantiated: %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("stop early", func(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))
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("errors", func(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)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
_ = (&nixbuild.MalformedInstantiatedError{Type: -1}).Error()
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
segmentPrefix = `evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/development/libraries/zlib/default.nix'
|
||||
evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/fetchurl/boot.nix'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'zlib-1.3.1.tar.gz' -> '/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv'
|
||||
source path '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' is uncacheable
|
||||
copying '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' to the store...
|
||||
performing daemon worker op: 7
|
||||
copied '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' to '/nix/store/96rvfw5vlv1hwwm9sdxhdkkpjyym6p2x-update-autotools-gnu-config-scripts.sh'
|
||||
copied source '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' -> '/nix/store/96rvfw5vlv1hwwm9sdxhdkkpjyym6p2x-update-autotools-gnu-config-scripts.sh'
|
||||
evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/by-name/gn/gnu-config/package.nix'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'config.guess-948ae97' -> '/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv'
|
||||
performing daemon worker op: 7
|
||||
`
|
||||
|
||||
segmentBody = `instantiated 'config.sub-948ae97' -> '/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv'`
|
||||
|
||||
segmentSuffix = `
|
||||
performing daemon worker op: 7
|
||||
instantiated 'gnu-config-2024-01-01' -> '/nix/store/ysp83x9nrks28zkblqmnc1s1kb68dr69-gnu-config-2024-01-01.drv'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'bootstrap-stage0-stdenv-linux' -> '/nix/store/3zilrlmq7r6rpzfd94mwss32b62yinj5-bootstrap-stage0-stdenv-linux.drv'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'update-autotools-gnu-config-scripts-hook' -> '/nix/store/7yfwy95p6lcdpljdajs5aw10h6q0sfx0-update-autotools-gnu-config-scripts-hook.drv'
|
||||
source path '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/bintools-wrapper/ld-wrapper.sh' is uncacheable
|
||||
copying '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/bintools-wrapper/ld-wrapper.sh' to the store...`
|
||||
)
|
||||
|
||||
func BenchmarkDecodeInstantiated(b *testing.B) {
|
||||
stderr := strings.NewReader(pluiedevPappardelleOut)
|
||||
var v []string
|
||||
for i := 0; i < b.N; i++ {
|
||||
v, _ = nixbuild.NewInstantiatedDecoder(stderr).Decode()
|
||||
runtime.KeepAlive(v)
|
||||
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 BenchmarkDecodeInstantiatedCopy(b *testing.B) {
|
||||
stderr := strings.NewReader(getchooAtlasOut)
|
||||
var v []string
|
||||
for i := 0; i < b.N; i++ {
|
||||
v, _ = nixbuild.NewInstantiatedDecoder(stderr).Decode()
|
||||
runtime.KeepAlive(v)
|
||||
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)
|
||||
}
|
||||
|
8
io.go
8
io.go
@ -1,8 +0,0 @@
|
||||
package nixbuild
|
||||
|
||||
import "io"
|
||||
|
||||
var (
|
||||
Stdout io.Writer = nil
|
||||
Stderr io.Writer = nil
|
||||
)
|
24
sample_getchoo_atlas_test.go
Normal file
24
sample_getchoo_atlas_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package nixbuild_test
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// github:getchoo/borealis#atlas
|
||||
|
||||
var (
|
||||
//go:embed testdata/getchoo_atlas
|
||||
getchooAtlasOut string
|
||||
|
||||
getchooAtlasInstantiated = sampleSplitPaths(getchooAtlasInstantiatedRaw)
|
||||
//go:embed testdata/instantiated/getchoo_atlas
|
||||
getchooAtlasInstantiatedRaw string
|
||||
|
||||
//go:embed testdata/derivation/show_getchoo_atlas.json
|
||||
getchooAtlasShow []byte
|
||||
|
||||
getchooAtlasCollective = sampleSplitPaths(getchooAtlasCollectiveRaw)
|
||||
//go:embed testdata/derivation/collect_getchoo_atlas
|
||||
getchooAtlasCollectiveRaw string
|
||||
|
||||
//go:embed testdata/format/stdin_getchoo_atlas
|
||||
getchooAtlasStdin string
|
||||
)
|
24
sample_getchoo_glados_test.go
Normal file
24
sample_getchoo_glados_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package nixbuild_test
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// github:getchoo/borealis#glados
|
||||
|
||||
var (
|
||||
//go:embed testdata/getchoo_glados
|
||||
getchooGladosOut string
|
||||
|
||||
getchooGladosInstantiated = sampleSplitPaths(getchooGladosInstantiatedRaw)
|
||||
//go:embed testdata/instantiated/getchoo_glados
|
||||
getchooGladosInstantiatedRaw string
|
||||
|
||||
//go:embed testdata/derivation/show_getchoo_glados.json
|
||||
getchooGladosShow []byte
|
||||
|
||||
getchooGladosCollective = sampleSplitPaths(getchooGladosCollectiveRaw)
|
||||
//go:embed testdata/derivation/collect_getchoo_glados
|
||||
getchooGladosCollectiveRaw string
|
||||
|
||||
//go:embed testdata/format/stdin_getchoo_glados
|
||||
getchooGladosStdin string
|
||||
)
|
26
sample_pluiedev_pappardelle_test.go
Normal file
26
sample_pluiedev_pappardelle_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package nixbuild_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
// git+https://tangled.sh/@pluie.me/flake#pappardelle
|
||||
|
||||
var (
|
||||
//go:embed testdata/pluiedev_pappardelle
|
||||
pluiedevPappardelleOut string
|
||||
|
||||
pluiedevPappardelleInstantiated = sampleSplitPaths(pluiedevPappardelleInstantiatedRaw)
|
||||
//go:embed testdata/instantiated/pluiedev_pappardelle
|
||||
pluiedevPappardelleInstantiatedRaw string
|
||||
|
||||
//go:embed testdata/derivation/show_pluiedev_pappardelle.json
|
||||
pluiedevPappardelleShow []byte
|
||||
|
||||
pluiedevPappardelleCollective = sampleSplitPaths(pluiedevPappardelleCollectiveRaw)
|
||||
//go:embed testdata/derivation/collect_pluiedev_pappardelle
|
||||
pluiedevPappardelleCollectiveRaw string
|
||||
|
||||
//go:embed testdata/format/stdin_pluiedev_pappardelle
|
||||
pluiedevPappardelleStdin string
|
||||
)
|
65
sample_test.go
Normal file
65
sample_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package nixbuild_test
|
||||
|
||||
import "strings"
|
||||
|
||||
var instSample = map[string]string{
|
||||
"bad fields": segmentPrefix + `instantiated 'config.sub-948ae97' ` + segmentSuffix,
|
||||
"unexpected quotes left": segmentPrefix + `instantiated 'config.sub-948ae97' -> /n'` + segmentSuffix,
|
||||
"unexpected quotes right": segmentPrefix + `instantiated 'config.sub-948ae97' -> '/n` + segmentSuffix,
|
||||
"unexpected quotes short": segmentPrefix + `instantiated 'config.sub-948ae97' -> ''` + segmentSuffix,
|
||||
"not absolute": segmentPrefix + `instantiated 'config.sub-948ae97' -> ' '` + segmentSuffix,
|
||||
"good segment": segmentPrefix + segmentBody + segmentSuffix,
|
||||
|
||||
"getchoo atlas": getchooAtlasOut,
|
||||
"getchoo glados": getchooGladosOut,
|
||||
"pluiedev pappardelle": pluiedevPappardelleOut,
|
||||
}
|
||||
|
||||
var instWant = map[string][]string{
|
||||
"getchoo atlas": getchooAtlasInstantiated,
|
||||
"getchoo glados": getchooGladosInstantiated,
|
||||
"pluiedev pappardelle": pluiedevPappardelleInstantiated,
|
||||
}
|
||||
|
||||
var collectWant = map[string][]string{
|
||||
"getchoo atlas": getchooAtlasCollective,
|
||||
"getchoo glados": getchooGladosCollective,
|
||||
"pluiedev pappardelle": pluiedevPappardelleCollective,
|
||||
}
|
||||
|
||||
var drvShow = map[string][]byte{
|
||||
"getchoo atlas": getchooAtlasShow,
|
||||
"getchoo glados": getchooGladosShow,
|
||||
"pluiedev pappardelle": pluiedevPappardelleShow,
|
||||
}
|
||||
|
||||
const (
|
||||
segmentPrefix = `evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/development/libraries/zlib/default.nix'
|
||||
evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/fetchurl/boot.nix'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'zlib-1.3.1.tar.gz' -> '/nix/store/gyks6vvl7x0gq214ldjhi3w4rg37nh8i-zlib-1.3.1.tar.gz.drv'
|
||||
source path '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' is uncacheable
|
||||
copying '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' to the store...
|
||||
performing daemon worker op: 7
|
||||
copied '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' to '/nix/store/96rvfw5vlv1hwwm9sdxhdkkpjyym6p2x-update-autotools-gnu-config-scripts.sh'
|
||||
copied source '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh' -> '/nix/store/96rvfw5vlv1hwwm9sdxhdkkpjyym6p2x-update-autotools-gnu-config-scripts.sh'
|
||||
evaluating file '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/by-name/gn/gnu-config/package.nix'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'config.guess-948ae97' -> '/nix/store/bamwxswxacs3cjdcydv0z7bj22d7g2kc-config.guess-948ae97.drv'
|
||||
performing daemon worker op: 7
|
||||
`
|
||||
|
||||
segmentBody = `instantiated 'config.sub-948ae97' -> '/nix/store/nbsdqpfzh1jlpmh95s69b3iivfcvv3lh-config.sub-948ae97.drv'`
|
||||
|
||||
segmentSuffix = `
|
||||
performing daemon worker op: 7
|
||||
instantiated 'gnu-config-2024-01-01' -> '/nix/store/ysp83x9nrks28zkblqmnc1s1kb68dr69-gnu-config-2024-01-01.drv'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'bootstrap-stage0-stdenv-linux' -> '/nix/store/3zilrlmq7r6rpzfd94mwss32b62yinj5-bootstrap-stage0-stdenv-linux.drv'
|
||||
performing daemon worker op: 7
|
||||
instantiated 'update-autotools-gnu-config-scripts-hook' -> '/nix/store/7yfwy95p6lcdpljdajs5aw10h6q0sfx0-update-autotools-gnu-config-scripts-hook.drv'
|
||||
source path '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/bintools-wrapper/ld-wrapper.sh' is uncacheable
|
||||
copying '/nix/store/vdzlppvrdkz9rv14q4j02g9kpjbww2ww-source/pkgs/build-support/bintools-wrapper/ld-wrapper.sh' to the store...`
|
||||
)
|
||||
|
||||
func sampleSplitPaths(s string) []string { return strings.Split(strings.TrimSpace(s), "\n") }
|
136
stub_test.go
Normal file
136
stub_test.go
Normal file
@ -0,0 +1,136 @@
|
||||
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 }
|
15013
testdata/format/stdin_getchoo_atlas
vendored
Normal file
15013
testdata/format/stdin_getchoo_atlas
vendored
Normal file
File diff suppressed because it is too large
Load Diff
25109
testdata/format/stdin_getchoo_glados
vendored
Normal file
25109
testdata/format/stdin_getchoo_glados
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24847
testdata/format/stdin_pluiedev_pappardelle
vendored
Normal file
24847
testdata/format/stdin_pluiedev_pappardelle
vendored
Normal file
File diff suppressed because it is too large
Load Diff
37432
testdata/getchoo_atlas
vendored
Normal file
37432
testdata/getchoo_atlas
vendored
Normal file
File diff suppressed because it is too large
Load Diff
84832
testdata/getchoo_glados
vendored
Normal file
84832
testdata/getchoo_glados
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43711
testdata/instantiated/getchoo_atlas
vendored
43711
testdata/instantiated/getchoo_atlas
vendored
File diff suppressed because it is too large
Load Diff
95562
testdata/instantiated/getchoo_glados
vendored
95562
testdata/instantiated/getchoo_glados
vendored
File diff suppressed because it is too large
Load Diff
119285
testdata/instantiated/pluiedev_pappardelle
vendored
119285
testdata/instantiated/pluiedev_pappardelle
vendored
File diff suppressed because it is too large
Load Diff
108599
testdata/pluiedev_pappardelle
vendored
Normal file
108599
testdata/pluiedev_pappardelle
vendored
Normal file
File diff suppressed because it is too large
Load Diff
46
util_test.go
Normal file
46
util_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package nixbuild_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/yonah/nixbuild"
|
||||
)
|
||||
|
||||
func TestCollectFromDerivations(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
}{
|
||||
{"getchoo atlas"},
|
||||
{"getchoo glados"},
|
||||
{"pluiedev pappardelle"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var derivations nixbuild.DerivationMap
|
||||
if err := json.Unmarshal(drvShow[tc.name], &derivations); err != nil {
|
||||
t.Fatalf("cannot unmarshal test data: %v", err)
|
||||
}
|
||||
got := nixbuild.CollectFromDerivations(derivations)
|
||||
want := collectWant[tc.name]
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("CollectFromDerivations:\n%s, want\n%s",
|
||||
strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("edge cases", func(t *testing.T) {
|
||||
// this exclusively tests edge cases for nil values and buffer growing, so the data doesn't have to make sense
|
||||
want := []string{"", "big"}
|
||||
got := nixbuild.CollectFromDerivations(nixbuild.DerivationMap{
|
||||
"nil": nil,
|
||||
"big": &nixbuild.Derivation{InputSources: make([]string, 1<<18)},
|
||||
})
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("CollectFromDerivations: %#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user