github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/src/archive/tar/tar_test.go (about)

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