aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/store/data_test.go
blob: 08eb4f9c564c5134933f8fc9385d9593c94e0535 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package store

import (
	"bytes"
	"encoding/gob"
	"errors"
	"io"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	"hakurei.app/hst"
	"hakurei.app/internal/stub"
)

func TestEntryData(t *testing.T) {
	t.Parallel()

	mustEncodeGob := func(e any) string {
		var buf bytes.Buffer
		if err := gob.NewEncoder(&buf).Encode(e); err != nil {
			t.Fatalf("cannot encode invalid state: %v", err)
			return "\x00" // not reached
		} else {
			return buf.String()
		}
	}
	templateStateGob := mustEncodeGob(NewTemplateState())

	testCases := []struct {
		name string
		data string
		s    *hst.State
		err  error
	}{
		{"invalid header", "\x00\xff\xca\xfe\xff\xff\xff\x00", nil, &hst.AppError{
			Step: "decode state header", Err: errors.New("unexpected revision ffff")}},

		{"invalid gob", "\x00\xff\xca\xfe\x00\x00\xff\x00", nil, &hst.AppError{
			Step: "decode state body", Err: io.EOF}},

		{"invalid config", "\x00\xff\xca\xfe\x00\x00\xff\x00" + mustEncodeGob(new(hst.State)), new(hst.State), &hst.AppError{
			Step: "validate configuration", Err: hst.ErrConfigNull,
			Msg: "invalid configuration"}},

		{"inconsistent enablement", "\x00\xff\xca\xfe\x00\x00\xff\x00" + templateStateGob, NewTemplateState(), &hst.AppError{
			Step: "validate state enablement", Err: os.ErrInvalid,
			Msg: "state entry aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa has unexpected enablement byte 0xd, 0xff"}},

		{"template", "\x00\xff\xca\xfe\x00\x00\x0d\xf2" + templateStateGob, NewTemplateState(), nil},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			t.Run("encode", func(t *testing.T) {
				if tc.s == nil || tc.s.Config == nil {
					return
				}
				t.Parallel()

				var buf bytes.Buffer
				if err := entryEncode(&buf, tc.s); err != nil {
					t.Fatalf("entryEncode: error = %v", err)
				}

				if tc.err == nil {
					// Gob encoding is not guaranteed to be deterministic.
					// While the current implementation mostly is, it has randomised order
					// for iterating over maps, and hst.Config holds a map for environ.
					var got hst.State
					if et, err := entryDecode(&buf, &got); err != nil {
						t.Fatalf("entryDecode: error = %v", err)
					} else if stateEt := got.Enablements.Unwrap(); et != stateEt {
						t.Fatalf("entryDecode: et = %x, state %x", et, stateEt)
					}
					if !reflect.DeepEqual(&got, tc.s) {
						t.Errorf("entryEncode: %x", buf.Bytes())
					}
				} else if testing.Verbose() {
					t.Logf("%x", buf.String())
				}
			})

			t.Run("decode", func(t *testing.T) {
				t.Parallel()

				var got hst.State
				if et, err := entryDecode(strings.NewReader(tc.data), &got); !reflect.DeepEqual(err, tc.err) {
					t.Fatalf("entryDecode: error = %#v, want %#v", err, tc.err)
				} else if err != nil {
					return
				} else if stateEt := got.Enablements.Unwrap(); et != stateEt {
					t.Fatalf("entryDecode: et = %x, state %x", et, stateEt)
				}

				if !reflect.DeepEqual(&got, tc.s) {
					t.Errorf("entryDecode: %#v, want %#v", &got, tc.s)
				}
			})
		})
	}

	t.Run("encode fault", func(t *testing.T) {
		t.Parallel()
		s := NewTemplateState()

		t.Run("gob", func(t *testing.T) {
			var want = &hst.AppError{Step: "encode state body", Err: stub.UniqueError(0xcafe)}
			if err := entryEncode(stubNErrorWriter(entryHeaderSize), s); !reflect.DeepEqual(err, want) {
				t.Errorf("entryEncode: error = %#v, want %#v", err, want)
			}
		})

		t.Run("header", func(t *testing.T) {
			var want = &hst.AppError{Step: "encode state header", Err: stub.UniqueError(0xcafe)}
			if err := entryEncode(stubNErrorWriter(entryHeaderSize-1), s); !reflect.DeepEqual(err, want) {
				t.Errorf("entryEncode: error = %#v, want %#v", err, want)
			}
		})
	})
}

// NewTemplateState returns the address of a new template [hst.State] struct.
func NewTemplateState() *hst.State {
	return &hst.State{
		ID:      hst.ID(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
		PID:     0xcafe,
		ShimPID: 0xdead,
		Config:  hst.Template(),
		Time:    time.Unix(0, 0),
	}
}

// stubNErrorWriter returns an error for writes above a certain size.
type stubNErrorWriter int

func (w stubNErrorWriter) Write(p []byte) (n int, err error) {
	if len(p) > int(w) {
		return int(w), stub.UniqueError(0xcafe)
	}
	return io.Discard.Write(p)
}