internal/pkg: consistency check for on-disk cache
All checks were successful
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 2m32s
Test / ShareFS (push) Successful in 3m43s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m45s
Test / Hakurei (push) Successful in 2m31s
Test / Flake checks (push) Successful in 1m44s
All checks were successful
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 2m32s
Test / ShareFS (push) Successful in 3m43s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m45s
Test / Hakurei (push) Successful in 2m31s
Test / Flake checks (push) Successful in 1m44s
This change adds a method to check on-disk cache consistency and destroy inconsistent entries as they are encountered. This primarily helps verify artifact implementation correctness, but can also repair a cache that got into an inconsistent state from curing a misbehaving artifact, without having to destroy the entire cache. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -190,6 +191,7 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
}
|
||||
})
|
||||
|
||||
var scrubFunc func() error // scrub after hashing
|
||||
if c, err := pkg.New(base); err != nil {
|
||||
t.Fatalf("New: error = %v", err)
|
||||
} else {
|
||||
@@ -197,6 +199,7 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
tc.early(t, base)
|
||||
}
|
||||
tc.f(t, base, c)
|
||||
scrubFunc = c.Scrub
|
||||
}
|
||||
|
||||
if checksum, err := pkg.HashDir(base); err != nil {
|
||||
@@ -207,6 +210,10 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
Want: tc.want,
|
||||
})
|
||||
}
|
||||
|
||||
if err := scrubFunc(); err != nil {
|
||||
t.Fatal("cache contains inconsistencies\n\n" + err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -560,12 +567,14 @@ func TestCache(t *testing.T) {
|
||||
}()
|
||||
|
||||
<-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
|
||||
@@ -585,6 +594,13 @@ func TestCache(t *testing.T) {
|
||||
}}, nil, pkg.Checksum{}, errors.New("non-file artifact produced regular file")},
|
||||
})
|
||||
|
||||
wantErrScrub := &pkg.ScrubError{
|
||||
Errs: []error{errors.New("scrub began with pending artifacts")},
|
||||
}
|
||||
if err := c.Scrub(); !reflect.DeepEqual(err, wantErrScrub) {
|
||||
t.Fatalf("Scrub: error = %#v, want %#v", err, wantErrScrub)
|
||||
}
|
||||
|
||||
identPendingVal := reflect.ValueOf(c).Elem().FieldByName("identPending")
|
||||
identPending := reflect.NewAt(
|
||||
identPendingVal.Type(),
|
||||
@@ -593,6 +609,74 @@ func TestCache(t *testing.T) {
|
||||
notify := identPending[pkg.ID{0xff}]
|
||||
go close(n)
|
||||
<-notify
|
||||
<-wCureDone
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
|
||||
{"scrub", 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}),
|
||||
), pkg.Checksum{0xff, 0}, 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 := &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: []error{
|
||||
pkg.InvalidFileModeError(fs.ModeSymlink),
|
||||
base64.CorruptInputError(4),
|
||||
base64.CorruptInputError(8),
|
||||
&os.PathError{
|
||||
Op: "readlink",
|
||||
Path: base.Append("identifier", pkg.Encode(pkg.ID{0xfe, 0xff})).String(),
|
||||
Err: syscall.EINVAL,
|
||||
},
|
||||
base64.CorruptInputError(4),
|
||||
},
|
||||
}
|
||||
if err := c.Scrub(); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Fatalf("Scrub: error =\n%s\nwant\n%s", err, wantErr)
|
||||
}
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
}
|
||||
checkWithCache(t, testCases)
|
||||
@@ -640,6 +724,61 @@ func TestErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrubError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err pkg.ScrubError
|
||||
want string
|
||||
unwrap []error
|
||||
}{
|
||||
{"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: []error{
|
||||
stub.UniqueError(0xcafe),
|
||||
stub.UniqueError(0xbad),
|
||||
stub.UniqueError(0xff),
|
||||
},
|
||||
}, `checksum mismatches:
|
||||
got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA instead of CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN
|
||||
|
||||
dangling identifiers:
|
||||
deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
|
||||
cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe
|
||||
|
||||
errors during scrub:
|
||||
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 TestNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user