1package cef
2
3// P2.1 tests: the bundle loads its WASM exactly once (success or failure),
4// with the hardened semantics — compressed authoritative, raw fallback only
5// on absence, raw reads bounded, failures cached.
6
7import (
8 "errors"
9 "io"
10 "io/fs"
11 "sync"
12 "testing"
13 "testing/fstest"
14
15 "github.com/stretchr/testify/require"
16)
17
18// countingFS counts Open and ReadFile calls per name. Note MapFS
19// implements ReadFileFS, so fs.ReadFile bypasses Open: both entry points
20// must be counted explicitly.
21type countingFS struct {
22 inner fstest.MapFS
23 mu sync.Mutex
24 opens map[string]int
25}
26
27func (c *countingFS) record(name string) {
28 c.mu.Lock()
29 defer c.mu.Unlock()
30 if c.opens == nil {
31 c.opens = map[string]int{}
32 }
33 c.opens[name]++
34}
35
36func (c *countingFS) Open(name string) (fs.File, error) {
37 c.record("open:" + name)
38 return c.inner.Open(name)
39}
40
41func (c *countingFS) ReadFile(name string) ([]byte, error) {
42 c.record("readfile:" + name)
43 return c.inner.ReadFile(name)
44}
45
46// compressedReads counts compressed WASM reads through either entry
47// point: the loader may Open the file directly (bounded reads) or use
48// the ReadFileFS shortcut, depending on the hardened path.
49func (c *countingFS) compressedReads() int {
50 c.mu.Lock()
51 defer c.mu.Unlock()
52 name := systemviewsWASMPath + ".br"
53 return c.opens["readfile:"+name] + c.opens["open:"+name]
54}
55
56// denyFS fails every Open with a fixed error (e.g. permission denied).
57type denyFS struct{ err error }
58
59func (d denyFS) Open(name string) (fs.File, error) {
60 return nil, &fs.PathError{Op: "open", Path: name, Err: d.err}
61}
62
63func TestSystemviewAssetCache_DecodesOnceUnderConcurrency(t *testing.T) {
64 t.Parallel()
65
66 wasm := []byte("\x00asm-bundle-once")
67 fsys := &countingFS{inner: fstest.MapFS{
68 "systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, wasm)},
69 }}
70 b := newSystemviewAssetBundle(fsys)
71
72 const readers = 8
73 got := make([][]byte, readers)
74 errs := make([]error, readers)
75 var wg sync.WaitGroup
76 for i := range got {
77 wg.Add(1)
78 go func(i int) {
79 defer wg.Done()
80 got[i], errs[i] = b.WASM()
81 }(i)
82 }
83 wg.Wait()
84 for i := range got {
85 require.NoError(t, errs[i])
86 require.Equal(t, wasm, got[i])
87 require.Same(t, &got[0][0], &got[i][0], "waiters must share one immutable buffer")
88 }
89 require.Equal(t, 1, fsys.compressedReads(), "bundle must decode exactly once")
90}
91
92func TestSystemviewAssetCache_CachesFailureWithoutRetryStorm(t *testing.T) {
93 t.Parallel()
94
95 fsys := &countingFS{inner: fstest.MapFS{
96 "systemviews/systemviews.wasm.br": {Data: []byte("not-brotli")},
97 "systemviews/systemviews.wasm": {Data: []byte("\x00asm-valid-raw")},
98 }}
99 b := newSystemviewAssetBundle(fsys)
100
101 _, err := b.WASM()
102 require.Error(t, err, "corrupt compressed WASM must fail even with valid raw present")
103 firstReads := fsys.compressedReads()
104 _, err = b.WASM()
105 require.Error(t, err)
106 require.Equal(t, firstReads, fsys.compressedReads(), "cached failure must not re-read")
107}
108
109func TestSystemviewAssetCache_CompressedReadErrorIsExplicit(t *testing.T) {
110 t.Parallel()
111
112 // Permission (or any non-absence) failures reading the compressed file
113 // must not silently fall back to raw.
114 b := newSystemviewAssetBundle(denyFS{err: fs.ErrPermission})
115
116 _, err := b.WASM()
117 require.Error(t, err)
118 require.ErrorIs(t, err, fs.ErrPermission, "must surface the read failure")
119 require.NotErrorIs(t, err, fs.ErrNotExist)
120}
121
122func TestSystemviewAssetCache_RawFallbackOnlyOnAbsence(t *testing.T) {
123 t.Parallel()
124
125 raw := []byte("\x00asm-raw-fallback")
126 b := newSystemviewAssetBundle(fstest.MapFS{
127 "systemviews/systemviews.wasm": {Data: raw},
128 })
129
130 data, err := b.WASM()
131 require.NoError(t, err)
132 require.Equal(t, raw, data)
133}
134
135func TestSystemviewAssetCache_RawOversizedRejected(t *testing.T) {
136 t.Parallel()
137
138 big := make([]byte, maxSystemviewsWASMBytes+1)
139 b := newSystemviewAssetBundle(fstest.MapFS{
140 "systemviews/systemviews.wasm": {Data: big},
141 })
142
143 _, err := b.WASM()
144 require.Error(t, err, "raw WASM past the bound must fail like the compressed path")
145}
146
147func TestSystemviewAssetCache_MissingBothReportsNotExist(t *testing.T) {
148 t.Parallel()
149
150 b := newSystemviewAssetBundle(fstest.MapFS{})
151
152 _, err := b.WASM()
153 require.Error(t, err)
154 require.ErrorIs(t, err, fs.ErrNotExist)
155}
156
157func TestSystemviewAssetCache_NilFSFailsCleanly(t *testing.T) {
158 t.Parallel()
159
160 b := newSystemviewAssetBundle(nil)
161 _, err := b.WASM()
162 require.Error(t, err)
163}
164
165// zeroFile yields size bytes of zeros without allocating them, proving
166// input bounds without a 64 MiB fixture in memory.
167type zeroFile struct {
168 remaining int64
169}
170
171func (f *zeroFile) Read(p []byte) (int, error) {
172 if f.remaining <= 0 {
173 return 0, io.EOF
174 }
175 n := min(int64(len(p)), f.remaining)
176 clear(p[:n])
177 f.remaining -= n
178 return int(n), nil
179}
180
181func (f *zeroFile) Close() error { return nil }
182
183func (f *zeroFile) Stat() (fs.FileInfo, error) { return nil, errors.New("zeroFile has no stat") }
184
185// oversizedInputFS serves an unbounded-looking compressed file: the
186// bundle must reject it on size before allocation.
187type oversizedInputFS struct {
188 fstest.MapFS
189}
190
191func (o oversizedInputFS) Open(name string) (fs.File, error) {
192 if name == systemviewsWASMPath+".br" {
193 return &zeroFile{remaining: int64(maxSystemviewsWASMBytes) + 1}, nil
194 }
195 return o.MapFS.Open(name)
196}
197
198func TestSystemviewAssetCache_OversizedCompressedInputRejected(t *testing.T) {
199 t.Parallel()
200
201 b := newSystemviewAssetBundle(oversizedInputFS{fstest.MapFS{
202 "systemviews/systemviews.wasm": {Data: []byte("\x00asm-valid-raw")},
203 }})
204 _, err := b.WASM()
205 require.Error(t, err, "oversized compressed input must fail before allocation")
206 require.Contains(t, err.Error(), "exceeds")
207}
208
209func TestSystemviewAssetCache_DigestCachedAtLoad(t *testing.T) {
210 t.Parallel()
211
212 wasm := []byte("\x00asm-digest")
213 b := newSystemviewAssetBundle(fstest.MapFS{
214 "systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, wasm)},
215 })
216 digest, err := b.WASMDigest()
217 require.NoError(t, err)
218 require.Equal(t, sha256Hex(wasm), digest, "cached digest must match the decoded bytes")
219
220 // A failing bundle reports the load error instead of an empty digest.
221 bad := newSystemviewAssetBundle(denyFS{err: fs.ErrPermission})
222 _, err = bad.WASMDigest()
223 require.Error(t, err)
224}