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