aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkg/pkg_test.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-08-29 18:58:06 +0900
committerOphestra <cat@gensokyo.uk>2026-08-29 18:58:06 +0900
commitc2d900ec74e03e9c782cfe7b7ce06ff62cda6601 (patch)
treefb13320bfdc62d810aa59769e8809dc2f707919e /pkg/pkg_test.go
parentbd4f29909e0750a4660eaf48e3169777a265b0ab (diff)
pkg: move from internalHEADstagingmasterdevelop
Closes #43. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'pkg/pkg_test.go')
-rw-r--r--pkg/pkg_test.go2375
1 files changed, 2375 insertions, 0 deletions
diff --git a/pkg/pkg_test.go b/pkg/pkg_test.go
new file mode 100644
index 00000000..c11b6ac4
--- /dev/null
+++ b/pkg/pkg_test.go
@@ -0,0 +1,2375 @@
+package pkg_test
+
+import (
+ "archive/tar"
+ "bytes"
+ "context"
+ "crypto/sha512"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "testing/fstest"
+ "time"
+ "unique"
+ "unsafe"
+
+ "hakurei.app/check"
+ "hakurei.app/container"
+ "hakurei.app/fhs"
+ "hakurei.app/internal/info"
+ "hakurei.app/internal/landlock"
+ "hakurei.app/internal/stub"
+ "hakurei.app/message"
+ "hakurei.app/pkg"
+)
+
+var skipLock = func() pkg.CacheAttr {
+ var attr pkg.CacheAttr
+ *(*bool)(unsafe.Pointer(reflect.ValueOf(&attr).
+ Elem().
+ FieldByName("skipLock").
+ UnsafeAddr())) = true
+ return attr
+}()
+
+var (
+ // extension is a string uniquely identifying a set of custom [Artifact]
+ // implementations registered by calling [Register].
+ //
+ //go:linkname extension hakurei.app/pkg.extension
+ extension string
+
+ // opened is false if [Open] was never called.
+ //
+ //go:linkname opened hakurei.app/pkg.opened
+ opened bool
+
+ // irArtifact refers to artifact IR interpretation functions and must not be
+ // written to directly.
+ //
+ //go:linkname irArtifact hakurei.app/pkg.irArtifact
+ irArtifact map[pkg.Kind]pkg.IRReadFunc
+
+ // statusHeader is the header written to all status files in dirStatus.
+ //
+ //go:linkname statusHeader hakurei.app/pkg.statusHeader
+ statusHeader string
+)
+
+// newRContext returns the address of a new [pkg.RContext] unsafely created for
+// the specified [testing.TB].
+func newRContext(tb testing.TB, c *pkg.Cache) *pkg.RContext {
+ var r pkg.RContext
+ rContextVal := reflect.ValueOf(&r).Elem().FieldByName("ctx")
+ reflect.NewAt(
+ rContextVal.Type(),
+ unsafe.Pointer(rContextVal.UnsafeAddr()),
+ ).Elem().Set(reflect.ValueOf(tb.Context()))
+ rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
+ reflect.NewAt(
+ rCacheVal.Type(),
+ unsafe.Pointer(rCacheVal.UnsafeAddr()),
+ ).Elem().Set(reflect.ValueOf(c))
+ return &r
+}
+
+// overrideIdent overrides the ID method of [Artifact].
+type overrideIdent struct {
+ id pkg.ID
+ pkg.TrivialArtifact
+}
+
+func (a overrideIdent) ID() pkg.ID { return a.id }
+
+// overrideIdentFile overrides the ID method of [FileArtifact].
+type overrideIdentFile struct {
+ id pkg.ID
+ pkg.FileArtifact
+}
+
+func (a overrideIdentFile) ID() pkg.ID { return a.id }
+
+// A knownIdentArtifact implements [pkg.KnownIdent] and [Artifact]
+type knownIdentArtifact interface {
+ pkg.KnownIdent
+ pkg.TrivialArtifact
+}
+
+// A knownIdentFile implements [pkg.KnownIdent] and [FileArtifact]
+type knownIdentFile interface {
+ pkg.KnownIdent
+ pkg.FileArtifact
+}
+
+// overrideChecksum overrides the Checksum method of [Artifact].
+type overrideChecksum struct {
+ checksum pkg.Checksum
+ knownIdentArtifact
+}
+
+func (a overrideChecksum) Checksum() pkg.Checksum { return a.checksum }
+
+// overrideChecksumFile overrides the Checksum method of [FileArtifact].
+type overrideChecksumFile struct {
+ checksum pkg.Checksum
+ knownIdentFile
+}
+
+func (a overrideChecksumFile) Checksum() pkg.Checksum { return a.checksum }
+
+// A stubArtifact implements [TrivialArtifact] with hardcoded behaviour.
+type stubArtifact struct {
+ kind pkg.Kind
+ params []byte
+ deps []pkg.Artifact
+
+ cure func(t *pkg.TContext) error
+}
+
+func (a *stubArtifact) Kind() pkg.Kind { return a.kind }
+func (a *stubArtifact) Params(ctx *pkg.IContext) { ctx.Write(a.params) }
+func (a *stubArtifact) Inputs() []pkg.Artifact { return a.deps }
+func (a *stubArtifact) Cure(t *pkg.TContext) error { return a.cure(t) }
+func (*stubArtifact) IsExclusive() bool { return false }
+
+// A stubArtifactF implements [FloodArtifact] with hardcoded behaviour.
+type stubArtifactF struct {
+ kind pkg.Kind
+ params []byte
+ deps []pkg.Artifact
+ excl bool
+
+ cure func(f *pkg.FContext) error
+}
+
+func (a *stubArtifactF) Kind() pkg.Kind { return a.kind }
+func (a *stubArtifactF) Params(ctx *pkg.IContext) { ctx.Write(a.params) }
+func (a *stubArtifactF) Inputs() []pkg.Artifact { return a.deps }
+func (a *stubArtifactF) Cure(f *pkg.FContext) error { return a.cure(f) }
+func (a *stubArtifactF) IsExclusive() bool { return a.excl }
+
+// A stubFile implements [FileArtifact] with hardcoded behaviour.
+type stubFile struct {
+ data []byte
+ err error
+
+ stubArtifact
+}
+
+func (*stubFile) IsExecutable() bool { return false }
+
+func (a *stubFile) Cure(*pkg.RContext) (io.ReadCloser, error) {
+ return io.NopCloser(bytes.NewReader(a.data)), a.err
+}
+
+// newStubFile returns an implementation of [pkg.File] with hardcoded behaviour.
+func newStubFile(
+ kind pkg.Kind,
+ id pkg.ID,
+ sum *pkg.Checksum,
+ data []byte,
+ err error,
+) pkg.FileArtifact {
+ f := overrideIdentFile{id, &stubFile{data, err, stubArtifact{
+ kind,
+ nil,
+ nil,
+ func(*pkg.TContext) error {
+ panic("unreachable")
+ },
+ }}}
+ if sum == nil {
+ return f
+ } else {
+ return overrideChecksumFile{*sum, f}
+ }
+}
+
+// stubExtern implements [External] with hardcoded prepared outcomes.
+type stubExtern struct {
+ artifact map[unique.Handle[pkg.ID]]pkg.Checksum
+ checksum map[unique.Handle[pkg.Checksum]]fstest.MapFS
+ status map[unique.Handle[pkg.ID]]string
+}
+
+func (e stubExtern) Artifact(_ context.Context, id unique.Handle[pkg.ID]) (*pkg.Checksum, error) {
+ if checksum, ok := e.artifact[id]; ok {
+ return &checksum, nil
+ }
+ return nil, nil
+}
+
+func (e stubExtern) Checksum(checksum unique.Handle[pkg.Checksum]) pkg.Artifact {
+ var buf bytes.Buffer
+ if err := pkg.Write(e.checksum[checksum], ".", &buf); err != nil {
+ panic(err)
+ }
+ return pkg.NewArchive(pkg.NewFile("", buf.Bytes()))
+}
+
+func (e stubExtern) Status(_ *pkg.RContext, id unique.Handle[pkg.ID]) (io.ReadCloser, error) {
+ if status, ok := e.status[id]; ok {
+ return io.NopCloser(strings.NewReader(status)), nil
+ }
+ return nil, nil
+}
+
+// destroyArtifact removes all traces of an [Artifact] from the on-disk cache.
+// Do not use this in a test case without a very good reason to do so.
+func destroyArtifact(
+ t *testing.T,
+ base *check.Absolute,
+ c *pkg.Cache,
+ a pkg.Artifact,
+) {
+ if pathname, checksum, err := c.Cure(a); err != nil {
+ t.Fatalf("Cure: error = %v", err)
+ } else if err = os.Remove(pathname.String()); err != nil {
+ t.Fatal(err)
+ } else {
+ p := base.Append(
+ "checksum",
+ pkg.Encode(checksum.Value()),
+ )
+ if err = filepath.WalkDir(p.String(), func(
+ path string,
+ d fs.DirEntry,
+ err error,
+ ) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return os.Chmod(path, 0700)
+ }
+ return nil
+ }); err != nil && !errors.Is(err, os.ErrNotExist) {
+ t.Fatal(err)
+ }
+ if err = os.RemoveAll(p.String()); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+// newDestroyArtifactFunc returns a function that calls destroyArtifact.
+func newDestroyArtifactFunc(a pkg.Artifact) func(
+ t *testing.T,
+ base *check.Absolute,
+ c *pkg.Cache,
+) {
+ return func(
+ t *testing.T,
+ base *check.Absolute,
+ c *pkg.Cache,
+ ) {
+ destroyArtifact(t, base, c, a)
+ }
+}
+
+// destroyStatus counts non-substitution status entries and destroys them.
+func destroyStatus(t *testing.T, base *check.Absolute, c, s int) {
+ dents, err := os.ReadDir(base.Append("status").String())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var gotC, gotS int
+ for _, dent := range dents {
+ if err = os.Remove(base.Append(
+ "status",
+ dent.Name(),
+ ).String()); err != nil {
+ t.Fatal(err)
+ }
+
+ if dent.Type().IsRegular() {
+ gotC++
+ continue
+ }
+ if dent.Type()&fs.ModeSymlink == fs.ModeSymlink {
+ gotS++
+ continue
+ }
+ t.Errorf("%s: %s", dent.Name(), dent.Type())
+ }
+
+ if gotC != c {
+ t.Errorf("status: c = %d, want %d", gotC, c)
+ }
+ if gotS != s {
+ t.Errorf("status: s = %d, want %d", gotS, s)
+ }
+}
+
+func TestIdent(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ a pkg.Artifact
+ want unique.Handle[pkg.ID]
+ }{
+ {"decompress", &stubArtifact{
+ pkg.KindDecompress,
+ []byte{pkg.Gzip, 0, 0, 0, 0, 0, 0, 0},
+ []pkg.Artifact{
+ overrideIdent{pkg.ID{}, new(stubArtifact)},
+ },
+ nil,
+ }, unique.Make[pkg.ID](pkg.MustDecode(
+ "fkmge-H56Ph2LzNNbEUjTypes8LfOxrF_CEXg07kq6ho_nbTNvzQde9-bjB7gyB_",
+ ))},
+ }
+
+ msg := message.New(log.New(os.Stderr, "ident: ", 0))
+ msg.SwapVerbose(true)
+ var cache *pkg.Cache
+ if a, err := check.NewAbs(t.TempDir()); err != nil {
+ t.Fatal(err)
+ } else if cache, err = pkg.Open(t.Context(), msg, a, nil); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(cache.Close)
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := cache.Ident(tc.a); got != tc.want {
+ t.Errorf("Ident: %s, want %s",
+ pkg.Encode(got.Value()),
+ pkg.Encode(tc.want.Value()),
+ )
+ }
+ })
+ }
+}
+
+// An expectsKnown describes an expected file or directory.
+type expectsKnown interface {
+ // hash returns the checksum of the represented data.
+ hash() (checksum pkg.Checksum)
+}
+
+// An expectsChecksum is a prepared checksum value.
+type expectsChecksum pkg.Checksum
+
+// hash returns e.
+func (e expectsChecksum) hash() pkg.Checksum { return e }
+
+// An expectsFile is the contents of a file expected by the test suite.
+type expectsFile []byte
+
+// hash computes the checksum of e.
+func (e expectsFile) hash() (checksum pkg.Checksum) {
+ h := sha512.New384()
+ h.Write(e)
+ h.Sum(checksum[:0])
+ return
+}
+
+// An expectsFS describes the state of a filesystem expected by the test suite.
+type expectsFS fstest.MapFS
+
+// hash computes the checksum of e.
+func (e expectsFS) hash() (checksum pkg.Checksum) {
+ if err := pkg.SumFS(&checksum, fstest.MapFS(e), "."); err != nil {
+ panic(err)
+ }
+ return
+}
+
+// expectsFrom generates expectsFS for a filesystem directory.
+func expectsFrom(pathname string) string {
+ var buf strings.Builder
+ buf.WriteString("expectsFS{\n")
+ if err := filepath.WalkDir(pathname, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+
+ var rel string
+ if rel, err = filepath.Rel(pathname, path); err != nil {
+ return err
+ }
+ buf.WriteString("\t" + strconv.Quote(rel) + ": {Mode: ")
+
+ var fi fs.FileInfo
+ if fi, err = d.Info(); err != nil {
+ return err
+ }
+ mode := fi.Mode()
+
+ switch {
+ case mode.IsDir():
+ buf.WriteString("fs.ModeDir | 0" +
+ strconv.FormatInt(int64(mode&^fs.ModeDir), 8))
+
+ case mode&fs.ModeSymlink != 0:
+ buf.WriteString("fs.ModeSymlink | 0" +
+ strconv.FormatInt(int64(mode&^fs.ModeSymlink), 8) +
+ ", Data: []byte(")
+ var linkname string
+ if linkname, err = os.Readlink(path); err != nil {
+ return err
+ }
+ buf.WriteString(strconv.Quote(linkname))
+ buf.WriteByte(')')
+
+ case mode.IsRegular():
+ buf.WriteString("0" + strconv.FormatInt(int64(mode), 8))
+ var p []byte
+ if p, err = os.ReadFile(path); err != nil {
+ return err
+ }
+
+ if len(p) > 0 {
+ buf.WriteString(", Data: []byte(")
+ buf.WriteString(strconv.Quote(unsafe.String(unsafe.SliceData(p), len(p))))
+ buf.WriteByte(')')
+ }
+ }
+ buf.WriteString("},\n")
+ return nil
+ }); err != nil {
+ panic(err)
+ }
+ buf.WriteString("}")
+ return buf.String()
+}
+
+// cacheTestCase is a test case passed to checkWithCache where a new instance
+// of [pkg.Cache] is prepared for the test case, and is validated and removed
+// on test completion.
+type cacheTestCase struct {
+ name string
+ flags int
+ early func(t *testing.T, base *check.Absolute)
+ f func(t *testing.T, base *check.Absolute, c *pkg.Cache)
+ want expectsFS
+}
+
+const (
+ // checkDestroySubstitutes arranges for substitutes to be destroyed before
+ // measurement during checkWithCache.
+ checkDestroySubstitutes = 1 << (iota + 32)
+)
+
+// makeBase returns a [pkg.Cache] base directory created for tb.
+func makeBase(tb testing.TB) (base *check.Absolute) {
+ tb.Helper()
+
+ base = check.MustAbs(tb.TempDir())
+ if err := os.Chmod(base.String(), 0700); err != nil {
+ tb.Fatal(err)
+ }
+ tb.Cleanup(func() {
+ if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ tb.Error(err)
+ return nil
+ }
+ if !d.IsDir() {
+ return nil
+ }
+ return os.Chmod(path, 0700)
+ }); err != nil {
+ tb.Fatal(err)
+ }
+ })
+ return
+}
+
+// checkWithCache runs a slice of cacheTestCase.
+func checkWithCache(t *testing.T, testCases []cacheTestCase) {
+ t.Helper()
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Helper()
+ t.Parallel()
+
+ base := makeBase(t)
+ msg := message.New(log.New(os.Stderr, "cache: ", 0))
+ msg.SwapVerbose(testing.Verbose())
+
+ flags := tc.flags | pkg.CSuppressInit
+
+ if info.CanDegrade {
+ if _, err := landlock.GetABI(); err != nil {
+ if !errors.Is(err, syscall.ENOSYS) {
+ t.Fatalf("LandlockGetABI: error = %v", err)
+ }
+ flags |= pkg.CHostAbstract
+ t.Log("Landlock LSM is unavailable, setting CHostAbstract")
+ }
+ }
+
+ var scrubFunc func() error // scrub after hashing
+ if c, err := pkg.Open(t.Context(), msg, base, &pkg.CacheAttr{
+ Cures: 1 << 4,
+ Flags: flags,
+ }); err != nil {
+ t.Fatalf("Open: error = %v", err)
+ } else {
+ t.Cleanup(c.Close)
+ if tc.early != nil {
+ tc.early(t, base)
+ }
+ tc.f(t, base, c)
+ scrubFunc = func() error {
+ err = c.Scrub(1 << 7)
+ idents, checksums, cleanErr := c.Clean(false, false)
+ if len(idents) > 0 {
+ t.Errorf("destroyed %d idents", len(idents))
+ }
+ if len(checksums) > 0 {
+ t.Errorf("destroyed %d checksums", len(checksums))
+ }
+ return errors.Join(err, cleanErr)
+ }
+ }
+
+ var restoreTemp bool
+ if _, err := os.Lstat(base.Append("temp").String()); err != nil {
+ if !errors.Is(err, os.ErrNotExist) {
+ t.Fatal(err)
+ }
+ } else {
+ restoreTemp = true
+ }
+
+ // destroy lock and variant file to avoid changing cache checksums
+ for _, s := range []string{
+ "lock",
+ "variant",
+ } {
+ pathname := base.Append(s)
+ if p, err := os.ReadFile(pathname.String()); err != nil {
+ t.Fatal(err)
+ } else if len(p) != 0 {
+ t.Fatalf("file %q: %q", s, string(p))
+ }
+ if err := os.Remove(pathname.String()); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // destroy non-deterministic substitutes
+ if tc.flags&checkDestroySubstitutes != 0 {
+ substitute := base.Append("substitute")
+ if err := os.RemoveAll(substitute.String()); err != nil {
+ t.Fatal(err)
+ } else if err = os.Mkdir(substitute.String(), 0700); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // destroy empty status directory
+ if err := syscall.Rmdir(base.Append("status").String()); err != nil {
+ if !errors.Is(err, syscall.ENOTEMPTY) {
+ t.Fatal(err)
+ }
+ }
+
+ // destroy empty fault directory
+ if err := os.Remove(base.Append("fault").String()); err != nil {
+ t.Fatal(err)
+ }
+
+ want := tc.want.hash()
+
+ var checksum pkg.Checksum
+ if err := pkg.SumDir(&checksum, base); err != nil {
+ t.Fatalf("SumDir: error = %v", err)
+ } else if checksum != want {
+ t.Fatal(expectsFrom(base.String()))
+ }
+
+ if err := scrubFunc(); err != nil {
+ t.Fatal("cache contains inconsistencies\n\n" + err.Error())
+ }
+
+ if restoreTemp {
+ if err := os.Mkdir(
+ base.Append("temp").String(),
+ 0700,
+ ); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // validate again to make sure scrub did not condemn anything
+ if err := pkg.SumDir(&checksum, base); err != nil {
+ t.Fatalf("SumDir: error = %v", err)
+ } else if checksum != want {
+ t.Fatalf("(scrubbed) %s", expectsFrom(base.String()))
+ }
+ })
+ }
+}
+
+// A cureStep contains an [Artifact] to be cured, and the expected outcome.
+type cureStep struct {
+ name string
+
+ a pkg.Artifact
+
+ pathname *check.Absolute
+ output expectsKnown
+ whence int
+ err error
+}
+
+// ignorePathname is passed to cureMany to skip the pathname check.
+var ignorePathname = check.MustAbs("/\x00")
+
+// cureMany cures many artifacts against a [Cache] and checks their outcomes.
+func cureMany(t *testing.T, c *pkg.Cache, steps []cureStep) {
+ t.Helper()
+
+ makeChecksumH := func(checksum pkg.Checksum) unique.Handle[pkg.Checksum] {
+ if checksum == (pkg.Checksum{}) {
+ return unique.Handle[pkg.Checksum]{}
+ }
+ return unique.Make(checksum)
+ }
+
+ for _, step := range steps {
+ t.Log("cure step:", step.name)
+ if pathname, checksum, whence, err := c.CureWhence(step.a); !reflect.DeepEqual(err, step.err) {
+ faults, _err := c.ReadFaults(step.a)
+ if _err != nil {
+ t.Errorf("ReadFaults: error = %v", _err)
+ }
+
+ var p []byte
+ for _, fault := range faults {
+ p, _err = os.ReadFile(fault.String())
+ if _err != nil {
+ t.Error(_err)
+ continue
+ }
+ t.Log(string(p))
+ t.Logf("faulting cure terminated %s ago", time.Since(faults[0].Time()))
+ }
+ t.Fatalf("Cure: error = %v, want %v", err, step.err)
+ } else if step.whence != whence {
+ t.Fatalf("Cure: whence = %s, want %s", pkg.WhenceString(whence), pkg.WhenceString(step.whence))
+ } else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
+ t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
+ } else if step.output == nil || checksum != makeChecksumH(step.output.hash()) {
+ if pathname != nil {
+ if name, _err := filepath.EvalSymlinks(pathname.String()); _err != nil {
+ t.Fatal(_err)
+ } else {
+ t.Fatal(expectsFrom(name))
+ }
+ } else if checksum != (unique.Handle[pkg.Checksum]{}) {
+ t.Fatalf("Cure: unexpected checksum %s", pkg.Encode(checksum.Value()))
+ }
+ } else {
+ v := any(err)
+ if err == nil {
+ v = pathname
+ }
+ var checksumVal pkg.Checksum
+ if checksum != (unique.Handle[pkg.Checksum]{}) {
+ checksumVal = checksum.Value()
+ }
+ t.Log(pkg.Encode(checksumVal)+":", v)
+ }
+ }
+}
+
+// newWantScrubError returns the address to a new [ScrubError] for base.
+func newWantScrubError(base *check.Absolute) *pkg.ScrubError {
+ return &pkg.ScrubError{
+ ChecksumMismatches: []pkg.ChecksumMismatchError{
+ {Got: pkg.MustDecode(
+ "vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX",
+ ), Want: pkg.Checksum{0xff, 0}},
+ },
+ DanglingIdentifiers: []pkg.ID{
+ {0xfe, 0},
+ {0xfe, 0xfe},
+ {0xfe, 0xff},
+ },
+ Errs: map[unique.Handle[string]][]error{
+ base.Append("checksum", "__8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").Handle(): {
+ pkg.InvalidFileModeError(fs.ModeSymlink),
+ },
+
+ base.Append("checksum", "invalid").Handle(): {
+ base64.CorruptInputError(4),
+ },
+
+ base.Append("nonexistent").Handle(): {
+ base64.CorruptInputError(8),
+ },
+
+ base.Append("identifier", pkg.Encode(pkg.ID{0xfe, 0xff})).Handle(): {
+ &os.PathError{
+ Op: "readlink",
+ Path: base.Append(
+ "identifier",
+ pkg.Encode(pkg.ID{0xfe, 0xff}),
+ ).String(),
+ Err: syscall.EINVAL,
+ },
+ },
+ base.Append("identifier", "invalid").Handle(): {
+ base64.CorruptInputError(4),
+ },
+ },
+ }
+}
+
+func TestCache(t *testing.T) {
+ t.Parallel()
+
+ testdata := expectsFile("" +
+ "\x00\x00\x00\x00" +
+ "\xad\x0b\x00" +
+ "\x04" +
+ "\xfe\xfe\x00\x00" +
+ "\xfe\xca\x00\x00")
+
+ testCases := []cacheTestCase{
+ {"file", pkg.CValidateKnown | pkg.CAssumeChecksum, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ identifier := (pkg.ID)(bytes.Repeat([]byte{
+ 0x75, 0xe6, 0x9d, 0x6d, 0xe7, 0x9f,
+ }, 8))
+ wantPathname := base.Append(
+ "identifier",
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
+ )
+ identifier0 := (pkg.ID)(bytes.Repeat([]byte{
+ 0x71, 0xa7, 0xde, 0x6d, 0xa6, 0xde,
+ }, 8))
+ wantPathname0 := base.Append(
+ "identifier",
+ "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe",
+ )
+ failingFile := newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 3},
+ nil,
+ nil, struct {
+ _ []byte
+ stub.UniqueError
+ }{UniqueError: 0xbad},
+ )
+
+ cureMany(t, c, []cureStep{
+ {"initial file", newStubFile(
+ pkg.KindHTTPGet,
+ identifier,
+ new(testdata.hash()),
+ testdata, nil,
+ ), wantPathname, testdata, pkg.WNew, nil},
+
+ {"identical content", newStubFile(
+ pkg.KindHTTPGet,
+ identifier0,
+ new(testdata.hash()),
+ testdata, nil,
+ ), wantPathname0, testdata, pkg.WCache, nil},
+
+ {"existing entry", newStubFile(
+ pkg.KindHTTPGet,
+ identifier,
+ new(testdata.hash()),
+ testdata, nil,
+ ), wantPathname, testdata, pkg.WCache, nil},
+
+ {"checksum mismatch", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 0},
+ new(pkg.Checksum),
+ testdata, nil,
+ ), nil, nil, pkg.WNew, &pkg.ChecksumMismatchError{
+ Got: testdata.hash(),
+ }},
+
+ {"store without validation", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.MustDecode("vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX"),
+ nil,
+ []byte{0}, nil,
+ ), base.Append(
+ "identifier",
+ "vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX",
+ ), expectsChecksum{
+ 0xbe, 0xc0, 0x21, 0xb4, 0xf3, 0x68,
+ 0xe3, 0x06, 0x91, 0x34, 0xe0, 0x12,
+ 0xc2, 0xb4, 0x30, 0x70, 0x83, 0xd3,
+ 0xa9, 0xbd, 0xd2, 0x06, 0xe2, 0x4e,
+ 0x5f, 0x0d, 0x86, 0xe1, 0x3d, 0x66,
+ 0x36, 0x65, 0x59, 0x33, 0xec, 0x2b,
+ 0x41, 0x34, 0x65, 0x96, 0x68, 0x17,
+ 0xa9, 0xc2, 0x08, 0xa1, 0x17, 0x17,
+ }, pkg.WNew, nil},
+
+ {"incomplete implementation", struct{ pkg.Artifact }{&stubArtifact{
+ kind: pkg.KindExec,
+ params: []byte("artifact overridden to be incomplete"),
+ }}, nil, nil, pkg.WNew, pkg.InvalidArtifactError(pkg.MustDecode(
+ "poVrv3zNINbHd-WUJihwgWQwJpbHURFHfpY7Zux-ztto_bQhhS5PdpbNMjdkXv-V",
+ ))},
+
+ {"error passthrough", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 1},
+ nil,
+ nil, stub.UniqueError(0xcafe),
+ ), nil, nil, pkg.WNew, stub.UniqueError(0xcafe)},
+
+ {"error caching", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 1},
+ nil,
+ nil, nil,
+ ), nil, nil, pkg.WCache, stub.UniqueError(0xcafe)},
+
+ {"cache hit bad type", overrideChecksum{testdata.hash(), overrideIdent{pkg.ID{0xff, 2}, &stubArtifact{
+ kind: pkg.KindTar,
+ }}}, nil, nil, pkg.WCache, pkg.InvalidFileModeError(
+ 0400,
+ )},
+
+ {"noncomparable error", &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("artifact with dependency returning noncomparable error"),
+ deps: []pkg.Artifact{failingFile},
+
+ cure: func(f *pkg.FContext) error {
+ panic("attempting to cure impossible artifact")
+ },
+ }, nil, nil, pkg.WNew, pkg.InputError{
+ failingFile: struct {
+ _ []byte
+ stub.UniqueError
+ }{UniqueError: 0xbad},
+ }},
+ })
+
+ if c0, err := pkg.Open(
+ t.Context(),
+ message.New(nil),
+ base, &skipLock,
+ ); err != nil {
+ t.Fatalf("open: error = %v", err)
+ } else {
+ t.Cleanup(c.Close) // check doubled cancel
+ cureMany(t, c0, []cureStep{
+ {"cache hit ident", overrideIdent{
+ id: identifier,
+ }, wantPathname, testdata, pkg.WCache, nil},
+
+ {"cache miss checksum match", newStubFile(
+ pkg.KindHTTPGet,
+ testdata.hash(),
+ nil,
+ testdata,
+ nil,
+ ), base.Append(
+ "identifier",
+ pkg.Encode(testdata.hash()),
+ ), testdata, pkg.WNew, nil},
+ })
+
+ // cure after close
+ c.Close()
+ if _, _, err = c.Cure(&stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("unreachable artifact cured after cancel"),
+ deps: []pkg.Artifact{pkg.NewFile("", []byte("unreachable dependency"))},
+ }); !reflect.DeepEqual(err, context.Canceled) {
+ t.Fatalf("(closed) Cure: error = %v", err)
+ }
+ }
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: 0400, Data: []byte{0}},
+ "checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq": {Mode: 0400, Data: []byte{0, 0, 0, 0, 0xad, 0xb, 0, 4, 0xfe, 0xfe, 0, 0, 0xfe, 0xca, 0, 0}},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
+ "identifier/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
+ "identifier/cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
+ "identifier/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"directory", pkg.CAssumeChecksum, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ id := pkg.MustDecode(
+ "HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY",
+ )
+ makeSample := func(t *pkg.TContext) error {
+ work := t.GetWorkDir()
+ if err := os.Mkdir(work.String(), 0700); err != nil {
+ return err
+ }
+
+ if err := os.WriteFile(
+ work.Append("check").String(),
+ []byte{0, 0},
+ 0400,
+ ); err != nil {
+ return err
+ }
+
+ if err := os.MkdirAll(work.Append(
+ "lib",
+ "pkgconfig",
+ ).String(), 0700); err != nil {
+ return err
+ }
+
+ return os.Symlink(
+ "/proc/nonexistent/libedac.so",
+ work.Append(
+ "lib",
+ "libedac.so",
+ ).String(),
+ )
+ }
+ want := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+
+ "check": {Mode: 0400, Data: []byte{0, 0}},
+
+ "lib": {Mode: fs.ModeDir | 0700},
+ "lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
+
+ "lib/pkgconfig": {Mode: fs.ModeDir | 0700},
+ }
+ wantPathname := base.Append(
+ "identifier",
+ pkg.Encode(id),
+ )
+
+ id0 := pkg.MustDecode(
+ "Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa",
+ )
+ wantPathname0 := base.Append(
+ "identifier",
+ pkg.Encode(id0),
+ )
+
+ makeGarbage := func(work *check.Absolute, wantErr error) error {
+ if err := os.Mkdir(work.String(), 0700); err != nil {
+ return err
+ }
+
+ mode := fs.FileMode(0)
+ if wantErr == nil {
+ mode = 0500
+ }
+
+ if err := os.MkdirAll(work.Append(
+ "lib",
+ "pkgconfig",
+ ).String(), 0700); err != nil {
+ return err
+ }
+
+ if err := os.WriteFile(work.Append(
+ "lib",
+ "check",
+ ).String(), nil, 0400&mode); err != nil {
+ return err
+ }
+
+ if err := os.Chmod(work.Append(
+ "lib",
+ "pkgconfig",
+ ).String(), 0500&mode); err != nil {
+ return err
+ }
+ if err := os.Chmod(work.Append(
+ "lib",
+ ).String(), 0500&mode); err != nil {
+ return err
+ }
+
+ return wantErr
+ }
+
+ cureMany(t, c, []cureStep{
+ {"initial directory", overrideChecksum{want.hash(), overrideIdent{id, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: makeSample,
+ }}}, wantPathname, want, pkg.WNew, nil},
+
+ {"identical identifier", overrideChecksum{want.hash(), overrideIdent{id, &stubArtifact{
+ kind: pkg.KindTar,
+ }}}, wantPathname, want, pkg.WCache, nil},
+
+ {"identical checksum", overrideIdent{id0, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: makeSample,
+ }}, wantPathname0, want, pkg.WNew, nil},
+
+ {"cure fault", overrideIdent{pkg.ID{0xff, 0}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return makeGarbage(t.GetWorkDir(), stub.UniqueError(0xcafe))
+ },
+ }}, nil, nil, pkg.WNew, stub.UniqueError(0xcafe)},
+
+ {"checksum mismatch", overrideChecksum{pkg.Checksum{}, overrideIdent{pkg.ID{0xff, 1}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return makeGarbage(t.GetWorkDir(), nil)
+ },
+ }}}, nil, nil, pkg.WNew, &pkg.ChecksumMismatchError{
+ Got: pkg.MustDecode(
+ "CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT",
+ ),
+ }},
+
+ {"cache hit bad type", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 2},
+ new(want.hash()),
+ testdata, nil,
+ ), nil, nil, pkg.WCache, pkg.InvalidFileModeError(
+ fs.ModeDir | 0500,
+ )},
+
+ {"openFile directory", overrideIdent{pkg.ID{0xff, 3}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ r, err := t.Open(overrideChecksumFile{checksum: want.hash()})
+ if err != nil {
+ panic(err)
+ }
+ _, err = io.ReadAll(r)
+ return err
+ },
+ }}, nil, nil, pkg.WNew, &os.PathError{
+ Op: "read",
+ Path: base.Append(
+ "checksum",
+ pkg.Encode(want.hash()),
+ ).String(),
+ Err: syscall.EISDIR,
+ }},
+
+ {"no output", overrideIdent{pkg.ID{0xff, 4}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return nil
+ },
+ }}, nil, nil, pkg.WNew, pkg.NoOutputError{}},
+
+ {"file output", overrideIdent{pkg.ID{0xff, 5}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return os.WriteFile(t.GetWorkDir().String(), []byte{0}, 0400)
+ },
+ }}, nil, nil, pkg.WNew, errors.New("non-file artifact produced regular file")},
+
+ {"symlink output", overrideIdent{pkg.ID{0xff, 6}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return os.Symlink(
+ t.GetWorkDir().String(),
+ t.GetWorkDir().String(),
+ )
+ },
+ }}, nil, nil, pkg.WNew, pkg.InvalidFileModeError(
+ fs.ModeSymlink | 0777,
+ )},
+
+ {"alternative", &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("substitutable artifact"),
+ deps: []pkg.Artifact{newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 8},
+ nil,
+ []byte("substitutable dependency"),
+ nil,
+ )},
+
+ cure: func(f *pkg.FContext) error {
+ return makeSample(&f.TContext)
+ },
+ }, base.Append(
+ "identifier",
+ "gIH5eti07nBAhVti3ZTn8EIGOBCZWWvNpxg5CmCZlDzIC4mBdomPmQLeEpf1IQg9",
+ ), want, pkg.WNew, nil},
+
+ {"substitutable", &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("substitutable artifact"),
+ deps: []pkg.Artifact{newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 10},
+ nil,
+ []byte("substitutable dependency"),
+ nil,
+ )},
+
+ cure: func(f *pkg.FContext) error {
+ panic("substitution missed")
+ },
+ }, base.Append(
+ "identifier",
+ "VGjpp7NVw3OYLpgOQumfM0KcJgY3suz6torykUczcg4bGT24ySkPn5hy-4dNqqkR",
+ ), want, pkg.WSubstitute, nil},
+ })
+
+ if c0, err := pkg.Open(
+ t.Context(),
+ message.New(nil),
+ base, &skipLock,
+ ); err != nil {
+ t.Fatalf("open: error = %v", err)
+ } else {
+ t.Cleanup(c.Close)
+ cureMany(t, c0, []cureStep{
+ {"substitutable", &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("substitutable artifact"),
+ deps: []pkg.Artifact{newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 0xff, 0xfd, 0xfd},
+ nil,
+ []byte("substitutable dependency"),
+ nil,
+ )},
+
+ cure: func(f *pkg.FContext) error {
+ panic("substitution missed")
+ },
+ }, base.Append(
+ "identifier",
+ "zeZ4JEZ0atAKl6Am67cBID6-KR18nZGgeYfU4qTuYaWhQC4eAn0dyTXdIBH9Bos8",
+ ), want, pkg.WSubstitute, nil},
+ })
+ }
+
+ if dents, err := os.ReadDir(base.Append("status").String()); err != nil {
+ t.Fatal(err)
+ } else if len(dents) > 0 {
+ t.Errorf("ReadDir: %v", dents)
+ }
+
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/7lfQ4QwSpV8nw7IDh0JiQ_jqUPrPv3_Vfie034RxsSy-cy4vO8DVvxgpx2LW08oO": {Mode: 0400, Data: []byte("substitutable dependency")},
+ "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b": {Mode: fs.ModeDir | 0500},
+ "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/check": {Mode: 0400, Data: []byte{0, 0}},
+ "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib": {Mode: fs.ModeDir | 0700},
+ "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/pkgconfig": {Mode: fs.ModeDir | 0700},
+ "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
+ "identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
+ "identifier/zeZ4JEZ0atAKl6Am67cBID6-KR18nZGgeYfU4qTuYaWhQC4eAn0dyTXdIBH9Bos8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
+ "identifier/___9_QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/7lfQ4QwSpV8nw7IDh0JiQ_jqUPrPv3_Vfie034RxsSy-cy4vO8DVvxgpx2LW08oO")},
+ "identifier/_wgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/7lfQ4QwSpV8nw7IDh0JiQ_jqUPrPv3_Vfie034RxsSy-cy4vO8DVvxgpx2LW08oO")},
+ "identifier/_woAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/7lfQ4QwSpV8nw7IDh0JiQ_jqUPrPv3_Vfie034RxsSy-cy4vO8DVvxgpx2LW08oO")},
+ "identifier/VGjpp7NVw3OYLpgOQumfM0KcJgY3suz6torykUczcg4bGT24ySkPn5hy-4dNqqkR": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
+ "identifier/gIH5eti07nBAhVti3ZTn8EIGOBCZWWvNpxg5CmCZlDzIC4mBdomPmQLeEpf1IQg9": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "substitute/nWPg893oqn_YNN8kgfZrs_LVLwP90e0mKxqU8UwoazQh-yBPwhoviD7of6YALx-M": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"pending", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ wantErr := stub.UniqueError(0xcafe)
+ n, ready := make(chan struct{}), make(chan struct{})
+ go func() {
+ if _, _, err := c.Cure(overrideIdent{pkg.ID{0xff}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ close(ready)
+ <-n
+ return wantErr
+ },
+ }}); !reflect.DeepEqual(err, wantErr) {
+ panic(fmt.Sprintf("Cure: error = %v, want %v", err, wantErr))
+ }
+ }()
+
+ <-ready
+ wCureDone := make(chan struct{})
+ go func() {
+ if _, _, err := c.Cure(overrideIdent{pkg.ID{0xff}, &stubArtifact{
+ kind: pkg.KindTar,
+ }}); !reflect.DeepEqual(err, wantErr) {
+ panic(fmt.Sprintf("Cure: error = %v, want %v", err, wantErr))
+ }
+ close(wCureDone)
+ }()
+
+ // check cache activity while a cure is blocking
+ cureMany(t, c, []cureStep{
+ {"error passthrough", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xff, 1},
+ nil,
+ nil, stub.UniqueError(0xbad),
+ ), nil, nil, pkg.WNew, stub.UniqueError(0xbad)},
+
+ {"file output", overrideIdent{pkg.ID{0xff, 2}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return os.WriteFile(
+ t.GetWorkDir().String(),
+ []byte{0},
+ 0400,
+ )
+ },
+ }}, nil, nil, pkg.WNew, errors.New(
+ "non-file artifact produced regular file",
+ )},
+ })
+
+ wantErrScrub := &pkg.ScrubError{
+ Errs: map[unique.Handle[string]][]error{
+ base.Handle(): {errors.New("scrub began with pending artifacts")},
+ },
+ }
+ if err := c.Scrub(1 << 6); !reflect.DeepEqual(err, wantErrScrub) {
+ t.Fatalf("Scrub: error = %#v, want %#v", err, wantErrScrub)
+ }
+
+ notify := c.Done(unique.Make(pkg.ID{0xff}))
+ go close(n)
+ if notify != nil {
+ <-notify
+ }
+ for c.Done(unique.Make(pkg.ID{0xff})) != nil {
+ }
+ <-wCureDone
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"cancel abort block", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ var started sync.WaitGroup
+ defer started.Wait()
+
+ blockCures := func(d byte, e stub.UniqueError, n int) {
+ started.Add(n)
+ for i := range n {
+ wg.Go(func() {
+ if _, _, err := c.Cure(overrideIdent{pkg.ID{d, byte(i)}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ started.Done()
+ <-t.Unwrap().Done()
+ return e + stub.UniqueError(i)
+ },
+ }}); !reflect.DeepEqual(err, e+stub.UniqueError(i)) {
+ panic(err)
+ }
+ })
+ }
+ started.Wait()
+ }
+
+ blockCures(0xfd, 0xbad, 16)
+ c.Abort()
+ wg.Wait()
+
+ blockCures(0xfd, 0xcafe, 16)
+ c.Abort()
+ wg.Wait()
+
+ blockCures(0xff, 0xbad, 1)
+ if !c.Cancel(unique.Make(pkg.ID{0xff})) {
+ t.Fatal("missed cancellation")
+ }
+ wg.Wait()
+
+ blockCures(0xff, 0xcafe, 1)
+ if !c.Cancel(unique.Make(pkg.ID{0xff})) {
+ t.Fatal("missed cancellation")
+ }
+ wg.Wait()
+
+ for c.Cancel(unique.Make(pkg.ID{0xff})) {
+ }
+
+ c.Close()
+ c.Abort()
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"no assume checksum", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ makeGarbage := func(work *check.Absolute, wantErr error) error {
+ if err := os.Mkdir(work.String(), 0700); err != nil {
+ return err
+ }
+
+ if err := os.WriteFile(work.Append(
+ "check",
+ ).String(), nil, 0400); err != nil {
+ return err
+ }
+
+ return wantErr
+ }
+
+ want := expectsChecksum(pkg.MustDecode(
+ "Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M",
+ ))
+
+ cureMany(t, c, []cureStep{
+ {"create", overrideChecksum{want.hash(), overrideIdent{pkg.ID{0xff, 0}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return makeGarbage(t.GetWorkDir(), nil)
+ },
+ }}}, base.Append(
+ "identifier",
+ pkg.Encode(pkg.ID{0xff, 0}),
+ ), want, pkg.WNew, nil},
+
+ {"reject", overrideChecksum{want.hash(), overrideIdent{pkg.ID{0xfe, 1}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return makeGarbage(t.GetWorkDir(), stub.UniqueError(0xbad))
+ },
+ }}}, nil, nil, pkg.WNew, stub.UniqueError(0xbad)},
+
+ {"match", overrideChecksum{want.hash(), overrideIdent{pkg.ID{0xff, 1}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ return makeGarbage(t.GetWorkDir(), nil)
+ },
+ }}}, base.Append(
+ "identifier",
+ pkg.Encode(pkg.ID{0xff, 1}),
+ ), want, pkg.WNew, nil},
+ })
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M": {Mode: fs.ModeDir | 0500},
+ "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check": {Mode: 0400, Data: []byte{}},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
+ "identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"scrub", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ cureMany(t, c, []cureStep{
+ {"bad measured file", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.Checksum{0xfe, 0},
+ &pkg.Checksum{0xff, 0},
+ []byte{0}, nil,
+ ), base.Append(
+ "identifier",
+ pkg.Encode(pkg.Checksum{0xfe, 0}),
+ ), expectsChecksum{0xff, 0}, pkg.WNew, nil},
+ })
+
+ for _, p := range [][]string{
+ {"identifier", "invalid"},
+ {"identifier", pkg.Encode(pkg.ID{0xfe, 0xff})},
+ {"checksum", "invalid"},
+ } {
+ if err := os.WriteFile(
+ base.Append(p...).String(),
+ nil,
+ 0400,
+ ); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for _, p := range [][]string{
+ {"../nonexistent", "checksum", pkg.Encode(pkg.Checksum{0xff, 0xff})},
+ {"../nonexistent", "identifier", pkg.Encode(pkg.Checksum{0xfe, 0xfe})},
+ } {
+ if err := os.Symlink(
+ p[0],
+ base.Append(p[1:]...).String(),
+ ); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ wantErr := newWantScrubError(base)
+ if err := c.Scrub(1 << 6); !reflect.DeepEqual(err, wantErr) {
+ t.Fatalf("Scrub: error =\n%s\nwant\n%s", err, wantErr)
+ }
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"status substitute clean", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ destroyed := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("destroyed"),
+ deps: []pkg.Artifact{
+ pkg.NewFile("destroyed-input", []byte("destroyed")),
+ },
+ cure: func(f *pkg.FContext) error {
+ if w, err := f.GetStatusWriter(); err != nil {
+ return err
+ } else if _, err = w.Write([]byte("destroyed")); err != nil {
+ return err
+ }
+
+ p := f.GetWorkDir()
+ if err := os.MkdirAll(p.String(), 0755); err != nil {
+ return err
+ }
+ return os.WriteFile(p.Append("result").String(), nil, 0444)
+ },
+ }
+ substituted := new(*destroyed)
+ substituted.deps = []pkg.Artifact{
+ pkg.NewFile("destroyed-input-0", []byte("destroyed")),
+ }
+ substituted.cure = func(*pkg.FContext) error {
+ panic("substitutable cure reached")
+ }
+
+ cureMany(t, c, []cureStep{
+ {"destroyed", destroyed, base.Append(
+ "identifier",
+ pkg.Encode(c.Ident(destroyed).Value()),
+ ), expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ "result": {Mode: 0444},
+ }, pkg.WNew, nil},
+
+ {"substituted", substituted, base.Append(
+ "identifier",
+ pkg.Encode(c.Ident(substituted).Value()),
+ ), expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ "result": {Mode: 0444},
+ }, pkg.WSubstitute, nil},
+ })
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
+ "checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
+ "checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV": {Mode: 0400, Data: []byte("destroyed")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/CLyyxJWn1yUGFs_0KnJ4ZSU2hH4-elvGFRB36x14LrNrKBJPxvORhL9O4Rc7oeYU": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
+ "identifier/arv8a6z4i0x6pUb5Z2T-3iE4ETvyZj-uReHGKsS7uiBgMgKNEOzTGG3LIZb8vp0L": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
+ "identifier/heRgWhNJmhm898V68kw0ta76t4rP1o29tYW_TrdyBWXGfo37K3QWA7Z-JkLXAwcn": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
+ "identifier/q-VoVBI3IjcWTqwIokpi3y05CP113CHgHslc-XWnhOvDULwzVc3q9Y5xOCm_YyLZ": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
+
+ "status": {Mode: fs.ModeDir | 0700},
+ "status/q-VoVBI3IjcWTqwIokpi3y05CP113CHgHslc-XWnhOvDULwzVc3q9Y5xOCm_YyLZ": {Mode: fs.ModeSymlink | 0777, Data: []byte("dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x")},
+ "status/heRgWhNJmhm898V68kw0ta76t4rP1o29tYW_TrdyBWXGfo37K3QWA7Z-JkLXAwcn": {Mode: 0400, Data: []byte(statusHeader + "destroyed")},
+ "status/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: 0400, Data: []byte(statusHeader + "destroyed")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "substitute/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"extern", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ a := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("extern"),
+ }
+ wantIdent := c.Ident(a)
+ wantOutput := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ "result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")},
+ }
+ var wantChecksum pkg.Checksum
+ if err := pkg.SumFS(
+ &wantChecksum,
+ fstest.MapFS(wantOutput),
+ ".",
+ ); err != nil {
+ t.Fatal(err)
+ }
+ wantChecksumH := unique.Make(wantChecksum)
+
+ _a := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("extern substitute"),
+ deps: []pkg.Artifact{pkg.NewFile("", nil)},
+ }
+ _wantIdent := c.Ident(_a)
+ _wantOutput := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ }
+ var _wantChecksum pkg.Checksum
+ if err := pkg.SumFS(
+ &_wantChecksum,
+ fstest.MapFS(_wantOutput),
+ ".",
+ ); err != nil {
+ t.Fatal(err)
+ }
+ _wantChecksumH := unique.Make(_wantChecksum)
+
+ kca := pkg.NewExec(
+ "", "",
+ new(pkg.Checksum), 0, false, false,
+ fhs.AbsRoot, nil, fhs.AbsRoot, nil,
+ )
+ kcIdent := c.Ident(kca)
+
+ c.SetExternal(stubExtern{
+ artifact: map[unique.Handle[pkg.ID]]pkg.Checksum{
+ wantIdent: wantChecksum,
+ _wantIdent: _wantChecksum,
+ kcIdent: wantChecksum,
+ },
+ checksum: map[unique.Handle[pkg.Checksum]]fstest.MapFS{
+ wantChecksumH: fstest.MapFS(wantOutput),
+ _wantChecksumH: fstest.MapFS(_wantOutput),
+ },
+ status: map[unique.Handle[pkg.ID]]string{
+ wantIdent: "\x00",
+ kcIdent: "unreachable",
+ },
+ })
+
+ cureMany(t, c, []cureStep{
+ {"extern", a, base.Append(
+ "identifier",
+ pkg.Encode(wantIdent.Value()),
+ ), wantOutput, pkg.WExternal, nil},
+
+ {"substitute", _a, base.Append(
+ "identifier",
+ pkg.Encode(_wantIdent.Value()),
+ ), _wantOutput, pkg.WExternal, nil},
+
+ {"mismatch", kca, nil, nil, pkg.WExternal, &pkg.ChecksumMismatchError{
+ Got: wantChecksum,
+ }},
+ })
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400},
+ "checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl": {Mode: fs.ModeDir | 0500},
+ "checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl/result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/k8yyhlBV3JpYzzywAnrRWkfp2TmgIDNC6FqMFwrrR2x0C3unBLveDNKgigOjG9MM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/Xys8hPpJsKirZmTTuT7SwQ6661TCp_ZRu8iUjPPD7pC5E3ll7xw4zhNlZ2PVcz_-": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+ "identifier/8sInO_dhPW9kweit7jVuH_vtMkRxlZT7XE4_yEKPZmpGkgVxkmxz0OpXgbMsH6ET": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/ofaXbB5gIza_7W4Rzo5XDe_8FwlCPKWn3TBcmGgFAkkTWMl5GXGmOzX89Jd2Mk2A": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
+ "identifier/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+
+ "status": {Mode: fs.ModeDir | 0700},
+ "status/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: 0400, Data: []byte("\x00")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "substitute/socGRnhdsJlJ4Z01ZwHFZdLzFH0rWvoaJrylcUKtvhBdGsE7VoYl-vdUuvfK8B4I": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"extern shallow", pkg.CExternShallow, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ a := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("extern"),
+ }
+ wantIdent := c.Ident(a)
+ wantOutput := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ "result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")},
+ }
+ var wantChecksum pkg.Checksum
+ if err := pkg.SumFS(
+ &wantChecksum,
+ fstest.MapFS(wantOutput),
+ ".",
+ ); err != nil {
+ t.Fatal(err)
+ }
+ wantChecksumH := unique.Make(wantChecksum)
+
+ _a := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("extern substitute"),
+ deps: []pkg.Artifact{pkg.NewFile("", nil)},
+ }
+ _wantIdent := c.Ident(_a)
+ _wantOutput := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ }
+ var _wantChecksum pkg.Checksum
+ if err := pkg.SumFS(
+ &_wantChecksum,
+ fstest.MapFS(_wantOutput),
+ ".",
+ ); err != nil {
+ t.Fatal(err)
+ }
+ _wantChecksumH := unique.Make(_wantChecksum)
+
+ kca := pkg.NewExec(
+ "", "",
+ new(pkg.Checksum), 0, false, false,
+ fhs.AbsRoot, nil, fhs.AbsRoot, nil,
+ )
+ kcIdent := c.Ident(kca)
+
+ fia := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("flood input"),
+ deps: []pkg.Artifact{_a},
+ }
+ fiaIdent := c.Ident(fia)
+
+ flia := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("flood input local"),
+ deps: []pkg.Artifact{_a},
+ }
+ fliaIdent := c.Ident(flia)
+
+ h := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("hanging input"),
+ }
+
+ fhia := &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("flood input local"),
+ deps: []pkg.Artifact{h},
+ }
+ fhiaIdent := c.Ident(fhia)
+
+ c.SetExternal(stubExtern{
+ artifact: map[unique.Handle[pkg.ID]]pkg.Checksum{
+ wantIdent: wantChecksum,
+ _wantIdent: _wantChecksum,
+ kcIdent: wantChecksum,
+ fiaIdent: wantChecksum,
+ fliaIdent: wantChecksum,
+ fhiaIdent: wantChecksum,
+ },
+ checksum: map[unique.Handle[pkg.Checksum]]fstest.MapFS{
+ wantChecksumH: fstest.MapFS(wantOutput),
+ _wantChecksumH: fstest.MapFS(_wantOutput),
+ },
+ status: map[unique.Handle[pkg.ID]]string{
+ wantIdent: "\x00",
+ kcIdent: "unreachable",
+ },
+ })
+
+ cureMany(t, c, []cureStep{
+ {"extern", a, base.Append(
+ "identifier",
+ pkg.Encode(wantIdent.Value()),
+ ), wantOutput, pkg.WExternal, nil},
+
+ {"flood shallow extern", fia, base.Append(
+ "identifier",
+ pkg.Encode(fiaIdent.Value()),
+ ), wantOutput, pkg.WExternal, nil},
+
+ {"substitute", _a, base.Append(
+ "identifier",
+ pkg.Encode(_wantIdent.Value()),
+ ), _wantOutput, pkg.WExternal, nil},
+
+ {"flood shallow local", flia, base.Append(
+ "identifier",
+ pkg.Encode(fliaIdent.Value()),
+ ), wantOutput, pkg.WExternal, nil},
+
+ {"flood shallow hanging", fhia, nil, nil,
+ pkg.WNew, pkg.HangingInputError(c.Ident(h))},
+
+ {"mismatch", kca, nil, nil, pkg.WExternal, &pkg.ChecksumMismatchError{
+ Got: wantChecksum,
+ }},
+ })
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl": {Mode: fs.ModeDir | 0500},
+ "checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl/result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/3fYJjSiWhklt7TK9MPVLGk5_8AIU8ostTcFPXULJf_qeAVG6pmC-Cqqu4ZhEXRxn": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+ "identifier/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+ "identifier/8sInO_dhPW9kweit7jVuH_vtMkRxlZT7XE4_yEKPZmpGkgVxkmxz0OpXgbMsH6ET": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/Xys8hPpJsKirZmTTuT7SwQ6661TCp_ZRu8iUjPPD7pC5E3ll7xw4zhNlZ2PVcz_-": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+ "identifier/k8yyhlBV3JpYzzywAnrRWkfp2TmgIDNC6FqMFwrrR2x0C3unBLveDNKgigOjG9MM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/mXlNz_v77nW7jstn_rJn4RL7dCQlkakM0y-HjO6tkDubtzaj-wCmW1hIr2Gk64_O": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+
+ "status": {Mode: fs.ModeDir | 0700},
+ "status/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: 0400, Data: []byte("\x00")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "substitute/socGRnhdsJlJ4Z01ZwHFZdLzFH0rWvoaJrylcUKtvhBdGsE7VoYl-vdUuvfK8B4I": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "substitute/MccYezQ4pshkeBV4w_aAEIwvZdKHL3OAgAkR3ouZ43EF94Ur1HBHaN7njPfJAj50": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+ "substitute/VV5W0_DiA1iylaqbKFThp1ZpV_y1ccVS1XyqDnDaXoHQ-EEyPI4XFrQHcHOF2hWG": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+ }
+ checkWithCache(t, testCases)
+}
+
+func TestErrors(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ err error
+ want string
+ }{
+ {"InvalidLookupError", pkg.InvalidLookupError{
+ 0xff, 0xf0,
+ }, "attempting to look up non-input artifact __AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},
+
+ {"InvalidArtifactError", pkg.InvalidArtifactError{
+ 0xff, 0xfd,
+ }, "artifact __0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA cannot be cured"},
+
+ {"ChecksumMismatchError", &pkg.ChecksumMismatchError{
+ Want: (pkg.Checksum)(bytes.Repeat([]byte{
+ 0x75, 0xe6, 0x9d, 0x6d, 0xe7, 0x9f,
+ }, 8)),
+ }, "got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ " instead of deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"},
+
+ {"ResponseStatusError", pkg.ResponseStatusError(
+ http.StatusNotAcceptable,
+ ), "the requested URL returned non-OK status: Not Acceptable"},
+
+ {"DisallowedTypeflagError", pkg.DisallowedTypeflagError(
+ tar.TypeChar,
+ ), "disallowed typeflag '3'"},
+
+ {"InvalidFileModeError", pkg.InvalidFileModeError(
+ fs.ModeSymlink | 0777,
+ ), "artifact did not produce a regular file or directory"},
+
+ {"NoOutputError", pkg.NoOutputError{
+ // empty struct
+ }, "artifact cured successfully but did not produce any output"},
+
+ {"IRKindError", &pkg.IRKindError{
+ Got: pkg.IRKindEnd,
+ Want: pkg.IRKindIdent,
+ Ancillary: 0xcafebabe,
+ }, "got terminator IR value (0xcafebabe) instead of ident"},
+ {"IRKindError invalid", &pkg.IRKindError{
+ Got: 0xbeef,
+ Want: pkg.IRKindIdent,
+ Ancillary: 0xcafe,
+ }, "got invalid kind 48879 IR value (0xcafe) instead of ident"},
+
+ {"UnsupportedVariantError", pkg.UnsupportedVariantError(
+ "rosa",
+ ), `unsupported variant "rosa"`},
+
+ {"UnsupportedArchError zero", pkg.UnsupportedArchError(""),
+ "invalid architecture name"},
+ {"UnsupportedArchError", pkg.UnsupportedArchError("riscv64"),
+ "unsupported architecture riscv64"},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.err.Error(); got != tc.want {
+ t.Errorf("Error: %q, want %q", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestScrubError(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ err pkg.ScrubError
+ want string
+ unwrap []error
+ }{
+ {"sample", *newWantScrubError(
+ fhs.AbsVarLib.Append("cure"),
+ ), `checksum mismatches:
+got vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX instead of _wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+
+dangling identifiers:
+_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+_v4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+
+errors during scrub:
+ /var/lib/cure/checksum/__8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:
+ artifact did not produce a regular file or directory
+ /var/lib/cure/checksum/invalid:
+ illegal base64 data at input byte 4
+ /var/lib/cure/identifier/_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:
+ readlink /var/lib/cure/identifier/_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: invalid argument
+ /var/lib/cure/identifier/invalid:
+ illegal base64 data at input byte 4
+ /var/lib/cure/nonexistent:
+ illegal base64 data at input byte 8
+`, []error{
+ &pkg.ChecksumMismatchError{Got: pkg.MustDecode(
+ "vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX",
+ ), Want: pkg.Checksum{0xff, 0}},
+
+ pkg.InvalidFileModeError(fs.ModeSymlink),
+ base64.CorruptInputError(4),
+
+ &os.PathError{
+ Op: "readlink",
+ Path: fhs.AbsVarLib.Append("cure").Append(
+ "identifier",
+ pkg.Encode(pkg.ID{0xfe, 0xff}),
+ ).String(),
+ Err: syscall.EINVAL,
+ },
+
+ base64.CorruptInputError(4),
+ base64.CorruptInputError(8),
+ }},
+
+ {"full", pkg.ScrubError{
+ ChecksumMismatches: []pkg.ChecksumMismatchError{
+ {Want: pkg.MustDecode("CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
+ },
+ DanglingIdentifiers: []pkg.ID{
+ (pkg.ID)(bytes.Repeat([]byte{0x75, 0xe6, 0x9d, 0x6d, 0xe7, 0x9f}, 8)),
+ (pkg.ID)(bytes.Repeat([]byte{0x71, 0xa7, 0xde, 0x6d, 0xa6, 0xde}, 8)),
+ },
+ Errs: map[unique.Handle[string]][]error{
+ unique.Make("/proc/nonexistent"): {
+ stub.UniqueError(0xcafe),
+ stub.UniqueError(0xbad),
+ stub.UniqueError(0xff),
+ },
+ },
+ }, `checksum mismatches:
+got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA instead of CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN
+
+dangling identifiers:
+deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe
+
+errors during scrub:
+ /proc/nonexistent:
+ unique error 51966 injected by the test suite
+ unique error 2989 injected by the test suite
+ unique error 255 injected by the test suite
+`, []error{
+ &pkg.ChecksumMismatchError{Want: pkg.MustDecode("CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
+ stub.UniqueError(0xcafe),
+ stub.UniqueError(0xbad),
+ stub.UniqueError(0xff),
+ }},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.err.Error(); got != tc.want {
+ t.Errorf("Error:\n\n%s\n\nwant\n\n%s", got, tc.want)
+ }
+
+ if unwrap := tc.err.Unwrap(); !reflect.DeepEqual(unwrap, tc.unwrap) {
+ t.Errorf("Unwrap: %#v, want %#v", unwrap, tc.unwrap)
+ }
+ })
+ }
+}
+
+func TestInputError(t *testing.T) {
+ t.Parallel()
+
+ makeIdent := func(ident ...byte) pkg.Artifact {
+ var a overrideIdent
+ copy(a.id[:], ident)
+ // does not compare equal
+ a.TrivialArtifact = new(stubArtifact)
+ return a
+ }
+
+ testCases := []struct {
+ name string
+ err pkg.InputError
+ want string
+ unwrap []error
+ }{
+ {"simple", pkg.InputError{
+ makeIdent(0xff, 9): stub.UniqueError(0xbad09),
+ makeIdent(0xff, 0): stub.UniqueError(0xbad00),
+ makeIdent(0xff, 0xf): stub.UniqueError(0xbad0f),
+ makeIdent(0xff, 1): stub.UniqueError(0xbad01),
+ }, `errors curing inputs:
+ _wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765184 injected by the test suite
+ _wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765185 injected by the test suite
+ _wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765193 injected by the test suite
+ _w8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765199 injected by the test suite`, []error{
+ stub.UniqueError(0xbad00),
+ stub.UniqueError(0xbad01),
+ stub.UniqueError(0xbad09),
+ stub.UniqueError(0xbad0f),
+ }},
+
+ {"dedup", pkg.InputError{
+ makeIdent(0xff, 9): stub.UniqueError(0xbad09),
+ makeIdent(0xff, 9): stub.UniqueError(0xbad09),
+ makeIdent(0xff, 9): stub.UniqueError(0xbad09),
+ makeIdent(0xff, 0): stub.UniqueError(0xbad00),
+ makeIdent(0xff, 0): stub.UniqueError(0xbad00),
+ makeIdent(0xff, 1): stub.UniqueError(0xbad01),
+ }, `errors curing inputs:
+ _wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765184 injected by the test suite
+ _wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765185 injected by the test suite
+ _wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: unique error 765193 injected by the test suite`, []error{
+ stub.UniqueError(0xbad00),
+ stub.UniqueError(0xbad01),
+ stub.UniqueError(0xbad09),
+ }},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.err.Error(); got != tc.want {
+ t.Errorf("Error:\n%s\nwant\n%s", got, tc.want)
+ }
+
+ if unwrap := tc.err.Unwrap(); !reflect.DeepEqual(unwrap, tc.unwrap) {
+ t.Errorf("Unwrap: %#v, want %#v", unwrap, tc.unwrap)
+ }
+ })
+ }
+}
+
+// earlyFailureF is a [FloodArtifact] with a large dependency graph resulting in
+// a large [DependencyCureError].
+type earlyFailureF int
+
+func (earlyFailureF) Kind() pkg.Kind { return pkg.KindExec }
+func (earlyFailureF) Params(*pkg.IContext) {}
+func (earlyFailureF) IsExclusive() bool { return false }
+
+func (a earlyFailureF) Inputs() []pkg.Artifact {
+ deps := make([]pkg.Artifact, a)
+ for i := range deps {
+ deps[i] = a - 1
+ }
+ return deps
+}
+
+func (a earlyFailureF) Cure(*pkg.FContext) error {
+ if a != 0 {
+ panic("unexpected cure on " + strconv.Itoa(int(a)))
+ }
+ return stub.UniqueError(0xcafe)
+}
+
+func BenchmarkEarlyDCE(b *testing.B) {
+ msg := message.New(log.New(os.Stderr, "dce: ", 0))
+ msg.SwapVerbose(testing.Verbose())
+
+ for b.Loop() {
+ b.StopTimer()
+ c, err := pkg.Open(b.Context(), msg, check.MustAbs(b.TempDir()), nil)
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.StartTimer()
+ _, _, err = c.Cure(earlyFailureF(8))
+ b.StopTimer()
+ if !errors.Is(err, stub.UniqueError(0xcafe)) {
+ b.Fatalf("Cure: error = %v", err)
+ }
+ c.Close()
+ b.StartTimer()
+ }
+}
+
+func TestDependencyCureErrorEarly(t *testing.T) {
+ t.Parallel()
+
+ checkWithCache(t, []cacheTestCase{
+ {"early", 0, nil, func(t *testing.T, _ *check.Absolute, c *pkg.Cache) {
+ _, _, err := c.Cure(earlyFailureF(8))
+ if !errors.Is(err, stub.UniqueError(0xcafe)) {
+ t.Fatalf("Cure: error = %v", err)
+ }
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+ })
+}
+
+func TestOpen(t *testing.T) {
+ t.Parallel()
+
+ t.Run("nonexistent", func(t *testing.T) {
+ t.Parallel()
+
+ wantErr := &os.PathError{
+ Op: "mkdir",
+ Path: container.Nonexistent,
+ Err: syscall.ENOENT,
+ }
+ if _, err := pkg.Open(
+ t.Context(),
+ message.New(nil),
+ check.MustAbs(container.Nonexistent),
+ nil,
+ ); !reflect.DeepEqual(err, wantErr) {
+ t.Errorf("Open: error = %#v, want %#v", err, wantErr)
+ }
+ })
+
+ t.Run("permission", func(t *testing.T) {
+ t.Parallel()
+
+ tempDir := check.MustAbs(t.TempDir())
+ if err := os.Chmod(tempDir.String(), 0); err != nil {
+ t.Fatal(err)
+ } else {
+ t.Cleanup(func() {
+ if err = os.Chmod(tempDir.String(), 0700); err != nil {
+ t.Fatal(err)
+ }
+ })
+ }
+
+ wantErr := &os.PathError{
+ Op: "mkdir",
+ Path: tempDir.Append("cache").String(),
+ Err: syscall.EACCES,
+ }
+ if _, err := pkg.Open(
+ t.Context(),
+ message.New(nil),
+ tempDir.Append("cache"),
+ nil,
+ ); !reflect.DeepEqual(err, wantErr) {
+ t.Errorf("Open: error = %#v, want %#v", err, wantErr)
+ }
+ })
+
+ t.Run("dirty", func(t *testing.T) {
+ t.Parallel()
+
+ tempDir := check.MustAbs(t.TempDir())
+ if err := os.MkdirAll(tempDir.Append(
+ "cache",
+ "work",
+ "dirty",
+ ).String(), 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ wantErr := errors.New("work is not empty, scrub likely required")
+ if _, err := pkg.Open(
+ t.Context(),
+ message.New(nil),
+ tempDir.Append("cache"),
+ nil,
+ ); !reflect.DeepEqual(err, wantErr) {
+ t.Errorf("Open: error = %#v, want %#v", err, wantErr)
+ }
+ })
+
+ t.Run("scratch", func(t *testing.T) {
+ t.Parallel()
+
+ tempDir := check.MustAbs(t.TempDir())
+ if err := os.MkdirAll(tempDir.Append(
+ "cache",
+ "scratch",
+ ).String(), 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ wantErr := errors.New("scratch is present, scrub likely required")
+ if _, err := pkg.Open(
+ t.Context(),
+ message.New(nil),
+ tempDir.Append("cache"), nil,
+ ); !reflect.DeepEqual(err, wantErr) {
+ t.Errorf("Open: error = %#v, want %#v", err, wantErr)
+ }
+ })
+}
+
+func TestExtensionRegister(t *testing.T) {
+ extensionOld := extension
+ openedOld := opened
+ t.Cleanup(func() { extension = extensionOld; opened = openedOld })
+ extension = ""
+ opened = false
+
+ t.Run("set", func(t *testing.T) {
+ t.Cleanup(func() { extension = "" })
+
+ const want = "rosa"
+ pkg.SetExtension(want)
+ if got := pkg.Extension(); got != want {
+ t.Fatalf("Extension: %q, want %q", got, want)
+ }
+ })
+
+ t.Run("twice", func(t *testing.T) {
+ t.Cleanup(func() { extension = "" })
+
+ defer func() {
+ const wantPanic = "attempting to set extension twice"
+ if r := recover(); r != wantPanic {
+ t.Errorf("panic: %#v, want %q", r, wantPanic)
+ }
+ }()
+ pkg.SetExtension("rosa")
+ pkg.SetExtension("rosa")
+ })
+
+ t.Run("invalid", func(t *testing.T) {
+ defer func() {
+ var wantPanic = pkg.ErrInvalidExtension
+ if r := recover(); r != wantPanic {
+ t.Errorf("panic: %#v, want %#v", r, wantPanic)
+ }
+ }()
+ pkg.SetExtension(" ")
+ })
+
+ t.Run("opened", func(t *testing.T) {
+ t.Cleanup(func() { opened = false })
+
+ if _, err := pkg.Open(
+ t.Context(),
+ message.New(log.Default()),
+ check.MustAbs(container.Nonexistent),
+ nil,
+ ); !errors.Is(err, os.ErrNotExist) {
+ t.Fatalf("Open: error = %v", err)
+ }
+
+ t.Run("variant", func(t *testing.T) {
+ defer func() {
+ const wantPanic = "attempting to set extension after open"
+ if r := recover(); r != wantPanic {
+ t.Errorf("panic: %#v, want %q", r, wantPanic)
+ }
+ }()
+ pkg.SetExtension("rosa")
+ })
+
+ t.Run("register", func(t *testing.T) {
+ defer func() {
+ const wantPanic = "attempting to register after open"
+ if r := recover(); r != wantPanic {
+ t.Errorf("panic: %#v, want %q", r, wantPanic)
+ }
+ }()
+ pkg.Register(pkg.KindCustomOffset, nil)
+ })
+ })
+
+ t.Run("incomplete", func(t *testing.T) {
+ t.Cleanup(func() { delete(irArtifact, pkg.KindCustomOffset) })
+
+ defer func() {
+ const wantPanic = "attempting to open cache with incomplete variant setup"
+ if r := recover(); r != wantPanic {
+ t.Errorf("panic: %#v, want %q", r, wantPanic)
+ }
+ }()
+ pkg.Register(pkg.KindCustomOffset, nil)
+
+ t.Cleanup(func() { opened = false })
+ _, _ = pkg.Open(nil, nil, nil, nil)
+ panic("unreachable")
+ })
+
+ t.Run("create", func(t *testing.T) {
+ t.Cleanup(func() { extension = "" })
+ const want = "rosa"
+ pkg.SetExtension(want)
+
+ base := check.MustAbs(t.TempDir())
+ t.Cleanup(func() { opened = false })
+ if c, err := pkg.Open(t.Context(), nil, base, nil); err != nil {
+ t.Fatal(err)
+ } else {
+ c.Close()
+ }
+
+ if got, err := os.ReadFile(base.Append("variant").String()); err != nil {
+ t.Fatal(err)
+ } else if string(got) != want {
+ t.Fatalf("variant: %q", string(got))
+ }
+ })
+
+ t.Run("access", func(t *testing.T) {
+ base := check.MustAbs(t.TempDir())
+ t.Cleanup(func() { opened = false })
+
+ if err := os.WriteFile(base.Append("variant").String(), nil, 0); err != nil {
+ t.Fatal(err)
+ }
+
+ wantErr := &os.PathError{
+ Op: "open",
+ Path: base.Append("variant").String(),
+ Err: syscall.EACCES,
+ }
+ if _, err := pkg.Open(
+ t.Context(), nil,
+ base, nil,
+ ); !reflect.DeepEqual(err, wantErr) {
+ t.Fatalf("Open: error = %v, want %v", err, wantErr)
+ }
+ })
+
+ t.Run("promote", func(t *testing.T) {
+ t.Cleanup(func() { extension = "" })
+ const want = "rosa"
+ pkg.SetExtension(want)
+
+ base := check.MustAbs(t.TempDir())
+ t.Cleanup(func() { opened = false })
+
+ variantPath := base.Append("variant")
+ if err := os.WriteFile(variantPath.String(), nil, 0600); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := pkg.Open(
+ t.Context(), nil,
+ base,
+ nil,
+ ); !reflect.DeepEqual(err, pkg.ErrWouldPromote) {
+ t.Fatalf("Open: error = %v", err)
+ }
+
+ if p, err := os.ReadFile(variantPath.String()); err != nil {
+ t.Fatal(err)
+ } else if len(p) != 0 {
+ t.Fatalf("variant: %q", string(p))
+ }
+
+ if c, err := pkg.Open(
+ t.Context(), nil,
+ base, &pkg.CacheAttr{Flags: pkg.CPromoteVariant},
+ ); err != nil {
+ t.Fatalf("Open: error = %v", err)
+ } else {
+ c.Close()
+ }
+
+ if p, err := os.ReadFile(variantPath.String()); err != nil {
+ t.Fatal(err)
+ } else if string(p) != want {
+ t.Fatalf("variant: %q, want %q", string(p), want)
+ }
+ })
+
+ t.Run("open invalid", func(t *testing.T) {
+ base := check.MustAbs(t.TempDir())
+ t.Cleanup(func() { opened = false })
+
+ variantPath := base.Append("variant")
+ if err := os.WriteFile(variantPath.String(), make([]byte, 129), 0400); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := pkg.Open(
+ t.Context(), nil,
+ base, nil,
+ ); !reflect.DeepEqual(err, pkg.ErrInvalidExtension) {
+ t.Fatalf("Open: error = %v", err)
+ }
+ })
+
+ t.Run("unsupported", func(t *testing.T) {
+ base := check.MustAbs(t.TempDir())
+ t.Cleanup(func() { opened = false })
+
+ variantPath := base.Append("variant")
+ if err := os.WriteFile(variantPath.String(), []byte("rosa"), 0400); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := pkg.Open(
+ t.Context(), nil,
+ base, nil,
+ ); !reflect.DeepEqual(err, pkg.UnsupportedVariantError("rosa")) {
+ t.Fatalf("Open: error = %v", err)
+ }
+ })
+}