diff options
Diffstat (limited to 'internal/store/header_test.go')
| -rw-r--r-- | internal/store/header_test.go | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/internal/store/header_test.go b/internal/store/header_test.go new file mode 100644 index 00000000..51a6939a --- /dev/null +++ b/internal/store/header_test.go @@ -0,0 +1,184 @@ +package store + +import ( + "bytes" + "errors" + "io" + "io/fs" + "os" + "reflect" + "syscall" + "testing" + "time" + + "hakurei.app/hst" +) + +func TestEntryHeader(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + data [entryHeaderSize]byte + et hst.Enablement + err error + }{ + {"complement mismatch", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00, + 0x0a, 0xf6}, 0, + errors.New("header enablement value is inconsistent")}, + {"unexpected revision", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0xff, 0xff}, 0, + errors.New("unexpected revision ffff")}, + {"invalid header", [entryHeaderSize]byte{0x00, 0xfe, 0xca, 0xfe}, 0, + errors.New("invalid header 00fecafe")}, + + {"success high", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00, + 0xff, 0x00}, 0xff, nil}, + {"success", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00, + 0x09, 0xf6}, hst.EWayland | hst.EPulse, nil}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + t.Run("encode", func(t *testing.T) { + if tc.err != nil { + return + } + t.Parallel() + + if got := entryHeaderEncode(tc.et); *got != tc.data { + t.Errorf("entryHeaderEncode: %x, want %x", *got, tc.data) + } + + t.Run("write", func(t *testing.T) { + var buf bytes.Buffer + if err := entryWriteHeader(&buf, tc.et); err != nil { + t.Fatalf("entryWriteHeader: error = %v", err) + } + if got := ([entryHeaderSize]byte)(buf.Bytes()); got != tc.data { + t.Errorf("entryWriteHeader: %x, want %x", got, tc.data) + } + }) + }) + + t.Run("decode", func(t *testing.T) { + t.Parallel() + + got, err := entryHeaderDecode(&tc.data) + if !reflect.DeepEqual(err, tc.err) { + t.Fatalf("entryHeaderDecode: error = %#v, want %#v", err, tc.err) + } + if err != nil { + return + } + if got != tc.et { + t.Errorf("entryHeaderDecode: et = %q, want %q", got, tc.et) + } + + if got, err = entryReadHeader(bytes.NewReader(tc.data[:])); err != nil { + t.Fatalf("entryReadHeader: error = %#v", err) + } else if got != tc.et { + t.Errorf("entryReadHeader: et = %q, want %q", got, tc.et) + } + }) + }) + } +} + +func TestEntrySizeError(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + err error + want string + }{ + {"size only", &EntrySizeError{Size: 0xdeadbeef}, + `state entry file is too short`}, + {"full", &EntrySizeError{Name: "nonexistent", Size: 0xdeadbeef}, + `state entry file "nonexistent" is too short`}, + } + 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: %s, want %s", got, tc.want) + } + }) + } +} + +func TestEntryCheckFile(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + fi os.FileInfo + err error + }{ + {"dir", &stubFi{name: "dir", isDir: true}, + syscall.EISDIR}, + {"short", stubFi{name: "short", size: 8}, + &EntrySizeError{Name: "short", Size: 8}}, + {"success", stubFi{size: 9}, nil}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + if err := entryCheckFile(tc.fi); !reflect.DeepEqual(err, tc.err) { + t.Errorf("entryCheckFile: error = %#v, want %#v", err, tc.err) + } + }) + } +} + +func TestEntryReadHeader(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + newR func() io.Reader + err error + }{ + {"eof", func() io.Reader { return bytes.NewReader([]byte{}) }, io.EOF}, + {"short", func() io.Reader { return bytes.NewReader([]byte{0}) }, &EntrySizeError{Size: 1}}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + if _, err := entryReadHeader(tc.newR()); !reflect.DeepEqual(err, tc.err) { + t.Errorf("entryReadHeader: error = %#v, want %#v", err, tc.err) + } + }) + } +} + +// stubFi partially implements [os.FileInfo] using hardcoded values. +type stubFi struct { + name string + size int64 + isDir bool +} + +func (fi stubFi) Name() string { + if fi.name == "" { + panic("unreachable") + } + return fi.name +} + +func (fi stubFi) Size() int64 { + if fi.size < 0 { + panic("unreachable") + } + return fi.size +} + +func (fi stubFi) IsDir() bool { return fi.isDir } + +func (fi stubFi) Mode() fs.FileMode { panic("unreachable") } +func (fi stubFi) ModTime() time.Time { panic("unreachable") } +func (fi stubFi) Sys() any { panic("unreachable") } |
