Files
hakurei/internal/pkg/pkg_test.go
Ophestra 4da26681b5
All checks were successful
Test / Create distribution (push) Successful in 44s
Test / Sandbox (push) Successful in 2m30s
Test / ShareFS (push) Successful in 3m40s
Test / Hpkg (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 4m46s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Hakurei (push) Successful in 2m28s
Test / Flake checks (push) Successful in 1m41s
internal/pkg: compute http identifier from url
The previous implementation exposes arbitrary user input to the cache as an identifier, which is highly error-prone and can cause the cache to enter an inconsistent state if the user is not careful. This change replaces the implementation to compute identifier late, using url string as params.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-01-05 00:43:21 +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 *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)
}
// 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
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(
"1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP",
)
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(
"GbjlYMcHQANdfwL6qNGopBF99IscPTvCy95HSH1_kIF3eKjFDSLP0_iUUT0z8hiw",
),
}},
{"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("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.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)
}
})
}