squashfs - squashfs_test.go
1 package squashfs
2
3 import (
4 "fmt"
5 "io"
6 "io/fs"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11 "testing/fstest"
12 "time"
13 )
14
15 var (
16 contentsA = "my contents"
17 contentsB = strings.Repeat("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"+contentsA, 1024)
18 contentsC = strings.Repeat("ABCDEFGHIJKLMNOP", 8192)
19 contentsD = strings.Repeat("ZYXWVUTSRQPONMLK", 16384)
20 contentsE = contentsA + contentsD
21
22 timestamp = time.Unix(1234567, 0)
23 )
24
25 type testFn func(FS) error
26
27 func test(t *testing.T, runFSTest bool, tests []testFn, children ...child) {
28 t.Helper()
29
30 sqfs, err := buildSquashFS(t, children...)
31 if err != nil {
32 t.Fatalf("unexpected error creating squashfs file: %s", err)
33 }
34
35 f, err := os.Open(sqfs)
36 if err != nil {
37 t.Fatalf("unexpected error opening squashfs file: %s", err)
38 }
39 defer f.Close()
40
41 sfs, err := Open(f)
42 if err != nil {
43 t.Fatalf("unexpected error opening squashfs reader: %s", err)
44 }
45
46 if runFSTest {
47 if err := fstest.TestFS(sfs, ".required"); err != nil {
48 t.Fatal(err)
49 }
50 }
51
52 for n, test := range tests {
53 if err := test(sfs); err != nil {
54 t.Errorf("test %d: %s", n+1, err)
55 }
56 }
57 }
58
59 func readSqfsFile(sfs fs.FS, path, expectation string) error {
60 f, err := sfs.Open(path)
61 if err != nil {
62 return fmt.Errorf("unexpected error opening file in squashfs FS: %w", err)
63 }
64
65 contents, err := io.ReadAll(f)
66 if err != nil {
67 return fmt.Errorf("unexpected error reading file in squashfs FS: %w", err)
68 }
69
70 if string(contents) != expectation {
71 return fmt.Errorf("expected to read %q, got %q", expectation, contents)
72 }
73
74 return nil
75 }
76
77 func TestOpenRead(t *testing.T) {
78 test(
79 t,
80 true,
81 []testFn{
82 func(sfs FS) error {
83 return readSqfsFile(sfs, filepath.Join("dirA", "fileA"), contentsA)
84 },
85 func(sfs FS) error {
86 return readSqfsFile(sfs, filepath.Join("dirA", "fileB"), contentsB)
87 },
88 func(sfs FS) error {
89 return readSqfsFile(sfs, filepath.Join("dirA", "fileC"), contentsC)
90 },
91 func(sfs FS) error {
92 return readSqfsFile(sfs, filepath.Join("dirA", "fileD"), contentsD)
93 },
94 func(sfs FS) error {
95 return readSqfsFile(sfs, filepath.Join("dirA", "fileE"), contentsE)
96 },
97 },
98 dirData("dirA", []child{
99 fileData("fileA", contentsA),
100 fileData("fileB", contentsB),
101 fileData("fileC", contentsC),
102 fileData("fileD", contentsD),
103 fileData("fileE", contentsE),
104 }),
105 )
106 }
107
108 var offsetReadTests = [...]struct {
109 Start, Length int64
110 Expectation string
111 }{
112 {
113 0, 10,
114 contentsE[:10],
115 },
116 {
117 0, 100,
118 contentsE[:100],
119 },
120 {
121 0, 1000,
122 contentsE[:1000],
123 },
124 {
125 100, 10,
126 contentsE[100:110],
127 },
128 {
129 100, 100,
130 contentsE[100:200],
131 },
132 {
133 100, 1000,
134 contentsE[100:1100],
135 },
136 {
137 0, 1 << 15,
138 contentsE[:1<<15],
139 },
140 {
141 1, 1 << 15,
142 contentsE[1 : 1+1<<15],
143 },
144 {
145 int64(len(contentsE)) - 1000, 1000,
146 contentsE[len(contentsE)-1000:],
147 },
148 }
149
150 func TestOpenReadAt(t *testing.T) {
151 var buf [1 << 15]byte
152
153 test(
154 t,
155 true,
156 []testFn{
157 func(sfs FS) error {
158 a, err := sfs.Open("dirA/fileE")
159 if err != nil {
160 return fmt.Errorf("unexpected error opening file in squashfs FS: %w", err)
161 }
162
163 r, ok := a.(io.ReaderAt)
164 if !ok {
165 return fmt.Errorf("didn't get io.ReaderAt")
166 }
167
168 for n, test := range offsetReadTests {
169 m, err := r.ReadAt(buf[:test.Length], test.Start)
170 if err != nil {
171 return fmt.Errorf("test %d: %w", n+1, err)
172 }
173
174 if out := string(buf[:m]); out != test.Expectation {
175 return fmt.Errorf("test %d: expecting to read %q (%d), got %q (%d)", n+1, test.Expectation, len(test.Expectation), out, m)
176 }
177 }
178
179 return nil
180 },
181 },
182 dirData("dirA", []child{
183 fileData("fileE", contentsE),
184 }),
185 )
186 }
187
188 func TestSeek(t *testing.T) {
189 var buf [1 << 15]byte
190
191 test(
192 t,
193 true,
194 []testFn{
195 func(sfs FS) error {
196 a, err := sfs.Open("dirA/fileE")
197 if err != nil {
198 return fmt.Errorf("unexpected error opening file in squashfs FS: %w", err)
199 }
200
201 r, ok := a.(io.ReadSeeker)
202 if !ok {
203 return fmt.Errorf("didn't get io.ReaderAt")
204 }
205
206 for n, test := range offsetReadTests {
207 for s, seek := range [...]struct {
208 Offset int64
209 Whence int
210 }{
211 {
212 test.Start,
213 io.SeekStart,
214 },
215 {
216 -test.Length,
217 io.SeekCurrent,
218 },
219 {
220 test.Start - int64(len(contentsE)),
221 io.SeekEnd,
222 },
223 } {
224 p, err := r.Seek(seek.Offset, seek.Whence)
225 if err != nil {
226 return fmt.Errorf("test %d.%d: %w", n+1, s+1, err)
227 }
228
229 if p != test.Start {
230 return fmt.Errorf("test %d.%d: expecting to be at byte %d, actually at %d", n+1, s+1, test.Start, p)
231 }
232
233 m, err := r.Read(buf[:test.Length])
234 if err != nil {
235 return fmt.Errorf("test %d.%d: %w", n+1, s+1, err)
236 }
237
238 if out := string(buf[:m]); out != test.Expectation {
239 return fmt.Errorf("test %d.%d: expecting to read %q (%d), got %q (%d)", s+1, n+1, test.Expectation, len(test.Expectation), out, m)
240 }
241 }
242 }
243
244 return nil
245 },
246 },
247 dirData("dirA", []child{
248 fileData("fileE", contentsE),
249 }),
250 )
251 }
252
253 func TestStat(t *testing.T) {
254 test(
255 t,
256 false,
257 []testFn{
258 func(sfs FS) error {
259 stats, err := sfs.Stat(".")
260 if err != nil {
261 return fmt.Errorf("unexpected error stat'ing root: %w", err)
262 }
263
264 if !stats.IsDir() {
265 return fmt.Errorf("expecting stat for root dir to be a dir")
266 }
267
268 if m := stats.Mode(); m&0o777 != 0o777 {
269 return fmt.Errorf("expecting perms 777, got %s", m)
270 }
271
272 return nil
273 },
274 func(sfs FS) error {
275 stats, err := sfs.Stat("dirA")
276 if err != nil {
277 return fmt.Errorf("unexpected error stat'ing dir: %w", err)
278 }
279
280 if !stats.IsDir() {
281 return fmt.Errorf("expecting stat for dir to be a dir")
282 }
283
284 if m := stats.Mode(); m&0o555 != 0o555 {
285 return fmt.Errorf("expecting perms 555, got %s", m)
286 }
287
288 return nil
289 },
290 func(sfs FS) error {
291 stats, err := sfs.Stat("dirB/fileA")
292 if err != nil {
293 return fmt.Errorf("unexpected error stat'ing file: %w", err)
294 }
295
296 if stats.IsDir() {
297 return fmt.Errorf("expecting stat for file to be not a dir")
298 }
299
300 if size := stats.Size(); size != int64(len(contentsA)) {
301 return fmt.Errorf("expecting size for file to be %d, got %d", len(contentsA), size)
302 }
303
304 if m := stats.Mode(); m != 0o600 {
305 return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o777), m)
306 }
307
308 if time := stats.ModTime(); time.Sub(timestamp) != 0 {
309 return fmt.Errorf("expecting modtime %s, got %s", timestamp, time)
310 }
311
312 return nil
313 },
314 func(sfs FS) error {
315 stats, err := sfs.Stat("dirC/fileC")
316 if err != nil {
317 return fmt.Errorf("unexpected error stat'ing file: %w", err)
318 }
319
320 if m := stats.Mode(); m != 0o123 {
321 return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
322 }
323
324 return nil
325 },
326 func(sfs FS) error {
327 stats, err := sfs.Stat("dirD/fileD")
328 if err != nil {
329 return fmt.Errorf("unexpected error stat'ing file: %w", err)
330 }
331
332 if m := stats.Mode(); m != 0o123 {
333 return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
334 }
335
336 return nil
337 },
338 func(sfs FS) error {
339 stats, err := sfs.Stat("dirD/fileE")
340 if err != nil {
341 return fmt.Errorf("unexpected error stat'ing file: %w", err)
342 }
343
344 if m := stats.Mode(); m != 0o123 {
345 return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
346 }
347
348 return nil
349 },
350 func(sfs FS) error {
351 stats, err := sfs.LStat("dirD/fileE")
352 if err != nil {
353 return fmt.Errorf("unexpected error stat'ing file: %w", err)
354 }
355
356 expected := fs.ModeSymlink | fs.ModePerm
357
358 if m := stats.Mode(); m != expected {
359 return fmt.Errorf("expecting perms %s, got %s", expected, m)
360 }
361
362 return nil
363 },
364 func(sfs FS) error {
365 stats, err := sfs.Stat("dirE/fileB")
366 if err != nil {
367 return fmt.Errorf("unexpected error stat'ing file: %w", err)
368 }
369
370 if m := stats.Mode(); m != 0o123 {
371 return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
372 }
373
374 return nil
375 },
376 func(sfs FS) error {
377 stats, err := sfs.LStat("dirE/fileB")
378 if err != nil {
379 return fmt.Errorf("unexpected error stat'ing file: %w", err)
380 }
381
382 if m := stats.Mode(); m != 0o123 {
383 return fmt.Errorf("expecting perms %s, got %s", fs.FileMode(0o123), m)
384 }
385
386 return nil
387 },
388 },
389 dirData("dirA", []child{}, chmod(0o555)),
390 dirData("dirB", []child{
391 fileData("fileA", contentsA, chmod(0o600), modtime(timestamp)),
392 }),
393 dirData("dirC", []child{
394 fileData("fileB", contentsA, chmod(0o123)),
395 symlink("fileC", "fileB", chmod(0o321)),
396 }),
397 dirData("dirD", []child{
398 symlink("fileD", "../dirC/fileB", chmod(0o231)),
399 symlink("fileE", "/dirC/fileB", chmod(0o132)),
400 }),
401 symlink("dirE", "dirC"),
402 )
403 }
404
405 func TestReadDir(t *testing.T) {
406 test(
407 t,
408 true,
409 []testFn{
410 func(sfs FS) error {
411 entries, err := sfs.ReadDir("dirA")
412 if err != nil {
413 return err
414 }
415
416 if len(entries) != 0 {
417 return fmt.Errorf("expecting no entries, got %v", entries)
418 }
419
420 return nil
421 },
422 func(sfs FS) error {
423 entries, err := sfs.ReadDir("dirB")
424 if err != nil {
425 return err
426 }
427
428 if len(entries) != 1 {
429 return fmt.Errorf("expecting 1 entry, got %d", len(entries))
430 }
431
432 if name := entries[0].Name(); name != "childA" {
433 return fmt.Errorf("expecting entry to be %q, got %q", "childA", name)
434 }
435
436 return nil
437 },
438 func(sfs FS) error {
439 entries, err := sfs.ReadDir("dirC")
440 if err != nil {
441 return err
442 }
443
444 if len(entries) != 3 {
445 return fmt.Errorf("expecting 3 entries, got %d", len(entries))
446 }
447
448 for n, child := range [...]string{"childA", "childB", "childC"} {
449 if name := entries[n].Name(); name != child {
450 return fmt.Errorf("expecting entry %d to be %q, got %q", n+1, child, name)
451 }
452 }
453
454 return nil
455 },
456 },
457 dirData("dirA", []child{}),
458 dirData("dirB", []child{
459 fileData("childA", ""),
460 }),
461 dirData("dirC", []child{
462 dirData("childA", []child{}),
463 fileData("childB", ""),
464 symlink("childC", "childB"),
465 }, chmod(0o321)),
466 )
467 }
468