1
2
3
4
5 package tar
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "io"
12 "math"
13 "os"
14 "path"
15 "path/filepath"
16 "reflect"
17 "runtime"
18 "strings"
19 "testing"
20 "time"
21 )
22
23 type testError struct{ error }
24
25 type fileOps []interface{}
26
27
28
29 type testFile struct {
30 ops fileOps
31 pos int64
32 }
33
34 func (f *testFile) Read(b []byte) (int, error) {
35 if len(b) == 0 {
36 return 0, nil
37 }
38 if len(f.ops) == 0 {
39 return 0, io.EOF
40 }
41 s, ok := f.ops[0].(string)
42 if !ok {
43 return 0, errors.New("unexpected Read operation")
44 }
45
46 n := copy(b, s)
47 if len(s) > n {
48 f.ops[0] = s[n:]
49 } else {
50 f.ops = f.ops[1:]
51 }
52 f.pos += int64(len(b))
53 return n, nil
54 }
55
56 func (f *testFile) Write(b []byte) (int, error) {
57 if len(b) == 0 {
58 return 0, nil
59 }
60 if len(f.ops) == 0 {
61 return 0, errors.New("unexpected Write operation")
62 }
63 s, ok := f.ops[0].(string)
64 if !ok {
65 return 0, errors.New("unexpected Write operation")
66 }
67
68 if !strings.HasPrefix(s, string(b)) {
69 return 0, testError{fmt.Errorf("got Write(%q), want Write(%q)", b, s)}
70 }
71 if len(s) > len(b) {
72 f.ops[0] = s[len(b):]
73 } else {
74 f.ops = f.ops[1:]
75 }
76 f.pos += int64(len(b))
77 return len(b), nil
78 }
79
80 func (f *testFile) Seek(pos int64, whence int) (int64, error) {
81 if pos == 0 && whence == io.SeekCurrent {
82 return f.pos, nil
83 }
84 if len(f.ops) == 0 {
85 return 0, errors.New("unexpected Seek operation")
86 }
87 s, ok := f.ops[0].(int64)
88 if !ok {
89 return 0, errors.New("unexpected Seek operation")
90 }
91
92 if s != pos || whence != io.SeekCurrent {
93 return 0, testError{fmt.Errorf("got Seek(%d, %d), want Seek(%d, %d)", pos, whence, s, io.SeekCurrent)}
94 }
95 f.pos += s
96 f.ops = f.ops[1:]
97 return f.pos, nil
98 }
99
100 func equalSparseEntries(x, y []sparseEntry) bool {
101 return (len(x) == 0 && len(y) == 0) || reflect.DeepEqual(x, y)
102 }
103
104 func TestSparseEntries(t *testing.T) {
105 vectors := []struct {
106 in []sparseEntry
107 size int64
108
109 wantValid bool
110 wantAligned []sparseEntry
111 wantInverted []sparseEntry
112 }{{
113 in: []sparseEntry{}, size: 0,
114 wantValid: true,
115 wantInverted: []sparseEntry{{0, 0}},
116 }, {
117 in: []sparseEntry{}, size: 5000,
118 wantValid: true,
119 wantInverted: []sparseEntry{{0, 5000}},
120 }, {
121 in: []sparseEntry{{0, 5000}}, size: 5000,
122 wantValid: true,
123 wantAligned: []sparseEntry{{0, 5000}},
124 wantInverted: []sparseEntry{{5000, 0}},
125 }, {
126 in: []sparseEntry{{1000, 4000}}, size: 5000,
127 wantValid: true,
128 wantAligned: []sparseEntry{{1024, 3976}},
129 wantInverted: []sparseEntry{{0, 1000}, {5000, 0}},
130 }, {
131 in: []sparseEntry{{0, 3000}}, size: 5000,
132 wantValid: true,
133 wantAligned: []sparseEntry{{0, 2560}},
134 wantInverted: []sparseEntry{{3000, 2000}},
135 }, {
136 in: []sparseEntry{{3000, 2000}}, size: 5000,
137 wantValid: true,
138 wantAligned: []sparseEntry{{3072, 1928}},
139 wantInverted: []sparseEntry{{0, 3000}, {5000, 0}},
140 }, {
141 in: []sparseEntry{{2000, 2000}}, size: 5000,
142 wantValid: true,
143 wantAligned: []sparseEntry{{2048, 1536}},
144 wantInverted: []sparseEntry{{0, 2000}, {4000, 1000}},
145 }, {
146 in: []sparseEntry{{0, 2000}, {8000, 2000}}, size: 10000,
147 wantValid: true,
148 wantAligned: []sparseEntry{{0, 1536}, {8192, 1808}},
149 wantInverted: []sparseEntry{{2000, 6000}, {10000, 0}},
150 }, {
151 in: []sparseEntry{{0, 2000}, {2000, 2000}, {4000, 0}, {4000, 3000}, {7000, 1000}, {8000, 0}, {8000, 2000}}, size: 10000,
152 wantValid: true,
153 wantAligned: []sparseEntry{{0, 1536}, {2048, 1536}, {4096, 2560}, {7168, 512}, {8192, 1808}},
154 wantInverted: []sparseEntry{{10000, 0}},
155 }, {
156 in: []sparseEntry{{0, 0}, {1000, 0}, {2000, 0}, {3000, 0}, {4000, 0}, {5000, 0}}, size: 5000,
157 wantValid: true,
158 wantInverted: []sparseEntry{{0, 5000}},
159 }, {
160 in: []sparseEntry{{1, 0}}, size: 0,
161 wantValid: false,
162 }, {
163 in: []sparseEntry{{-1, 0}}, size: 100,
164 wantValid: false,
165 }, {
166 in: []sparseEntry{{0, -1}}, size: 100,
167 wantValid: false,
168 }, {
169 in: []sparseEntry{{0, 0}}, size: -100,
170 wantValid: false,
171 }, {
172 in: []sparseEntry{{math.MaxInt64, 3}, {6, -5}}, size: 35,
173 wantValid: false,
174 }, {
175 in: []sparseEntry{{1, 3}, {6, -5}}, size: 35,
176 wantValid: false,
177 }, {
178 in: []sparseEntry{{math.MaxInt64, math.MaxInt64}}, size: math.MaxInt64,
179 wantValid: false,
180 }, {
181 in: []sparseEntry{{3, 3}}, size: 5,
182 wantValid: false,
183 }, {
184 in: []sparseEntry{{2, 0}, {1, 0}, {0, 0}}, size: 3,
185 wantValid: false,
186 }, {
187 in: []sparseEntry{{1, 3}, {2, 2}}, size: 10,
188 wantValid: false,
189 }}
190
191 for i, v := range vectors {
192 gotValid := validateSparseEntries(v.in, v.size)
193 if gotValid != v.wantValid {
194 t.Errorf("test %d, validateSparseEntries() = %v, want %v", i, gotValid, v.wantValid)
195 }
196 if !v.wantValid {
197 continue
198 }
199 gotAligned := alignSparseEntries(append([]sparseEntry{}, v.in...), v.size)
200 if !equalSparseEntries(gotAligned, v.wantAligned) {
201 t.Errorf("test %d, alignSparseEntries():\ngot %v\nwant %v", i, gotAligned, v.wantAligned)
202 }
203 gotInverted := invertSparseEntries(append([]sparseEntry{}, v.in...), v.size)
204 if !equalSparseEntries(gotInverted, v.wantInverted) {
205 t.Errorf("test %d, inverseSparseEntries():\ngot %v\nwant %v", i, gotInverted, v.wantInverted)
206 }
207 }
208 }
209
210 func TestFileInfoHeader(t *testing.T) {
211 fi, err := os.Stat("testdata/small.txt")
212 if err != nil {
213 t.Fatal(err)
214 }
215 h, err := FileInfoHeader(fi, "")
216 if err != nil {
217 t.Fatalf("FileInfoHeader: %v", err)
218 }
219 if g, e := h.Name, "small.txt"; g != e {
220 t.Errorf("Name = %q; want %q", g, e)
221 }
222 if g, e := h.Mode, int64(fi.Mode().Perm()); g != e {
223 t.Errorf("Mode = %#o; want %#o", g, e)
224 }
225 if g, e := h.Size, int64(5); g != e {
226 t.Errorf("Size = %v; want %v", g, e)
227 }
228 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
229 t.Errorf("ModTime = %v; want %v", g, e)
230 }
231
232 if _, err := FileInfoHeader(nil, ""); err == nil {
233 t.Fatalf("Expected error when passing nil to FileInfoHeader")
234 }
235 }
236
237 func TestFileInfoHeaderDir(t *testing.T) {
238 fi, err := os.Stat("testdata")
239 if err != nil {
240 t.Fatal(err)
241 }
242 h, err := FileInfoHeader(fi, "")
243 if err != nil {
244 t.Fatalf("FileInfoHeader: %v", err)
245 }
246 if g, e := h.Name, "testdata/"; g != e {
247 t.Errorf("Name = %q; want %q", g, e)
248 }
249
250 if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e {
251 t.Errorf("Mode = %#o; want %#o", g, e)
252 }
253 if g, e := h.Size, int64(0); g != e {
254 t.Errorf("Size = %v; want %v", g, e)
255 }
256 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
257 t.Errorf("ModTime = %v; want %v", g, e)
258 }
259 }
260
261 func TestFileInfoHeaderSymlink(t *testing.T) {
262 switch runtime.GOOS {
263 case "android", "nacl", "plan9", "windows":
264 t.Skip("symlinks not supported")
265 }
266 tmpdir, err := os.MkdirTemp("", "TestFileInfoHeaderSymlink")
267 if err != nil {
268 t.Fatal(err)
269 }
270 defer os.RemoveAll(tmpdir)
271
272 link := filepath.Join(tmpdir, "link")
273 target := tmpdir
274 err = os.Symlink(target, link)
275 if err != nil {
276 t.Fatal(err)
277 }
278 fi, err := os.Lstat(link)
279 if err != nil {
280 t.Fatal(err)
281 }
282
283 h, err := FileInfoHeader(fi, target)
284 if err != nil {
285 t.Fatal(err)
286 }
287 if g, e := h.Name, fi.Name(); g != e {
288 t.Errorf("Name = %q; want %q", g, e)
289 }
290 if g, e := h.Linkname, target; g != e {
291 t.Errorf("Linkname = %q; want %q", g, e)
292 }
293 if g, e := h.Typeflag, byte(TypeSymlink); g != e {
294 t.Errorf("Typeflag = %v; want %v", g, e)
295 }
296 }
297
298 func TestRoundTrip(t *testing.T) {
299 data := []byte("some file contents")
300
301 var b bytes.Buffer
302 tw := NewWriter(&b)
303 hdr := &Header{
304 Name: "file.txt",
305 Uid: 1 << 21,
306 Size: int64(len(data)),
307 ModTime: time.Now().Round(time.Second),
308 PAXRecords: map[string]string{"uid": "2097152"},
309 Format: FormatPAX,
310 Typeflag: TypeReg,
311 }
312 if err := tw.WriteHeader(hdr); err != nil {
313 t.Fatalf("tw.WriteHeader: %v", err)
314 }
315 if _, err := tw.Write(data); err != nil {
316 t.Fatalf("tw.Write: %v", err)
317 }
318 if err := tw.Close(); err != nil {
319 t.Fatalf("tw.Close: %v", err)
320 }
321
322
323 tr := NewReader(&b)
324 rHdr, err := tr.Next()
325 if err != nil {
326 t.Fatalf("tr.Next: %v", err)
327 }
328 if !reflect.DeepEqual(rHdr, hdr) {
329 t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
330 }
331 rData, err := io.ReadAll(tr)
332 if err != nil {
333 t.Fatalf("Read: %v", err)
334 }
335 if !bytes.Equal(rData, data) {
336 t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
337 }
338 }
339
340 type headerRoundTripTest struct {
341 h *Header
342 fm os.FileMode
343 }
344
345 func TestHeaderRoundTrip(t *testing.T) {
346 vectors := []headerRoundTripTest{{
347
348 h: &Header{
349 Name: "test.txt",
350 Mode: 0644,
351 Size: 12,
352 ModTime: time.Unix(1360600916, 0),
353 Typeflag: TypeReg,
354 },
355 fm: 0644,
356 }, {
357
358 h: &Header{
359 Name: "link.txt",
360 Mode: 0777,
361 Size: 0,
362 ModTime: time.Unix(1360600852, 0),
363 Typeflag: TypeSymlink,
364 },
365 fm: 0777 | os.ModeSymlink,
366 }, {
367
368 h: &Header{
369 Name: "dev/null",
370 Mode: 0666,
371 Size: 0,
372 ModTime: time.Unix(1360578951, 0),
373 Typeflag: TypeChar,
374 },
375 fm: 0666 | os.ModeDevice | os.ModeCharDevice,
376 }, {
377
378 h: &Header{
379 Name: "dev/sda",
380 Mode: 0660,
381 Size: 0,
382 ModTime: time.Unix(1360578954, 0),
383 Typeflag: TypeBlock,
384 },
385 fm: 0660 | os.ModeDevice,
386 }, {
387
388 h: &Header{
389 Name: "dir/",
390 Mode: 0755,
391 Size: 0,
392 ModTime: time.Unix(1360601116, 0),
393 Typeflag: TypeDir,
394 },
395 fm: 0755 | os.ModeDir,
396 }, {
397
398 h: &Header{
399 Name: "dev/initctl",
400 Mode: 0600,
401 Size: 0,
402 ModTime: time.Unix(1360578949, 0),
403 Typeflag: TypeFifo,
404 },
405 fm: 0600 | os.ModeNamedPipe,
406 }, {
407
408 h: &Header{
409 Name: "bin/su",
410 Mode: 0755 | c_ISUID,
411 Size: 23232,
412 ModTime: time.Unix(1355405093, 0),
413 Typeflag: TypeReg,
414 },
415 fm: 0755 | os.ModeSetuid,
416 }, {
417
418 h: &Header{
419 Name: "group.txt",
420 Mode: 0750 | c_ISGID,
421 Size: 0,
422 ModTime: time.Unix(1360602346, 0),
423 Typeflag: TypeReg,
424 },
425 fm: 0750 | os.ModeSetgid,
426 }, {
427
428 h: &Header{
429 Name: "sticky.txt",
430 Mode: 0600 | c_ISVTX,
431 Size: 7,
432 ModTime: time.Unix(1360602540, 0),
433 Typeflag: TypeReg,
434 },
435 fm: 0600 | os.ModeSticky,
436 }, {
437
438 h: &Header{
439 Name: "hard.txt",
440 Mode: 0644,
441 Size: 0,
442 Linkname: "file.txt",
443 ModTime: time.Unix(1360600916, 0),
444 Typeflag: TypeLink,
445 },
446 fm: 0644,
447 }, {
448
449 h: &Header{
450 Name: "info.txt",
451 Mode: 0600,
452 Size: 0,
453 Uid: 1000,
454 Gid: 1000,
455 ModTime: time.Unix(1360602540, 0),
456 Uname: "slartibartfast",
457 Gname: "users",
458 Typeflag: TypeReg,
459 },
460 fm: 0600,
461 }}
462
463 for i, v := range vectors {
464 fi := v.h.FileInfo()
465 h2, err := FileInfoHeader(fi, "")
466 if err != nil {
467 t.Error(err)
468 continue
469 }
470 if strings.Contains(fi.Name(), "/") {
471 t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
472 }
473 name := path.Base(v.h.Name)
474 if fi.IsDir() {
475 name += "/"
476 }
477 if got, want := h2.Name, name; got != want {
478 t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
479 }
480 if got, want := h2.Size, v.h.Size; got != want {
481 t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
482 }
483 if got, want := h2.Uid, v.h.Uid; got != want {
484 t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
485 }
486 if got, want := h2.Gid, v.h.Gid; got != want {
487 t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
488 }
489 if got, want := h2.Uname, v.h.Uname; got != want {
490 t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
491 }
492 if got, want := h2.Gname, v.h.Gname; got != want {
493 t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
494 }
495 if got, want := h2.Linkname, v.h.Linkname; got != want {
496 t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
497 }
498 if got, want := h2.Typeflag, v.h.Typeflag; got != want {
499 t.Logf("%#v %#v", v.h, fi.Sys())
500 t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
501 }
502 if got, want := h2.Mode, v.h.Mode; got != want {
503 t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
504 }
505 if got, want := fi.Mode(), v.fm; got != want {
506 t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
507 }
508 if got, want := h2.AccessTime, v.h.AccessTime; got != want {
509 t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
510 }
511 if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
512 t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
513 }
514 if got, want := h2.ModTime, v.h.ModTime; got != want {
515 t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
516 }
517 if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
518 t.Errorf("i=%d: Sys didn't return original *Header", i)
519 }
520 }
521 }
522
523 func TestHeaderAllowedFormats(t *testing.T) {
524 vectors := []struct {
525 header *Header
526 paxHdrs map[string]string
527 formats Format
528 }{{
529 header: &Header{},
530 formats: FormatUSTAR | FormatPAX | FormatGNU,
531 }, {
532 header: &Header{Size: 077777777777},
533 formats: FormatUSTAR | FormatPAX | FormatGNU,
534 }, {
535 header: &Header{Size: 077777777777, Format: FormatUSTAR},
536 formats: FormatUSTAR,
537 }, {
538 header: &Header{Size: 077777777777, Format: FormatPAX},
539 formats: FormatUSTAR | FormatPAX,
540 }, {
541 header: &Header{Size: 077777777777, Format: FormatGNU},
542 formats: FormatGNU,
543 }, {
544 header: &Header{Size: 077777777777 + 1},
545 paxHdrs: map[string]string{paxSize: "8589934592"},
546 formats: FormatPAX | FormatGNU,
547 }, {
548 header: &Header{Size: 077777777777 + 1, Format: FormatPAX},
549 paxHdrs: map[string]string{paxSize: "8589934592"},
550 formats: FormatPAX,
551 }, {
552 header: &Header{Size: 077777777777 + 1, Format: FormatGNU},
553 paxHdrs: map[string]string{paxSize: "8589934592"},
554 formats: FormatGNU,
555 }, {
556 header: &Header{Mode: 07777777},
557 formats: FormatUSTAR | FormatPAX | FormatGNU,
558 }, {
559 header: &Header{Mode: 07777777 + 1},
560 formats: FormatGNU,
561 }, {
562 header: &Header{Devmajor: -123},
563 formats: FormatGNU,
564 }, {
565 header: &Header{Devmajor: 1<<56 - 1},
566 formats: FormatGNU,
567 }, {
568 header: &Header{Devmajor: 1 << 56},
569 formats: FormatUnknown,
570 }, {
571 header: &Header{Devmajor: -1 << 56},
572 formats: FormatGNU,
573 }, {
574 header: &Header{Devmajor: -1<<56 - 1},
575 formats: FormatUnknown,
576 }, {
577 header: &Header{Name: "用戶名", Devmajor: -1 << 56},
578 formats: FormatGNU,
579 }, {
580 header: &Header{Size: math.MaxInt64},
581 paxHdrs: map[string]string{paxSize: "9223372036854775807"},
582 formats: FormatPAX | FormatGNU,
583 }, {
584 header: &Header{Size: math.MinInt64},
585 paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
586 formats: FormatUnknown,
587 }, {
588 header: &Header{Uname: "0123456789abcdef0123456789abcdef"},
589 formats: FormatUSTAR | FormatPAX | FormatGNU,
590 }, {
591 header: &Header{Uname: "0123456789abcdef0123456789abcdefx"},
592 paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
593 formats: FormatPAX,
594 }, {
595 header: &Header{Name: "foobar"},
596 formats: FormatUSTAR | FormatPAX | FormatGNU,
597 }, {
598 header: &Header{Name: strings.Repeat("a", nameSize)},
599 formats: FormatUSTAR | FormatPAX | FormatGNU,
600 }, {
601 header: &Header{Name: strings.Repeat("a", nameSize+1)},
602 paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
603 formats: FormatPAX | FormatGNU,
604 }, {
605 header: &Header{Linkname: "用戶名"},
606 paxHdrs: map[string]string{paxLinkpath: "用戶名"},
607 formats: FormatPAX | FormatGNU,
608 }, {
609 header: &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
610 paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
611 formats: FormatUnknown,
612 }, {
613 header: &Header{Linkname: "\x00hello"},
614 paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
615 formats: FormatUnknown,
616 }, {
617 header: &Header{Uid: 07777777},
618 formats: FormatUSTAR | FormatPAX | FormatGNU,
619 }, {
620 header: &Header{Uid: 07777777 + 1},
621 paxHdrs: map[string]string{paxUid: "2097152"},
622 formats: FormatPAX | FormatGNU,
623 }, {
624 header: &Header{Xattrs: nil},
625 formats: FormatUSTAR | FormatPAX | FormatGNU,
626 }, {
627 header: &Header{Xattrs: map[string]string{"foo": "bar"}},
628 paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
629 formats: FormatPAX,
630 }, {
631 header: &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU},
632 paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
633 formats: FormatUnknown,
634 }, {
635 header: &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
636 paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"},
637 formats: FormatPAX,
638 }, {
639 header: &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
640 formats: FormatUnknown,
641 }, {
642 header: &Header{Xattrs: map[string]string{"foo": ""}},
643 paxHdrs: map[string]string{paxSchilyXattr + "foo": ""},
644 formats: FormatPAX,
645 }, {
646 header: &Header{ModTime: time.Unix(0, 0)},
647 formats: FormatUSTAR | FormatPAX | FormatGNU,
648 }, {
649 header: &Header{ModTime: time.Unix(077777777777, 0)},
650 formats: FormatUSTAR | FormatPAX | FormatGNU,
651 }, {
652 header: &Header{ModTime: time.Unix(077777777777+1, 0)},
653 paxHdrs: map[string]string{paxMtime: "8589934592"},
654 formats: FormatPAX | FormatGNU,
655 }, {
656 header: &Header{ModTime: time.Unix(math.MaxInt64, 0)},
657 paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
658 formats: FormatPAX | FormatGNU,
659 }, {
660 header: &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR},
661 paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
662 formats: FormatUnknown,
663 }, {
664 header: &Header{ModTime: time.Unix(-1, 0)},
665 paxHdrs: map[string]string{paxMtime: "-1"},
666 formats: FormatPAX | FormatGNU,
667 }, {
668 header: &Header{ModTime: time.Unix(1, 500)},
669 paxHdrs: map[string]string{paxMtime: "1.0000005"},
670 formats: FormatUSTAR | FormatPAX | FormatGNU,
671 }, {
672 header: &Header{ModTime: time.Unix(1, 0)},
673 formats: FormatUSTAR | FormatPAX | FormatGNU,
674 }, {
675 header: &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
676 formats: FormatUSTAR | FormatPAX,
677 }, {
678 header: &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
679 paxHdrs: map[string]string{paxMtime: "1.0000005"},
680 formats: FormatUSTAR,
681 }, {
682 header: &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
683 paxHdrs: map[string]string{paxMtime: "1.0000005"},
684 formats: FormatPAX,
685 }, {
686 header: &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
687 paxHdrs: map[string]string{paxMtime: "1.0000005"},
688 formats: FormatGNU,
689 }, {
690 header: &Header{ModTime: time.Unix(-1, 500)},
691 paxHdrs: map[string]string{paxMtime: "-0.9999995"},
692 formats: FormatPAX | FormatGNU,
693 }, {
694 header: &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
695 paxHdrs: map[string]string{paxMtime: "-0.9999995"},
696 formats: FormatGNU,
697 }, {
698 header: &Header{AccessTime: time.Unix(0, 0)},
699 paxHdrs: map[string]string{paxAtime: "0"},
700 formats: FormatPAX | FormatGNU,
701 }, {
702 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
703 paxHdrs: map[string]string{paxAtime: "0"},
704 formats: FormatUnknown,
705 }, {
706 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
707 paxHdrs: map[string]string{paxAtime: "0"},
708 formats: FormatPAX,
709 }, {
710 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
711 paxHdrs: map[string]string{paxAtime: "0"},
712 formats: FormatGNU,
713 }, {
714 header: &Header{AccessTime: time.Unix(-123, 0)},
715 paxHdrs: map[string]string{paxAtime: "-123"},
716 formats: FormatPAX | FormatGNU,
717 }, {
718 header: &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
719 paxHdrs: map[string]string{paxAtime: "-123"},
720 formats: FormatPAX,
721 }, {
722 header: &Header{ChangeTime: time.Unix(123, 456)},
723 paxHdrs: map[string]string{paxCtime: "123.000000456"},
724 formats: FormatPAX | FormatGNU,
725 }, {
726 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
727 paxHdrs: map[string]string{paxCtime: "123.000000456"},
728 formats: FormatUnknown,
729 }, {
730 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
731 paxHdrs: map[string]string{paxCtime: "123.000000456"},
732 formats: FormatGNU,
733 }, {
734 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
735 paxHdrs: map[string]string{paxCtime: "123.000000456"},
736 formats: FormatPAX,
737 }, {
738 header: &Header{Name: "foo/", Typeflag: TypeDir},
739 formats: FormatUSTAR | FormatPAX | FormatGNU,
740 }, {
741 header: &Header{Name: "foo/", Typeflag: TypeReg},
742 formats: FormatUnknown,
743 }, {
744 header: &Header{Name: "foo/", Typeflag: TypeSymlink},
745 formats: FormatUSTAR | FormatPAX | FormatGNU,
746 }}
747
748 for i, v := range vectors {
749 formats, paxHdrs, err := v.header.allowedFormats()
750 if formats != v.formats {
751 t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
752 }
753 if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
754 t.Errorf("test %d, allowedFormats():\ngot %v\nwant %s", i, paxHdrs, v.paxHdrs)
755 }
756 if (formats != FormatUnknown) && (err != nil) {
757 t.Errorf("test %d, unexpected error: %v", i, err)
758 }
759 if (formats == FormatUnknown) && (err == nil) {
760 t.Errorf("test %d, got nil-error, want non-nil error", i)
761 }
762 }
763 }
764
765 func Benchmark(b *testing.B) {
766 type file struct {
767 hdr *Header
768 body []byte
769 }
770
771 vectors := []struct {
772 label string
773 files []file
774 }{{
775 "USTAR",
776 []file{{
777 &Header{Name: "bar", Mode: 0640, Size: int64(3)},
778 []byte("foo"),
779 }, {
780 &Header{Name: "world", Mode: 0640, Size: int64(5)},
781 []byte("hello"),
782 }},
783 }, {
784 "GNU",
785 []file{{
786 &Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
787 []byte("foo"),
788 }, {
789 &Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
790 []byte("hello"),
791 }},
792 }, {
793 "PAX",
794 []file{{
795 &Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
796 []byte("foo"),
797 }, {
798 &Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
799 []byte("hello"),
800 }},
801 }}
802
803 b.Run("Writer", func(b *testing.B) {
804 for _, v := range vectors {
805 b.Run(v.label, func(b *testing.B) {
806 b.ReportAllocs()
807 for i := 0; i < b.N; i++ {
808
809
810 tw := NewWriter(io.Discard)
811 for _, file := range v.files {
812 if err := tw.WriteHeader(file.hdr); err != nil {
813 b.Errorf("unexpected WriteHeader error: %v", err)
814 }
815 if _, err := tw.Write(file.body); err != nil {
816 b.Errorf("unexpected Write error: %v", err)
817 }
818 }
819 if err := tw.Close(); err != nil {
820 b.Errorf("unexpected Close error: %v", err)
821 }
822 }
823 })
824 }
825 })
826
827 b.Run("Reader", func(b *testing.B) {
828 for _, v := range vectors {
829 var buf bytes.Buffer
830 var r bytes.Reader
831
832
833 tw := NewWriter(&buf)
834 for _, file := range v.files {
835 _ = tw.WriteHeader(file.hdr)
836 _, _ = tw.Write(file.body)
837 }
838 tw.Close()
839 b.Run(v.label, func(b *testing.B) {
840 b.ReportAllocs()
841
842 for i := 0; i < b.N; i++ {
843 r.Reset(buf.Bytes())
844 tr := NewReader(&r)
845 if _, err := tr.Next(); err != nil {
846 b.Errorf("unexpected Next error: %v", err)
847 }
848 if _, err := io.Copy(io.Discard, tr); err != nil {
849 b.Errorf("unexpected Copy error : %v", err)
850 }
851 }
852 })
853 }
854 })
855
856 }
857
View as plain text