internal/pkg: move dependency flooding to cache
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m50s
Test / ShareFS (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hpkg (push) Successful in 5m24s
Test / Hakurei (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 7m27s
Test / Flake checks (push) Successful in 1m42s

This imposes a hard upper limit to concurrency during dependency satisfaction and moves all dependency-related code out of individual implementations of Artifact. This change also includes ctx and msg as part of Cache.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-01-09 01:51:39 +09:00
parent f2430b5f5e
commit f712466714
11 changed files with 680 additions and 409 deletions

View File

@@ -3,6 +3,7 @@ package pkg_test
import (
"archive/tar"
"bytes"
"context"
"crypto/sha512"
"encoding/base64"
"encoding/binary"
@@ -10,6 +11,7 @@ import (
"fmt"
"io"
"io/fs"
"log"
"net/http"
"os"
"path/filepath"
@@ -22,6 +24,7 @@ import (
"hakurei.app/container/check"
"hakurei.app/container/stub"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
func TestMain(m *testing.M) { container.TryArgv0(nil); os.Exit(m.Run()) }
@@ -29,7 +32,7 @@ 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.Artifact
pkg.TrivialArtifact
}
func (a overrideIdent) ID() pkg.ID { return a.id }
@@ -45,7 +48,7 @@ func (a overrideIdentFile) ID() pkg.ID { return a.id }
// A knownIdentArtifact implements [pkg.KnownIdent] and [Artifact]
type knownIdentArtifact interface {
pkg.KnownIdent
pkg.Artifact
pkg.TrivialArtifact
}
// A knownIdentFile implements [pkg.KnownIdent] and [File]
@@ -70,19 +73,33 @@ type overrideChecksumFile struct {
func (a overrideChecksumFile) Checksum() pkg.Checksum { return a.checksum }
// A stubArtifact implements [Artifact] with hardcoded behaviour.
// A stubArtifact implements [TrivialArtifact] with hardcoded behaviour.
type stubArtifact struct {
kind pkg.Kind
params []byte
deps []pkg.Artifact
cure func(c *pkg.CureContext) error
cure func(t *pkg.TContext) 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(c *pkg.CureContext) error { return a.cure(c) }
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(t *pkg.TContext) error { return a.cure(t) }
// A stubArtifactF implements [FloodArtifact] with hardcoded behaviour.
type stubArtifactF struct {
kind pkg.Kind
params []byte
deps []pkg.Artifact
cure func(f *pkg.FContext) error
}
func (a stubArtifactF) Kind() pkg.Kind { return a.kind }
func (a stubArtifactF) Params() []byte { return a.params }
func (a stubArtifactF) Dependencies() []pkg.Artifact { return a.deps }
func (a stubArtifactF) Cure(f *pkg.FContext) error { return a.cure(f) }
// A stubFile implements [File] with hardcoded behaviour.
type stubFile struct {
@@ -92,7 +109,7 @@ type stubFile struct {
stubArtifact
}
func (a stubFile) Data() ([]byte, error) { return a.data, a.err }
func (a stubFile) Cure() ([]byte, error) { return a.data, a.err }
// newStubFile returns an implementation of [pkg.File] with hardcoded behaviour.
func newStubFile(
@@ -106,7 +123,7 @@ func newStubFile(
kind,
nil,
nil,
func(*pkg.CureContext) error {
func(*pkg.TContext) error {
panic("unreachable")
},
}}}
@@ -241,10 +258,14 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
}
})
msg := message.New(log.New(os.Stderr, "cache: ", 0))
msg.SwapVerbose(testing.Verbose())
var scrubFunc func() error // scrub after hashing
if c, err := pkg.New(base); err != nil {
if c, err := pkg.New(t.Context(), msg, 0, base); err != nil {
t.Fatalf("New: error = %v", err)
} else {
t.Cleanup(c.Close)
if tc.early != nil {
tc.early(t, base)
}
@@ -312,6 +333,8 @@ 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()
for _, step := range steps {
t.Log("cure step:", step.name)
if pathname, checksum, err := c.Cure(step.a); !reflect.DeepEqual(err, step.err) {
@@ -415,6 +438,13 @@ func TestCache(t *testing.T) {
0xa9, 0xc2, 0x08, 0xa1, 0x17, 0x17,
}, nil},
{"incomplete implementation", struct{ pkg.Artifact }{stubArtifact{
kind: pkg.KindExec,
params: []byte("artifact overridden to be incomplete"),
}}, nil, pkg.Checksum{}, pkg.InvalidArtifactError(pkg.MustDecode(
"da4kLKa94g1wN2M0qcKflqgf2-Y2UL36iehhczqsIIW8G0LGvM7S8jjtnBc0ftB0",
))},
{"error passthrough", newStubFile(
pkg.KindHTTPGet,
pkg.ID{0xff, 1},
@@ -436,9 +466,14 @@ func TestCache(t *testing.T) {
)},
})
if c0, err := pkg.New(base); err != nil {
if c0, err := pkg.New(
t.Context(),
message.New(nil),
0, base,
); err != nil {
t.Fatalf("New: error = %v", err)
} else {
t.Cleanup(c.Close) // check doubled cancel
cureMany(t, c0, []cureStep{
{"cache hit ident", overrideIdent{
id: identifier,
@@ -455,6 +490,16 @@ func TestCache(t *testing.T) {
pkg.Encode(testdataChecksum),
), testdataChecksum, 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)
}
}
}, pkg.MustDecode("St9rlE-mGZ5gXwiv_hzQ_B8bZP-UUvSNmf4nHUZzCMOumb6hKnheZSe0dmnuc4Q2")},
@@ -463,8 +508,8 @@ func TestCache(t *testing.T) {
binary.LittleEndian.AppendUint64(nil, pkg.TarGzip),
overrideIdent{testdataChecksum, stubArtifact{}},
)
makeSample := func(c *pkg.CureContext) error {
work := c.GetWorkDir()
makeSample := func(t *pkg.TContext) error {
work := t.GetWorkDir()
if err := os.Mkdir(work.String(), 0700); err != nil {
return err
}
@@ -565,15 +610,15 @@ func TestCache(t *testing.T) {
{"cure fault", overrideIdent{pkg.ID{0xff, 0}, stubArtifact{
kind: pkg.KindTar,
cure: func(c *pkg.CureContext) error {
return makeGarbage(c.GetWorkDir(), stub.UniqueError(0xcafe))
cure: func(t *pkg.TContext) error {
return makeGarbage(t.GetWorkDir(), 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(c *pkg.CureContext) error {
return makeGarbage(c.GetWorkDir(), nil)
cure: func(t *pkg.TContext) error {
return makeGarbage(t.GetWorkDir(), nil)
},
}}}, nil, pkg.Checksum{}, &pkg.ChecksumMismatchError{
Got: pkg.MustDecode(
@@ -592,8 +637,8 @@ func TestCache(t *testing.T) {
{"openFile directory", overrideIdent{pkg.ID{0xff, 3}, stubArtifact{
kind: pkg.KindTar,
cure: func(c *pkg.CureContext) error {
r, err := c.OpenFile(overrideChecksumFile{checksum: wantChecksum})
cure: func(t *pkg.TContext) error {
r, err := t.Open(overrideChecksumFile{checksum: wantChecksum})
if err != nil {
panic(err)
}
@@ -611,24 +656,24 @@ func TestCache(t *testing.T) {
{"no output", overrideIdent{pkg.ID{0xff, 4}, stubArtifact{
kind: pkg.KindTar,
cure: func(c *pkg.CureContext) error {
cure: func(t *pkg.TContext) error {
return nil
},
}}, nil, pkg.Checksum{}, pkg.NoOutputError{}},
{"file output", overrideIdent{pkg.ID{0xff, 5}, stubArtifact{
kind: pkg.KindTar,
cure: func(c *pkg.CureContext) error {
return os.WriteFile(c.GetWorkDir().String(), []byte{0}, 0400)
cure: func(t *pkg.TContext) error {
return os.WriteFile(t.GetWorkDir().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(c *pkg.CureContext) error {
cure: func(t *pkg.TContext) error {
return os.Symlink(
c.GetWorkDir().String(),
c.GetWorkDir().String(),
t.GetWorkDir().String(),
t.GetWorkDir().String(),
)
},
}}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
@@ -645,7 +690,7 @@ func TestCache(t *testing.T) {
go func() {
if _, _, err := c.Cure(overrideIdent{pkg.ID{0xff}, stubArtifact{
kind: pkg.KindTar,
cure: func(c *pkg.CureContext) error {
cure: func(t *pkg.TContext) error {
close(ready)
<-n
return wantErr
@@ -677,9 +722,9 @@ func TestCache(t *testing.T) {
{"file output", overrideIdent{pkg.ID{0xff, 2}, stubArtifact{
kind: pkg.KindTar,
cure: func(c *pkg.CureContext) error {
cure: func(t *pkg.TContext) error {
return os.WriteFile(
c.GetWorkDir().String(),
t.GetWorkDir().String(),
[]byte{0},
0400,
)
@@ -785,6 +830,14 @@ func TestErrors(t *testing.T) {
err error
want string
}{
{"InvalidLookupError", pkg.InvalidLookupError{
0xff, 0xf0,
}, "attempting to look up non-dependency 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,
@@ -885,7 +938,11 @@ func TestNew(t *testing.T) {
Path: container.Nonexistent,
Err: syscall.ENOENT,
}
if _, err := pkg.New(check.MustAbs(container.Nonexistent)); !reflect.DeepEqual(err, wantErr) {
if _, err := pkg.New(
t.Context(),
message.New(nil),
0, check.MustAbs(container.Nonexistent),
); !reflect.DeepEqual(err, wantErr) {
t.Errorf("New: error = %#v, want %#v", err, wantErr)
}
})
@@ -909,7 +966,11 @@ func TestNew(t *testing.T) {
Path: tempDir.Append("cache").String(),
Err: syscall.EACCES,
}
if _, err := pkg.New(tempDir.Append("cache")); !reflect.DeepEqual(err, wantErr) {
if _, err := pkg.New(
t.Context(),
message.New(nil),
0, tempDir.Append("cache"),
); !reflect.DeepEqual(err, wantErr) {
t.Errorf("New: error = %#v, want %#v", err, wantErr)
}
})