Files
hakurei/internal/pkg/pkg_test.go
Ophestra 834cb0d40b
All checks were successful
Test / Create distribution (push) Successful in 46s
Test / Sandbox (push) Successful in 2m45s
Test / ShareFS (push) Successful in 3m51s
Test / Hpkg (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m0s
Test / Hakurei (push) Successful in 2m34s
Test / Flake checks (push) Successful in 1m50s
internal/pkg: override "." for directory checksum
This makes the checksum consistent with the final resting state of artifact directories without incurring the cost of an extra pair of chown syscalls.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-01-05 04:00:13 +09:00

683 lines
17 KiB
Go

package pkg_test
import (
"archive/tar"
"bytes"
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"reflect"
"syscall"
"testing"
"unsafe"
"hakurei.app/container"
"hakurei.app/container/check"
"hakurei.app/container/stub"
"hakurei.app/internal/pkg"
)
// 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
params []byte
deps []pkg.Artifact
cure func(work, temp *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, temp *check.Absolute,
loadData pkg.CacheDataFunc,
) error {
return a.cure(work, temp, loadData)
}
// 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(*check.Absolute, *check.Absolute, 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
a pkg.Artifact
want pkg.ID
}{
{"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 := pkg.Ident(tc.a); got != tc.want {
t.Errorf("Ident: %s, want %s",
pkg.Encode(got),
pkg.Encode(tc.want),
)
}
})
}
}
// 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
early func(t *testing.T, base *check.Absolute)
f func(t *testing.T, base *check.Absolute, c *pkg.Cache)
want pkg.Checksum
}
// 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 := check.MustAbs(t.TempDir())
if err := os.Chmod(base.String(), 0700); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
if err != nil {
t.Error(err)
return nil
}
if !d.IsDir() {
return nil
}
return os.Chmod(path, 0700)
}); err != nil {
t.Fatal(err)
}
})
if c, err := pkg.New(base); err != nil {
t.Fatalf("New: error = %v", err)
} else {
if tc.early != nil {
tc.early(t, base)
}
tc.f(t, base, c)
}
if checksum, err := pkg.HashDir(base); err != nil {
t.Fatalf("HashDir: error = %v", err)
} else if checksum != tc.want {
t.Fatalf("HashDir: %v", &pkg.ChecksumMismatchError{
Got: checksum,
Want: tc.want,
})
}
})
}
}
// 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()
const testdata = "" +
"\x00\x00\x00\x00" +
"\xad\x0b\x00" +
"\x04" +
"\xfe\xfe\x00\x00" +
"\xfe\xca\x00\x00"
testdataChecksum := func() pkg.Checksum {
h := sha512.New384()
h.Write([]byte(testdata))
return (pkg.Checksum)(h.Sum(nil))
}()
testCases := []cacheTestCase{
{"file", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
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",
)
cureMany(t, c, []cureStep{
{"initial file", newStubFile(
pkg.KindHTTPGet,
identifier,
&testdataChecksum,
[]byte(testdata), nil,
), wantPathname, testdataChecksum, nil},
{"identical content", newStubFile(
pkg.KindHTTPGet,
identifier0,
&testdataChecksum,
[]byte(testdata), nil,
), wantPathname0, testdataChecksum, nil},
{"existing entry", newStubFile(
pkg.KindHTTPGet,
identifier,
&testdataChecksum,
[]byte(testdata), nil,
), wantPathname, testdataChecksum, nil},
{"checksum mismatch", newStubFile(
pkg.KindHTTPGet,
pkg.ID{0xff, 0},
new(pkg.Checksum),
[]byte(testdata), nil,
), nil, pkg.Checksum{}, &pkg.ChecksumMismatchError{
Got: testdataChecksum,
}},
{"store without validation", newStubFile(
pkg.KindHTTPGet,
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},
{"error passthrough", newStubFile(
pkg.KindHTTPGet,
pkg.ID{0xff, 1},
nil,
nil, stub.UniqueError(0xcafe),
), nil, pkg.Checksum{}, stub.UniqueError(0xcafe)},
{"error caching", newStubFile(
pkg.KindHTTPGet,
pkg.ID{0xff, 1},
nil,
nil, nil,
), nil, pkg.Checksum{}, stub.UniqueError(0xcafe)},
{"cache hit bad type", overrideChecksum{testdataChecksum, overrideIdent{pkg.ID{0xff, 2}, stubArtifact{
kind: pkg.KindTar,
}}}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
0400,
)},
})
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},
{"cache miss checksum match", newStubFile(
pkg.KindHTTPGet,
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),
overrideIdent{testdataChecksum, stubArtifact{}},
)
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},
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(),
)
}
wantChecksum := pkg.MustDecode(
"qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b",
)
wantPathname := base.Append(
"identifier",
pkg.Encode(id),
)
id0 := pkg.KindTar.Ident(
binary.LittleEndian.AppendUint64(nil, pkg.TarGzip),
overrideIdent{pkg.ID{}, stubArtifact{}},
)
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{wantChecksum, overrideIdent{id, stubArtifact{
kind: pkg.KindTar,
cure: makeSample,
}}}, wantPathname, wantChecksum, nil},
{"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(
"CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT",
),
}},
{"cache hit bad type", newStubFile(
pkg.KindHTTPGet,
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("WVpvsVqVKg9Nsh744x57h51AuWUoUR2nnh8Md-EYBQpk6ziyTuUn6PLtF2e0Eu_d")},
{"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.KindHTTPGet,
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)
}
func TestErrors(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
err error
want string
}{
{"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"},
}
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 TestNew(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.New(check.MustAbs(container.Nonexistent)); !reflect.DeepEqual(err, wantErr) {
t.Errorf("New: 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.New(tempDir.Append("cache")); !reflect.DeepEqual(err, wantErr) {
t.Errorf("New: error = %#v, want %#v", err, wantErr)
}
})
}