package pkg_test import ( "bytes" "crypto/sha512" "encoding/base64" "io/fs" "os" "path/filepath" "reflect" "syscall" "testing" "hakurei.app/container" "hakurei.app/container/check" "hakurei.app/container/stub" "hakurei.app/internal/pkg" ) // 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.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, }) } }) } } 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)) }() testdataChecksumString := base64.URLEncoding.EncodeToString(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) } 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", ) // 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) } // 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") } // 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") } // 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 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) } // verify failed store if _, _, err := c.LoadFile(testdataChecksum); !reflect.DeepEqual(err, wantErrNonexistent) { t.Fatalf("LoadFile: error = %#v, want %#v", err, wantErrNonexistent) } // 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) } // 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", base64.URLEncoding.EncodeToString(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) } // 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) } // 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) } }, pkg.MustDecode("ZNSQH-mjhtIbFvi51lQ0UjatjoS8_5ILrBPNWlO2LWTq9P6MJEnekYzP0esUJnVr")}, } 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"}, } 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) } }) }