diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-01-04 21:57:57 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-01-05 00:01:23 +0900 |
| commit | 4897b0259edeb256a3d5cf77148758536e366e64 (patch) | |
| tree | 23c9e0ff7bbc0f49d233381e6c17b4cc357a939c /internal/pkg/pkg_test.go | |
| parent | d6e4f85864a1e9b2aadc2e7f47e481242d5e946a (diff) | |
internal/pkg: improve artifact interface
This moves all cache I/O code to Cache. Artifact now only contains methods for constructing their actual contents.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/pkg_test.go')
| -rw-r--r-- | internal/pkg/pkg_test.go | 616 |
1 files changed, 380 insertions, 236 deletions
diff --git a/internal/pkg/pkg_test.go b/internal/pkg/pkg_test.go index 915c7203..1479e2e6 100644 --- a/internal/pkg/pkg_test.go +++ b/internal/pkg/pkg_test.go @@ -5,6 +5,8 @@ import ( "bytes" "crypto/sha512" "encoding/binary" + "errors" + "fmt" "io/fs" "net/http" "os" @@ -12,6 +14,7 @@ import ( "reflect" "syscall" "testing" + "unsafe" "hakurei.app/container" "hakurei.app/container/check" @@ -19,41 +22,131 @@ import ( "hakurei.app/internal/pkg" ) -// A stubArtifact implements [Artifact] with hardcoded kind and identifier. +// overrideIdent overrides the ID method of [Artifact]. +type overrideIdent struct { + id pkg.ID + pkg.Artifact +} + +func (a overrideIdent) ID() pkg.ID { return a.id } + +// overrideIdentFile overrides the ID method of [File]. +type overrideIdentFile struct { + id pkg.ID + pkg.File +} + +func (a overrideIdentFile) ID() pkg.ID { return a.id } + +// A knownIdentArtifact implements [pkg.KnownIdent] and [Artifact] +type knownIdentArtifact interface { + pkg.KnownIdent + pkg.Artifact +} + +// A knownIdentFile implements [pkg.KnownIdent] and [File] +type knownIdentFile interface { + pkg.KnownIdent + pkg.File +} + +// 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 [File]. +type overrideChecksumFile struct { + checksum pkg.Checksum + knownIdentFile +} + +func (a overrideChecksumFile) Checksum() pkg.Checksum { return a.checksum } + +// A stubArtifact implements [Artifact] with hardcoded behaviour. type stubArtifact struct { - kind pkg.Kind - id pkg.ID + kind pkg.Kind + params []byte + deps []pkg.Artifact + + cure func(work *check.Absolute, loadData pkg.CacheDataFunc) error +} + +func (a stubArtifact) Kind() pkg.Kind { return a.kind } +func (a stubArtifact) Params() []byte { return a.params } +func (a stubArtifact) Dependencies() []pkg.Artifact { return a.deps } + +func (a stubArtifact) Cure( + work *check.Absolute, + loadData pkg.CacheDataFunc, +) error { + return a.cure(work, loadData) } -func (a stubArtifact) Kind() pkg.Kind { return a.kind } -func (a stubArtifact) ID() pkg.ID { return a.id } -func (a stubArtifact) Hash() (pkg.Checksum, error) { panic("unreachable") } -func (a stubArtifact) Pathname() (*check.Absolute, error) { panic("unreachable") } +// A stubFile implements [File] with hardcoded behaviour. +type stubFile struct { + data []byte + err error + + stubArtifact +} + +func (a stubFile) Data() ([]byte, error) { return 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.File { + f := overrideIdentFile{id, stubFile{data, err, stubArtifact{ + kind, + nil, + nil, + func(work *check.Absolute, loadData pkg.CacheDataFunc) error { + panic("unreachable") + }, + }}} + if sum == nil { + return f + } else { + return overrideChecksumFile{*sum, f} + } +} func TestIdent(t *testing.T) { t.Parallel() testCases := []struct { - name string - kind pkg.Kind - params []byte - deps []pkg.Artifact - want pkg.ID + name string + a pkg.Artifact + want pkg.ID }{ - {"tar", pkg.KindTar, []byte{ - pkg.TarGzip, 0, 0, 0, 0, 0, 0, 0, - }, []pkg.Artifact{ - stubArtifact{pkg.KindHTTP, pkg.ID{}}, - }, pkg.MustDecode("HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY")}, + {"tar", stubArtifact{ + pkg.KindTar, + []byte{pkg.TarGzip, 0, 0, 0, 0, 0, 0, 0}, + []pkg.Artifact{ + overrideIdent{pkg.ID{}, stubArtifact{}}, + }, + nil, + }, pkg.MustDecode( + "HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", + )}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() - if got := tc.kind.Ident(tc.params, tc.deps...); got != tc.want { + if got := pkg.Ident(tc.a); got != tc.want { t.Errorf("Ident: %s, want %s", pkg.Encode(got), - pkg.Encode(tc.want)) + pkg.Encode(tc.want), + ) } }) } @@ -118,6 +211,37 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) { } } +// A cureStep contains an [Artifact] to be cured, and the expected outcome. +type cureStep struct { + name string + + a pkg.Artifact + + pathname *check.Absolute + checksum pkg.Checksum + err error +} + +// cureMany cures many artifacts against a [Cache] and checks their outcomes. +func cureMany(t *testing.T, c *pkg.Cache, steps []cureStep) { + for _, step := range steps { + t.Log("cure step:", step.name) + if pathname, checksum, err := c.Cure(step.a); !reflect.DeepEqual(err, step.err) { + t.Fatalf("Cure: error = %v, want %v", err, step.err) + } else if !pathname.Is(step.pathname) { + t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname) + } else if checksum != step.checksum { + t.Fatalf("Cure: checksum = %s, want %s", pkg.Encode(checksum), pkg.Encode(step.checksum)) + } else { + v := any(err) + if err == nil { + v = pathname + } + t.Log(pkg.Encode(checksum)+":", v) + } + } +} + func TestCache(t *testing.T) { t.Parallel() @@ -134,21 +258,9 @@ func TestCache(t *testing.T) { return (pkg.Checksum)(h.Sum(nil)) }() - testdataChecksumString := pkg.Encode(testdataChecksum) - testCases := []cacheTestCase{ {"file", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - wantErrNonexistent := &os.PathError{ - Op: "open", - Path: base.Append( - "identifier", - testdataChecksumString, - ).String(), - Err: syscall.ENOENT, - } - if _, _, err := c.LoadFile(testdataChecksum); !reflect.DeepEqual(err, wantErrNonexistent) { - t.Fatalf("LoadFile: error = %#v, want %#v", err, wantErrNonexistent) - } + c.SetStrict(true) identifier := (pkg.ID)(bytes.Repeat([]byte{ 0x75, 0xe6, 0x9d, 0x6d, 0xe7, 0x9f, @@ -165,154 +277,109 @@ func TestCache(t *testing.T) { "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe", ) - // initial store - if pathname, err := c.StoreFile( - identifier, - []byte(testdata), - &testdataChecksum, - true, - ); err != nil { - t.Fatalf("StoreFile: error = %v", err) - } else if !pathname.Is(wantPathname) { - t.Fatalf("StoreFile: pathname = %q, want %q", pathname, wantPathname) - } + cureMany(t, c, []cureStep{ + {"initial file", newStubFile( + pkg.KindHTTP, + identifier, + &testdataChecksum, + []byte(testdata), nil, + ), wantPathname, testdataChecksum, nil}, - // load or store, identical content - if pathname, data, store, err := c.LoadOrStoreFile(identifier0, func() ([]byte, error) { - return []byte(testdata), nil - }, &testdataChecksum, true); err != nil { - t.Fatalf("LoadOrStoreFile: error = %v", err) - } else if !pathname.Is(wantPathname0) { - t.Fatalf("LoadOrStoreFile: pathname = %q, want %q", pathname, wantPathname0) - } else if string(data) != testdata { - t.Fatalf("LoadOrStoreFile: data = %x, want %x", data, testdata) - } else if !store { - t.Fatal("LoadOrStoreFile did not store nonpresent entry") - } + {"identical content", newStubFile( + pkg.KindHTTP, + identifier0, + &testdataChecksum, + []byte(testdata), nil, + ), wantPathname0, testdataChecksum, nil}, - // load or store, existing entry - if pathname, data, store, err := c.LoadOrStoreFile(identifier, func() ([]byte, error) { - return []byte(testdata), nil - }, &testdataChecksum, true); err != nil { - t.Fatalf("LoadOrStoreFile: error = %v", err) - } else if !pathname.Is(wantPathname) { - t.Fatalf("LoadOrStoreFile: pathname = %q, want %q", pathname, wantPathname) - } else if string(data) != testdata { - t.Fatalf("LoadOrStoreFile: data = %x, want %x", data, testdata) - } else if store { - t.Fatal("LoadOrStoreFile stored over present entry") - } + {"existing entry", newStubFile( + pkg.KindHTTP, + identifier, + &testdataChecksum, + []byte(testdata), nil, + ), wantPathname, testdataChecksum, nil}, - // load, existing entry - if pathname, data, err := c.LoadFile(identifier0); err != nil { - t.Fatalf("LoadFile: error = %v", err) - } else if !pathname.Is(wantPathname0) { - t.Fatalf("LoadFile: pathname = %q, want %q", pathname, wantPathname0) - } else if string(data) != testdata { - t.Fatalf("LoadFile: data = %x, want %x", data, testdata) - } + {"checksum mismatch", newStubFile( + pkg.KindHTTP, + pkg.ID{0xff, 0}, + new(pkg.Checksum), + []byte(testdata), nil, + ), nil, pkg.Checksum{}, &pkg.ChecksumMismatchError{ + Got: testdataChecksum, + }}, - // checksum mismatch - wantErrChecksum := &pkg.ChecksumMismatchError{ - Got: testdataChecksum, - } - if _, err := c.StoreFile( - testdataChecksum, - []byte(testdata), - new(pkg.Checksum), - true, - ); !reflect.DeepEqual(err, wantErrChecksum) { - t.Fatalf("StoreFile: error = %#v, want %#v", err, wantErrChecksum) - } + {"store without validation", newStubFile( + pkg.KindHTTP, + pkg.MustDecode("vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX"), + nil, + []byte{0}, nil, + ), base.Append( + "identifier", + "vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", + ), pkg.Checksum{ + 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, + }, nil}, - // verify failed store - if _, _, err := c.LoadFile(testdataChecksum); !reflect.DeepEqual(err, wantErrNonexistent) { - t.Fatalf("LoadFile: error = %#v, want %#v", err, wantErrNonexistent) - } + {"error passthrough", newStubFile( + pkg.KindHTTP, + pkg.ID{0xff, 1}, + nil, + nil, stub.UniqueError(0xcafe), + ), nil, pkg.Checksum{}, stub.UniqueError(0xcafe)}, - // store, same identifier - wantPathnameF := base.Append( - "identifier", - testdataChecksumString, - ) - if pathname, err := c.StoreFile( - testdataChecksum, - []byte(testdata), - &testdataChecksum, - true, - ); err != nil { - t.Fatalf("StoreFile: error = %v", err) - } else if !pathname.Is(wantPathnameF) { - t.Fatalf("StoreFile: pathname = %q, want %q", pathname, wantPathnameF) - } - - // load, same identifier - if pathname, data, err := c.LoadFile(testdataChecksum); err != nil { - t.Fatalf("LoadFile: error = %v", err) - } else if !pathname.Is(wantPathnameF) { - t.Fatalf("LoadFile: pathname = %q, want %q", pathname, wantPathnameF) - } else if string(data) != testdata { - t.Fatalf("LoadFile: data = %x, want %x", data, testdata) - } + {"error caching", newStubFile( + pkg.KindHTTP, + pkg.ID{0xff, 1}, + nil, + nil, nil, + ), nil, pkg.Checksum{}, stub.UniqueError(0xcafe)}, - // store without validation - wantChecksum := pkg.Checksum{ - 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, - } - var gotChecksum pkg.Checksum - wantPathnameG := base.Append( - "identifier", - pkg.Encode(wantChecksum), - ) - if pathname, err := c.StoreFile( - wantChecksum, - []byte{0}, - &gotChecksum, - false, - ); err != nil { - t.Fatalf("StoreFile: error = %#v", err) - } else if !pathname.Is(wantPathnameG) { - t.Fatalf("StoreFile: pathname = %q, want %q", pathname, wantPathnameG) - } else if gotChecksum != wantChecksum { - t.Fatalf("StoreFile: buf = %x, want %x", gotChecksum, wantChecksum) - } + {"cache hit bad type", overrideChecksum{testdataChecksum, overrideIdent{pkg.ID{0xff, 2}, stubArtifact{ + kind: pkg.KindTar, + }}}, nil, pkg.Checksum{}, pkg.InvalidFileModeError( + 0400, + )}, + }) - // makeData passthrough - var zeroIdent pkg.ID - wantErrPassthrough := stub.UniqueError(0xcafe) - if _, _, _, err := c.LoadOrStoreFile(zeroIdent, func() ([]byte, error) { - return nil, wantErrPassthrough - }, new(pkg.Checksum), true); !reflect.DeepEqual(err, wantErrPassthrough) { - t.Fatalf("LoadOrStoreFile: error = %#v, want %#v", err, wantErrPassthrough) - } + if c0, err := pkg.New(base); err != nil { + t.Fatalf("New: error = %v", err) + } else { + cureMany(t, c0, []cureStep{ + {"cache hit ident", overrideIdent{ + id: identifier, + }, wantPathname, testdataChecksum, nil}, - // verify failed store - wantErrNonexistentZero := &os.PathError{ - Op: "open", - Path: base.Append( - "identifier", - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - ).String(), - Err: syscall.ENOENT, - } - if _, _, err := c.LoadFile(zeroIdent); !reflect.DeepEqual(err, wantErrNonexistentZero) { - t.Fatalf("LoadFile: error = %#v, want %#v", err, wantErrNonexistentZero) + {"cache miss checksum match", newStubFile( + pkg.KindHTTP, + testdataChecksum, + nil, + []byte(testdata), + nil, + ), base.Append( + "identifier", + pkg.Encode(testdataChecksum), + ), testdataChecksum, nil}, + }) } }, pkg.MustDecode("St9rlE-mGZ5gXwiv_hzQ_B8bZP-UUvSNmf4nHUZzCMOumb6hKnheZSe0dmnuc4Q2")}, {"directory", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { id := pkg.KindTar.Ident( binary.LittleEndian.AppendUint64(nil, pkg.TarGzip), - stubArtifact{pkg.KindHTTP, testdataChecksum}, + overrideIdent{testdataChecksum, stubArtifact{}}, ) - makeSample := func(work *check.Absolute) error { + makeSample := func(work *check.Absolute, _ pkg.CacheDataFunc) error { + if err := os.Mkdir(work.String(), 0700); err != nil { + return err + } + if err := os.WriteFile( work.Append("check").String(), []byte{0, 0}, @@ -344,59 +411,22 @@ func TestCache(t *testing.T) { pkg.Encode(id), ) - if pathname, store, err := c.Store( - id, - makeSample, - &wantChecksum, - true, - ); err != nil { - t.Fatalf("Store: error = %v", err) - } else if !store { - t.Fatal("Store did not store nonpresent entry") - } else if !pathname.Is(wantPathname) { - t.Fatalf("Store: pathname = %q, want %q", pathname, wantPathname) - } - - // check lookup - if pathname, store, err := c.Store( - id, - nil, - &wantChecksum, - true, - ); err != nil { - t.Fatalf("Store: error = %v", err) - } else if store { - t.Fatal("Store stored over present entry") - } else if !pathname.Is(wantPathname) { - t.Fatalf("Store: pathname = %q, want %q", pathname, wantPathname) - } - - // check exist id0 := pkg.KindTar.Ident( binary.LittleEndian.AppendUint64(nil, pkg.TarGzip), - stubArtifact{pkg.KindHTTP, pkg.ID{}}, + overrideIdent{pkg.ID{}, stubArtifact{}}, ) wantPathname0 := base.Append( "identifier", pkg.Encode(id0), ) - if pathname, store, err := c.Store( - id0, - makeSample, - &wantChecksum, - true, - ); err != nil { - t.Fatalf("Store: error = %v", err) - } else if !store { - t.Fatal("Store did not store nonpresent entry") - } else if !pathname.Is(wantPathname0) { - t.Fatalf("Store: pathname = %q, want %q", pathname, wantPathname0) - } - var wantErrMakeGarbage error - makeGarbage := func(work *check.Absolute) error { + makeGarbage := func(work *check.Absolute, wantErr error) error { + if err := os.Mkdir(work.String(), 0700); err != nil { + return err + } + mode := fs.FileMode(0) - if wantErrMakeGarbage == nil { + if wantErr == nil { mode = 0500 } @@ -426,38 +456,144 @@ func TestCache(t *testing.T) { return err } - return wantErrMakeGarbage + return wantErr } - // check makeArtifact fault - wantErrMakeGarbage = stub.UniqueError(0xcafe) - if _, store, err := c.Store( - pkg.ID{}, - makeGarbage, - nil, - false, - ); !reflect.DeepEqual(err, wantErrMakeGarbage) { - t.Fatalf("Store: error = %#v, want %#v", err, wantErrMakeGarbage) - } else if !store { - t.Fatal("Store did not store nonpresent entry") - } + cureMany(t, c, []cureStep{ + {"initial directory", overrideChecksum{wantChecksum, overrideIdent{id, stubArtifact{ + kind: pkg.KindTar, + cure: makeSample, + }}}, wantPathname, wantChecksum, nil}, - // checksum mismatch - wantErrMakeGarbage = nil - wantErrMismatch := &pkg.ChecksumMismatchError{ - Got: pkg.MustDecode("GbjlYMcHQANdfwL6qNGopBF99IscPTvCy95HSH1_kIF3eKjFDSLP0_iUUT0z8hiw"), - } - if _, store, err := c.Store( - pkg.ID{}, - makeGarbage, - new(pkg.Checksum), - true, - ); !reflect.DeepEqual(err, wantErrMismatch) { - t.Fatalf("Store: error = %v, want %v", err, wantErrMismatch) - } else if !store { - t.Fatal("Store did not store nonpresent entry") - } + {"identical identifier", overrideChecksum{wantChecksum, overrideIdent{id, stubArtifact{ + kind: pkg.KindTar, + }}}, wantPathname, wantChecksum, nil}, + + {"identical checksum", overrideIdent{id0, stubArtifact{ + kind: pkg.KindTar, + cure: makeSample, + }}, wantPathname0, wantChecksum, nil}, + + {"cure fault", overrideIdent{pkg.ID{0xff, 0}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, _ pkg.CacheDataFunc) error { + return makeGarbage(work, stub.UniqueError(0xcafe)) + }, + }}, nil, pkg.Checksum{}, stub.UniqueError(0xcafe)}, + + {"checksum mismatch", overrideChecksum{pkg.Checksum{}, overrideIdent{pkg.ID{0xff, 1}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, _ pkg.CacheDataFunc) error { + return makeGarbage(work, nil) + }, + }}}, nil, pkg.Checksum{}, &pkg.ChecksumMismatchError{ + Got: pkg.MustDecode( + "GbjlYMcHQANdfwL6qNGopBF99IscPTvCy95HSH1_kIF3eKjFDSLP0_iUUT0z8hiw", + ), + }}, + + {"cache hit bad type", newStubFile( + pkg.KindHTTP, + pkg.ID{0xff, 2}, + &wantChecksum, + []byte(testdata), nil, + ), nil, pkg.Checksum{}, pkg.InvalidFileModeError( + fs.ModeDir | 0500, + )}, + + {"loadData directory", overrideIdent{pkg.ID{0xff, 3}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, loadData pkg.CacheDataFunc) error { + _, err := loadData(overrideChecksumFile{checksum: wantChecksum}) + return err + }, + }}, nil, pkg.Checksum{}, &os.PathError{ + Op: "read", + Path: base.Append( + "checksum", + pkg.Encode(wantChecksum), + ).String(), + Err: syscall.EISDIR, + }}, + + {"no output", overrideIdent{pkg.ID{0xff, 4}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, loadData pkg.CacheDataFunc) error { + return nil + }, + }}, nil, pkg.Checksum{}, pkg.NoOutputError{}}, + + {"file output", overrideIdent{pkg.ID{0xff, 5}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, loadData pkg.CacheDataFunc) error { + return os.WriteFile(work.String(), []byte{0}, 0400) + }, + }}, nil, pkg.Checksum{}, errors.New("non-file artifact produced regular file")}, + + {"symlink output", overrideIdent{pkg.ID{0xff, 6}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, loadData pkg.CacheDataFunc) error { + return os.Symlink(work.String(), work.String()) + }, + }}, nil, pkg.Checksum{}, pkg.InvalidFileModeError( + fs.ModeSymlink | 0777, + )}, + }) }, pkg.MustDecode("8OP6YxJAdRrhV2WSBt1BPD7oC_n2Qh7JqUMyVMoGvjDX83bDqq2hgVMNcdiBH_64")}, + + {"pending", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { + c.SetStrict(true) + + 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(work *check.Absolute, loadData pkg.CacheDataFunc) error { + close(ready) + <-n + return wantErr + }, + }}); !reflect.DeepEqual(err, wantErr) { + panic(fmt.Sprintf("Cure: error = %v, want %v", err, wantErr)) + } + }() + + <-ready + 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)) + } + }() + + // check cache activity while a cure is blocking + cureMany(t, c, []cureStep{ + {"error passthrough", newStubFile( + pkg.KindHTTP, + pkg.ID{0xff, 1}, + nil, + nil, stub.UniqueError(0xbad), + ), nil, pkg.Checksum{}, stub.UniqueError(0xbad)}, + + {"file output", overrideIdent{pkg.ID{0xff, 2}, stubArtifact{ + kind: pkg.KindTar, + cure: func(work *check.Absolute, loadData pkg.CacheDataFunc) error { + return os.WriteFile(work.String(), []byte{0}, 0400) + }, + }}, nil, pkg.Checksum{}, errors.New("non-file artifact produced regular file")}, + }) + + identPendingVal := reflect.ValueOf(c).Elem().FieldByName("identPending") + identPending := reflect.NewAt( + identPendingVal.Type(), + unsafe.Pointer(identPendingVal.UnsafeAddr()), + ).Elem().Interface().(map[pkg.ID]<-chan struct{}) + notify := identPending[pkg.ID{0xff}] + go close(n) + <-notify + }, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")}, } checkWithCache(t, testCases) } @@ -484,6 +620,14 @@ func TestErrors(t *testing.T) { {"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"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { |
