1
16
17
22
23 package estargz
24
25 import (
26 "archive/tar"
27 "bytes"
28 "compress/gzip"
29 "fmt"
30 "io"
31 "reflect"
32 "testing"
33 )
34
35 func TestSort(t *testing.T) {
36 longname1 := longstring(120)
37 longname2 := longstring(150)
38
39 tests := []struct {
40 name string
41 in []tarEntry
42 log []string
43 want []tarEntry
44 wantFail bool
45 allowMissedFiles []string
46 }{
47 {
48 name: "nolog",
49 in: tarOf(
50 file("foo.txt", "foo"),
51 dir("bar/"),
52 file("bar/baz.txt", "baz"),
53 file("bar/bar.txt", "bar"),
54 ),
55 want: tarOf(
56 noPrefetchLandmark(),
57 file("foo.txt", "foo"),
58 dir("bar/"),
59 file("bar/baz.txt", "baz"),
60 file("bar/bar.txt", "bar"),
61 ),
62 },
63 {
64 name: "identical",
65 in: tarOf(
66 file("foo.txt", "foo"),
67 dir("bar/"),
68 file("bar/baz.txt", "baz"),
69 file("bar/bar.txt", "bar"),
70 file("bar/baa.txt", "baa"),
71 ),
72 log: []string{"foo.txt", "bar/baz.txt"},
73 want: tarOf(
74 file("foo.txt", "foo"),
75 dir("bar/"),
76 file("bar/baz.txt", "baz"),
77 prefetchLandmark(),
78 file("bar/bar.txt", "bar"),
79 file("bar/baa.txt", "baa"),
80 ),
81 },
82 {
83 name: "shuffle_reg",
84 in: tarOf(
85 file("foo.txt", "foo"),
86 file("baz.txt", "baz"),
87 file("bar.txt", "bar"),
88 file("baa.txt", "baa"),
89 ),
90 log: []string{"baa.txt", "bar.txt", "baz.txt"},
91 want: tarOf(
92 file("baa.txt", "baa"),
93 file("bar.txt", "bar"),
94 file("baz.txt", "baz"),
95 prefetchLandmark(),
96 file("foo.txt", "foo"),
97 ),
98 },
99 {
100 name: "shuffle_directory",
101 in: tarOf(
102 file("foo.txt", "foo"),
103 dir("bar/"),
104 file("bar/bar.txt", "bar"),
105 file("bar/baa.txt", "baa"),
106 dir("baz/"),
107 file("baz/baz1.txt", "baz"),
108 file("baz/baz2.txt", "baz"),
109 dir("baz/bazbaz/"),
110 file("baz/bazbaz/bazbaz_b.txt", "baz"),
111 file("baz/bazbaz/bazbaz_a.txt", "baz"),
112 ),
113 log: []string{"baz/bazbaz/bazbaz_a.txt", "baz/baz2.txt", "foo.txt"},
114 want: tarOf(
115 dir("baz/"),
116 dir("baz/bazbaz/"),
117 file("baz/bazbaz/bazbaz_a.txt", "baz"),
118 file("baz/baz2.txt", "baz"),
119 file("foo.txt", "foo"),
120 prefetchLandmark(),
121 dir("bar/"),
122 file("bar/bar.txt", "bar"),
123 file("bar/baa.txt", "baa"),
124 file("baz/baz1.txt", "baz"),
125 file("baz/bazbaz/bazbaz_b.txt", "baz"),
126 ),
127 },
128 {
129 name: "shuffle_link",
130 in: tarOf(
131 file("foo.txt", "foo"),
132 file("baz.txt", "baz"),
133 link("bar.txt", "baz.txt"),
134 file("baa.txt", "baa"),
135 ),
136 log: []string{"baz.txt"},
137 want: tarOf(
138 file("baz.txt", "baz"),
139 prefetchLandmark(),
140 file("foo.txt", "foo"),
141 link("bar.txt", "baz.txt"),
142 file("baa.txt", "baa"),
143 ),
144 },
145 {
146 name: "longname",
147 in: tarOf(
148 file("foo.txt", "foo"),
149 file(longname1, "test"),
150 dir("bar/"),
151 file("bar/bar.txt", "bar"),
152 file("bar/baa.txt", "baa"),
153 file(fmt.Sprintf("bar/%s", longname2), "test2"),
154 ),
155 log: []string{fmt.Sprintf("bar/%s", longname2), longname1},
156 want: tarOf(
157 dir("bar/"),
158 file(fmt.Sprintf("bar/%s", longname2), "test2"),
159 file(longname1, "test"),
160 prefetchLandmark(),
161 file("foo.txt", "foo"),
162 file("bar/bar.txt", "bar"),
163 file("bar/baa.txt", "baa"),
164 ),
165 },
166 {
167 name: "various_types",
168 in: tarOf(
169 file("foo.txt", "foo"),
170 symlink("foo2", "foo.txt"),
171 chardev("foochar", 10, 50),
172 blockdev("fooblock", 15, 20),
173 fifo("fifoo"),
174 ),
175 log: []string{"fifoo", "foo2", "foo.txt", "fooblock"},
176 want: tarOf(
177 fifo("fifoo"),
178 symlink("foo2", "foo.txt"),
179 file("foo.txt", "foo"),
180 blockdev("fooblock", 15, 20),
181 prefetchLandmark(),
182 chardev("foochar", 10, 50),
183 ),
184 },
185 {
186 name: "existing_landmark",
187 in: tarOf(
188 file("baa.txt", "baa"),
189 file("bar.txt", "bar"),
190 file("baz.txt", "baz"),
191 prefetchLandmark(),
192 file("foo.txt", "foo"),
193 ),
194 log: []string{"foo.txt", "bar.txt"},
195 want: tarOf(
196 file("foo.txt", "foo"),
197 file("bar.txt", "bar"),
198 prefetchLandmark(),
199 file("baa.txt", "baa"),
200 file("baz.txt", "baz"),
201 ),
202 },
203 {
204 name: "existing_landmark_nolog",
205 in: tarOf(
206 file("baa.txt", "baa"),
207 file("bar.txt", "bar"),
208 file("baz.txt", "baz"),
209 prefetchLandmark(),
210 file("foo.txt", "foo"),
211 ),
212 want: tarOf(
213 noPrefetchLandmark(),
214 file("baa.txt", "baa"),
215 file("bar.txt", "bar"),
216 file("baz.txt", "baz"),
217 file("foo.txt", "foo"),
218 ),
219 },
220 {
221 name: "existing_noprefetch_landmark",
222 in: tarOf(
223 noPrefetchLandmark(),
224 file("baa.txt", "baa"),
225 file("bar.txt", "bar"),
226 file("baz.txt", "baz"),
227 file("foo.txt", "foo"),
228 ),
229 log: []string{"foo.txt", "bar.txt"},
230 want: tarOf(
231 file("foo.txt", "foo"),
232 file("bar.txt", "bar"),
233 prefetchLandmark(),
234 file("baa.txt", "baa"),
235 file("baz.txt", "baz"),
236 ),
237 },
238 {
239 name: "existing_noprefetch_landmark_nolog",
240 in: tarOf(
241 noPrefetchLandmark(),
242 file("baa.txt", "baa"),
243 file("bar.txt", "bar"),
244 file("baz.txt", "baz"),
245 file("foo.txt", "foo"),
246 ),
247 want: tarOf(
248 noPrefetchLandmark(),
249 file("baa.txt", "baa"),
250 file("bar.txt", "bar"),
251 file("baz.txt", "baz"),
252 file("foo.txt", "foo"),
253 ),
254 },
255 {
256 name: "not_existing_file",
257 in: tarOf(
258 file("foo.txt", "foo"),
259 file("baz.txt", "baz"),
260 file("bar.txt", "bar"),
261 file("baa.txt", "baa"),
262 ),
263 log: []string{"baa.txt", "bar.txt", "dummy"},
264 want: tarOf(),
265 wantFail: true,
266 },
267 {
268 name: "not_existing_file_allow_fail",
269 in: tarOf(
270 file("foo.txt", "foo"),
271 file("baz.txt", "baz"),
272 file("bar.txt", "bar"),
273 file("baa.txt", "baa"),
274 ),
275 log: []string{"baa.txt", "dummy1", "bar.txt", "dummy2"},
276 want: tarOf(
277 file("baa.txt", "baa"),
278 file("bar.txt", "bar"),
279 prefetchLandmark(),
280 file("foo.txt", "foo"),
281 file("baz.txt", "baz"),
282 ),
283 allowMissedFiles: []string{"dummy1", "dummy2"},
284 },
285 {
286 name: "duplicated_entry",
287 in: tarOf(
288 file("foo.txt", "foo"),
289 dir("bar/"),
290 file("bar/baz.txt", "baz"),
291 dir("bar/"),
292 file("bar/baz.txt", "baz2"),
293 ),
294 log: []string{"bar/baz.txt"},
295 want: tarOf(
296 dir("bar/"),
297 file("bar/baz.txt", "baz2"),
298 prefetchLandmark(),
299 file("foo.txt", "foo"),
300 ),
301 },
302 {
303 name: "hardlink",
304 in: tarOf(
305 file("baz.txt", "aaaaa"),
306 link("bazlink", "baz.txt"),
307 ),
308 log: []string{"bazlink"},
309 want: tarOf(
310 file("baz.txt", "aaaaa"),
311 link("bazlink", "baz.txt"),
312 prefetchLandmark(),
313 ),
314 },
315 {
316 name: "root_relative_file",
317 in: tarOf(
318 dir("./"),
319 file("./foo.txt", "foo"),
320 file("./baz.txt", "baz"),
321 file("./baa.txt", "baa"),
322 link("./bazlink", "./baz.txt"),
323 ),
324 log: []string{"baa.txt", "bazlink"},
325 want: tarOf(
326 dir("./"),
327 file("./baa.txt", "baa"),
328 file("./baz.txt", "baz"),
329 link("./bazlink", "./baz.txt"),
330 prefetchLandmark(),
331 file("./foo.txt", "foo"),
332 ),
333 },
334 {
335
336 name: "root_absolute_file",
337 in: tarOf(
338 dir("/"),
339 file("/foo.txt", "foo"),
340 file("/baz.txt", "baz"),
341 file("/baa.txt", "baa"),
342 link("/bazlink", "/baz.txt"),
343 ),
344 log: []string{"baa.txt", "bazlink"},
345 want: tarOf(
346 dir("/"),
347 file("/baa.txt", "baa"),
348 file("/baz.txt", "baz"),
349 link("/bazlink", "/baz.txt"),
350 prefetchLandmark(),
351 file("/foo.txt", "foo"),
352 ),
353 },
354 }
355 for _, tt := range tests {
356 for _, srcCompression := range srcCompressions {
357 srcCompression := srcCompression
358 for _, logprefix := range allowedPrefix {
359 logprefix := logprefix
360 for _, tarprefix := range allowedPrefix {
361 tarprefix := tarprefix
362 t.Run(fmt.Sprintf("%s-logprefix=%q-tarprefix=%q-src=%d", tt.name, logprefix, tarprefix, srcCompression), func(t *testing.T) {
363
364 var pfiles []string
365 for _, f := range tt.log {
366 pfiles = append(pfiles, logprefix+f)
367 }
368 var opts []Option
369 var missedFiles []string
370 if tt.allowMissedFiles != nil {
371 opts = append(opts, WithAllowPrioritizeNotFound(&missedFiles))
372 }
373 rc, err := Build(compressBlob(t, buildTar(t, tt.in, tarprefix), srcCompression),
374 append(opts, WithPrioritizedFiles(pfiles))...)
375 if tt.wantFail {
376 if err != nil {
377 return
378 }
379 t.Errorf("This test must fail but succeeded")
380 return
381 }
382 if err != nil {
383 t.Errorf("failed to build stargz: %v", err)
384 }
385 defer rc.Close()
386 zr, err := gzip.NewReader(rc)
387 if err != nil {
388 t.Fatalf("failed to create gzip reader: %v", err)
389 }
390 if tt.allowMissedFiles != nil {
391 want := map[string]struct{}{}
392 for _, f := range tt.allowMissedFiles {
393 want[logprefix+f] = struct{}{}
394 }
395 got := map[string]struct{}{}
396 for _, f := range missedFiles {
397 got[f] = struct{}{}
398 }
399 if !reflect.DeepEqual(got, want) {
400 t.Errorf("unexpected missed files: want %v, got: %v",
401 want, got)
402 return
403 }
404 }
405 gotTar := tar.NewReader(zr)
406
407
408 wantTar := tar.NewReader(buildTar(t, tt.want, tarprefix))
409 for {
410
411 gotH, wantH, err := next(t, gotTar, wantTar)
412 if err != nil {
413 if err == io.EOF {
414 break
415 } else {
416 t.Fatalf("Failed to parse tar file: %v", err)
417 }
418 }
419
420 if !reflect.DeepEqual(gotH, wantH) {
421 t.Errorf("different header (got = name:%q,type:%d,size:%d; want = name:%q,type:%d,size:%d)",
422 gotH.Name, gotH.Typeflag, gotH.Size, wantH.Name, wantH.Typeflag, wantH.Size)
423 return
424
425 }
426
427 got, err := io.ReadAll(gotTar)
428 if err != nil {
429 t.Fatal("failed to read got tar payload")
430 }
431 want, err := io.ReadAll(wantTar)
432 if err != nil {
433 t.Fatal("failed to read want tar payload")
434 }
435 if !bytes.Equal(got, want) {
436 t.Errorf("different payload (got = %q; want = %q)", string(got), string(want))
437 return
438 }
439 }
440 })
441 }
442 }
443 }
444 }
445 }
446
447 func next(t *testing.T, a *tar.Reader, b *tar.Reader) (ah *tar.Header, bh *tar.Header, err error) {
448 eofA, eofB := false, false
449
450 ah, err = nextWithSkipTOC(a)
451 if err != nil {
452 if err == io.EOF {
453 eofA = true
454 } else {
455 t.Fatalf("Failed to parse tar file: %q", err)
456 }
457 }
458
459 bh, err = nextWithSkipTOC(b)
460 if err != nil {
461 if err == io.EOF {
462 eofB = true
463 } else {
464 t.Fatalf("Failed to parse tar file: %q", err)
465 }
466 }
467
468 if eofA != eofB {
469 if !eofA {
470 t.Logf("a = %q", ah.Name)
471 }
472 if !eofB {
473 t.Logf("b = %q", bh.Name)
474 }
475 t.Fatalf("got eof %t != %t", eofB, eofA)
476 }
477 if eofA {
478 err = io.EOF
479 }
480
481 return
482 }
483
484 func nextWithSkipTOC(a *tar.Reader) (h *tar.Header, err error) {
485 if h, err = a.Next(); err != nil {
486 return
487 }
488 if h.Name == TOCTarName {
489 return nextWithSkipTOC(a)
490 }
491 return
492 }
493
494 func longstring(size int) (str string) {
495 unit := "long"
496 for i := 0; i < size/len(unit)+1; i++ {
497 str = fmt.Sprintf("%s%s", str, unit)
498 }
499
500 return str[:size]
501 }
502
503 func TestCountReader(t *testing.T) {
504 tests := []struct {
505 name string
506 ops func(*countReadSeeker) error
507 wantPos int64
508 }{
509 {
510 name: "nop",
511 ops: func(pw *countReadSeeker) error {
512 return nil
513 },
514 wantPos: 0,
515 },
516 {
517 name: "read",
518 ops: func(pw *countReadSeeker) error {
519 size := 5
520 if _, err := pw.Read(make([]byte, size)); err != nil {
521 return err
522 }
523 return nil
524 },
525 wantPos: 5,
526 },
527 {
528 name: "readtwice",
529 ops: func(pw *countReadSeeker) error {
530 size1, size2 := 5, 3
531 if _, err := pw.Read(make([]byte, size1)); err != nil {
532 if err != io.EOF {
533 return err
534 }
535 }
536 if _, err := pw.Read(make([]byte, size2)); err != nil {
537 if err != io.EOF {
538 return err
539 }
540 }
541 return nil
542 },
543 wantPos: 8,
544 },
545 {
546 name: "seek_start",
547 ops: func(pw *countReadSeeker) error {
548 size := int64(5)
549 if _, err := pw.Seek(size, io.SeekStart); err != nil {
550 if err != io.EOF {
551 return err
552 }
553 }
554 return nil
555 },
556 wantPos: 5,
557 },
558 {
559 name: "seek_start_twice",
560 ops: func(pw *countReadSeeker) error {
561 size1, size2 := int64(5), int64(3)
562 if _, err := pw.Seek(size1, io.SeekStart); err != nil {
563 if err != io.EOF {
564 return err
565 }
566 }
567 if _, err := pw.Seek(size2, io.SeekStart); err != nil {
568 if err != io.EOF {
569 return err
570 }
571 }
572 return nil
573 },
574 wantPos: 3,
575 },
576 {
577 name: "seek_current",
578 ops: func(pw *countReadSeeker) error {
579 size := int64(5)
580 if _, err := pw.Seek(size, io.SeekCurrent); err != nil {
581 if err != io.EOF {
582 return err
583 }
584 }
585 return nil
586 },
587 wantPos: 5,
588 },
589 {
590 name: "seek_current_twice",
591 ops: func(pw *countReadSeeker) error {
592 size1, size2 := int64(5), int64(3)
593 if _, err := pw.Seek(size1, io.SeekCurrent); err != nil {
594 if err != io.EOF {
595 return err
596 }
597 }
598 if _, err := pw.Seek(size2, io.SeekCurrent); err != nil {
599 if err != io.EOF {
600 return err
601 }
602 }
603 return nil
604 },
605 wantPos: 8,
606 },
607 {
608 name: "seek_current_twice_negative",
609 ops: func(pw *countReadSeeker) error {
610 size1, size2 := int64(5), int64(-3)
611 if _, err := pw.Seek(size1, io.SeekCurrent); err != nil {
612 if err != io.EOF {
613 return err
614 }
615 }
616 if _, err := pw.Seek(size2, io.SeekCurrent); err != nil {
617 if err != io.EOF {
618 return err
619 }
620 }
621 return nil
622 },
623 wantPos: 2,
624 },
625 {
626 name: "mixed",
627 ops: func(pw *countReadSeeker) error {
628 size1, size2, size3, size4, size5 := int64(5), int64(-3), int64(4), int64(-1), int64(6)
629 if _, err := pw.Read(make([]byte, size1)); err != nil {
630 if err != io.EOF {
631 return err
632 }
633 }
634 if _, err := pw.Seek(size2, io.SeekCurrent); err != nil {
635 if err != io.EOF {
636 return err
637 }
638 }
639 if _, err := pw.Seek(size3, io.SeekStart); err != nil {
640 if err != io.EOF {
641 return err
642 }
643 }
644 if _, err := pw.Seek(size4, io.SeekCurrent); err != nil {
645 if err != io.EOF {
646 return err
647 }
648 }
649 if _, err := pw.Read(make([]byte, size5)); err != nil {
650 if err != io.EOF {
651 return err
652 }
653 }
654 return nil
655 },
656 wantPos: 9,
657 },
658 }
659
660 for _, tt := range tests {
661 t.Run(tt.name, func(t *testing.T) {
662 pw, err := newCountReadSeeker(bytes.NewReader(make([]byte, 100)))
663 if err != nil {
664 t.Fatalf("failed to make position watcher: %q", err)
665 }
666
667 if err := tt.ops(pw); err != nil {
668 t.Fatalf("failed to operate position watcher: %q", err)
669 }
670
671 gotPos := pw.currentPos()
672 if tt.wantPos != gotPos {
673 t.Errorf("current position: %d; want %d", gotPos, tt.wantPos)
674 }
675 })
676 }
677
678 }
679
View as plain text