67dbb68818
Test / Create distribution (push) Successful in 56s
Test / Sandbox (push) Successful in 2m43s
Test / ShareFS (push) Successful in 3m57s
Test / Hakurei (push) Successful in 4m3s
Test / Sandbox (race detector) (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 6m36s
Test / Flake checks (push) Successful in 1m5s
This enables changing the behaviour of an artifact implementation without changing its IR structure. Unfortunately this changes the IR header structure resulting in many rebuilds. Signed-off-by: Ophestra <cat@gensokyo.uk>
2393 lines
71 KiB
Go
2393 lines
71 KiB
Go
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/pkg"
|
|
"hakurei.app/internal/stub"
|
|
"hakurei.app/message"
|
|
)
|
|
|
|
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/internal/pkg.extension
|
|
extension string
|
|
|
|
// opened is false if [Open] was never called.
|
|
//
|
|
//go:linkname opened hakurei.app/internal/pkg.opened
|
|
opened bool
|
|
|
|
// irArtifact refers to artifact IR interpretation functions and must not be
|
|
// written to directly.
|
|
//
|
|
//go:linkname irArtifact hakurei.app/internal/pkg.irArtifact
|
|
irArtifact map[pkg.Kind]pkg.IRReadFunc
|
|
|
|
// statusHeader is the header written to all status files in dirStatus.
|
|
//
|
|
//go:linkname statusHeader hakurei.app/internal/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
|
|
}
|
|
|
|
func TestMain(m *testing.M) { container.TryArgv0(nil); os.Exit(m.Run()) }
|
|
|
|
// 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 (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.DependencyCureError{
|
|
{
|
|
A: failingFile,
|
|
Err: 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/2vZYAE7AVme2W3EgcDP1QUfwrmgFWIswUQBd6ZIBtiCal-MLiCtAg940Jzed5w4_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
|
"identifier/42lkARs2-xl_7wXfjPB0DaOXH8U2BKimaDYX-POJv5l01ArTKMbABt_kUMp24F1U": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
|
|
"identifier/H5pfX9loHFLORmMrMFCDbVUGg0RtxUDj4GHs21Q6bD8PTiAaFVGLnTQlhbMC5Z0G": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
|
"identifier/sZVN1nOxhdWvZnus3nPTe6q0SqqzObfNBsJNnVu6Co6ajKA9wCiUP33MOdaaEt7K": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
|
|
|
|
"status": {Mode: fs.ModeDir | 0700},
|
|
"status/H5pfX9loHFLORmMrMFCDbVUGg0RtxUDj4GHs21Q6bD8PTiAaFVGLnTQlhbMC5Z0G": {Mode: fs.ModeSymlink | 0777, Data: []byte("dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x")},
|
|
"status/2vZYAE7AVme2W3EgcDP1QUfwrmgFWIswUQBd6ZIBtiCal-MLiCtAg940Jzed5w4_": {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/PITQy8S3pqQRTJOE9lPJVjUMri6jSuknElXX7SaUVtUTQqByg79dI-kpc6J2GNeY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
"identifier/TI4SrHTEYRZ9yeiJzPqPULx4pl1hVSFgZ7DTweQSwk49k8lAIC1A5UrPs45P3Hi6": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
|
|
"identifier/xCv2q_KJpNoTkXI9ua5UMAC1ndSrCeAhlQ9njUcwXCSd_qAnSXkgOy7uluHWWD3o": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
"identifier/DyLrgassC_E06vPgPzQFuCOxwoccc08C04FSuI6aliWWXdJLWnJt9R9OXwoakhhm": {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/xCv2q_KJpNoTkXI9ua5UMAC1ndSrCeAhlQ9njUcwXCSd_qAnSXkgOy7uluHWWD3o": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
"identifier/uaLFpraDJ8JOCFT94FhrVCNn9R2iXozSw3W4Rt_zdAKhlViF36CGjjwL6IJoe4fU": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
|
|
"identifier/aCVm-QVKHmWPyHu02CjnJ5xXlIr78nNQRv1x9HhFRamQkaYesdubZFy-m7JxI38v": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
|
|
"identifier/PITQy8S3pqQRTJOE9lPJVjUMri6jSuknElXX7SaUVtUTQqByg79dI-kpc6J2GNeY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
"identifier/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
|
|
"identifier/TI4SrHTEYRZ9yeiJzPqPULx4pl1hVSFgZ7DTweQSwk49k8lAIC1A5UrPs45P3Hi6": {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 TestDependencyCureError(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.DependencyCureError
|
|
want string
|
|
unwrap []error
|
|
}{
|
|
{"simple", pkg.DependencyCureError{
|
|
{A: makeIdent(0xff, 9), Err: stub.UniqueError(0xbad09)},
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
{A: makeIdent(0xff, 0xf), Err: stub.UniqueError(0xbad0f)},
|
|
{A: makeIdent(0xff, 1), Err: stub.UniqueError(0xbad01)},
|
|
}, `errors curing dependencies:
|
|
_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{
|
|
&pkg.CureError{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
&pkg.CureError{A: makeIdent(0xff, 1), Err: stub.UniqueError(0xbad01)},
|
|
&pkg.CureError{A: makeIdent(0xff, 9), Err: stub.UniqueError(0xbad09)},
|
|
&pkg.CureError{A: makeIdent(0xff, 0xf), Err: stub.UniqueError(0xbad0f)},
|
|
}},
|
|
|
|
{"dedup", pkg.DependencyCureError{
|
|
{A: makeIdent(0xff, 9), Err: stub.UniqueError(0xbad09)},
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
{A: makeIdent(0xff, 0xfd), Err: &pkg.DependencyCureError{
|
|
{A: makeIdent(0xff, 9), Err: stub.UniqueError(0xbad09)},
|
|
{A: makeIdent(0xff, 0xc), Err: &pkg.DependencyCureError{
|
|
{A: makeIdent(0xff, 0xf), Err: stub.UniqueError(0xbad0f)},
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
}},
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
}},
|
|
{A: makeIdent(0xff, 0xff), Err: &pkg.DependencyCureError{
|
|
{A: makeIdent(0xff, 9), Err: stub.UniqueError(0xbad09)},
|
|
{A: makeIdent(0xff, 0xc), Err: &pkg.DependencyCureError{
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
}},
|
|
{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
}},
|
|
{A: makeIdent(0xff, 0xf), Err: stub.UniqueError(0xbad0f)},
|
|
{A: makeIdent(0xff, 1), Err: stub.UniqueError(0xbad01)},
|
|
}, `errors curing dependencies:
|
|
_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{
|
|
&pkg.CureError{A: makeIdent(0xff, 0), Err: stub.UniqueError(0xbad00)},
|
|
&pkg.CureError{A: makeIdent(0xff, 1), Err: stub.UniqueError(0xbad01)},
|
|
&pkg.CureError{A: makeIdent(0xff, 9), Err: stub.UniqueError(0xbad09)},
|
|
&pkg.CureError{A: makeIdent(0xff, 0xf), Err: stub.UniqueError(0xbad0f)},
|
|
}},
|
|
}
|
|
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())
|
|
c, err := pkg.Open(b.Context(), msg, check.MustAbs(b.TempDir()), nil)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
_, _, err = c.Cure(earlyFailureF(8))
|
|
if !errors.Is(err, stub.UniqueError(0xcafe)) {
|
|
b.Fatalf("Cure: error = %v", err)
|
|
}
|
|
c.Close()
|
|
dce := err.(*pkg.DependencyCureError)
|
|
|
|
for b.Loop() {
|
|
dce.Unwrap()
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|