...
Run Format

Source file src/archive/tar/tar_test.go

Documentation: archive/tar

     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/ioutil"
    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 []interface{} // []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, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink")
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	defer os.RemoveAll(tmpdir)
   270  
   271  	link := filepath.Join(tmpdir, "link")
   272  	target := tmpdir
   273  	err = os.Symlink(target, link)
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	fi, err := os.Lstat(link)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  
   282  	h, err := FileInfoHeader(fi, target)
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	if g, e := h.Name, fi.Name(); g != e {
   287  		t.Errorf("Name = %q; want %q", g, e)
   288  	}
   289  	if g, e := h.Linkname, target; g != e {
   290  		t.Errorf("Linkname = %q; want %q", g, e)
   291  	}
   292  	if g, e := h.Typeflag, byte(TypeSymlink); g != e {
   293  		t.Errorf("Typeflag = %v; want %v", g, e)
   294  	}
   295  }
   296  
   297  func TestRoundTrip(t *testing.T) {
   298  	data := []byte("some file contents")
   299  
   300  	var b bytes.Buffer
   301  	tw := NewWriter(&b)
   302  	hdr := &Header{
   303  		Name:       "file.txt",
   304  		Uid:        1 << 21, // Too big for 8 octal digits
   305  		Size:       int64(len(data)),
   306  		ModTime:    time.Now().Round(time.Second),
   307  		PAXRecords: map[string]string{"uid": "2097152"},
   308  		Format:     FormatPAX,
   309  	}
   310  	if err := tw.WriteHeader(hdr); err != nil {
   311  		t.Fatalf("tw.WriteHeader: %v", err)
   312  	}
   313  	if _, err := tw.Write(data); err != nil {
   314  		t.Fatalf("tw.Write: %v", err)
   315  	}
   316  	if err := tw.Close(); err != nil {
   317  		t.Fatalf("tw.Close: %v", err)
   318  	}
   319  
   320  	// Read it back.
   321  	tr := NewReader(&b)
   322  	rHdr, err := tr.Next()
   323  	if err != nil {
   324  		t.Fatalf("tr.Next: %v", err)
   325  	}
   326  	if !reflect.DeepEqual(rHdr, hdr) {
   327  		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
   328  	}
   329  	rData, err := ioutil.ReadAll(tr)
   330  	if err != nil {
   331  		t.Fatalf("Read: %v", err)
   332  	}
   333  	if !bytes.Equal(rData, data) {
   334  		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
   335  	}
   336  }
   337  
   338  type headerRoundTripTest struct {
   339  	h  *Header
   340  	fm os.FileMode
   341  }
   342  
   343  func TestHeaderRoundTrip(t *testing.T) {
   344  	vectors := []headerRoundTripTest{{
   345  		// regular file.
   346  		h: &Header{
   347  			Name:     "test.txt",
   348  			Mode:     0644,
   349  			Size:     12,
   350  			ModTime:  time.Unix(1360600916, 0),
   351  			Typeflag: TypeReg,
   352  		},
   353  		fm: 0644,
   354  	}, {
   355  		// symbolic link.
   356  		h: &Header{
   357  			Name:     "link.txt",
   358  			Mode:     0777,
   359  			Size:     0,
   360  			ModTime:  time.Unix(1360600852, 0),
   361  			Typeflag: TypeSymlink,
   362  		},
   363  		fm: 0777 | os.ModeSymlink,
   364  	}, {
   365  		// character device node.
   366  		h: &Header{
   367  			Name:     "dev/null",
   368  			Mode:     0666,
   369  			Size:     0,
   370  			ModTime:  time.Unix(1360578951, 0),
   371  			Typeflag: TypeChar,
   372  		},
   373  		fm: 0666 | os.ModeDevice | os.ModeCharDevice,
   374  	}, {
   375  		// block device node.
   376  		h: &Header{
   377  			Name:     "dev/sda",
   378  			Mode:     0660,
   379  			Size:     0,
   380  			ModTime:  time.Unix(1360578954, 0),
   381  			Typeflag: TypeBlock,
   382  		},
   383  		fm: 0660 | os.ModeDevice,
   384  	}, {
   385  		// directory.
   386  		h: &Header{
   387  			Name:     "dir/",
   388  			Mode:     0755,
   389  			Size:     0,
   390  			ModTime:  time.Unix(1360601116, 0),
   391  			Typeflag: TypeDir,
   392  		},
   393  		fm: 0755 | os.ModeDir,
   394  	}, {
   395  		// fifo node.
   396  		h: &Header{
   397  			Name:     "dev/initctl",
   398  			Mode:     0600,
   399  			Size:     0,
   400  			ModTime:  time.Unix(1360578949, 0),
   401  			Typeflag: TypeFifo,
   402  		},
   403  		fm: 0600 | os.ModeNamedPipe,
   404  	}, {
   405  		// setuid.
   406  		h: &Header{
   407  			Name:     "bin/su",
   408  			Mode:     0755 | c_ISUID,
   409  			Size:     23232,
   410  			ModTime:  time.Unix(1355405093, 0),
   411  			Typeflag: TypeReg,
   412  		},
   413  		fm: 0755 | os.ModeSetuid,
   414  	}, {
   415  		// setguid.
   416  		h: &Header{
   417  			Name:     "group.txt",
   418  			Mode:     0750 | c_ISGID,
   419  			Size:     0,
   420  			ModTime:  time.Unix(1360602346, 0),
   421  			Typeflag: TypeReg,
   422  		},
   423  		fm: 0750 | os.ModeSetgid,
   424  	}, {
   425  		// sticky.
   426  		h: &Header{
   427  			Name:     "sticky.txt",
   428  			Mode:     0600 | c_ISVTX,
   429  			Size:     7,
   430  			ModTime:  time.Unix(1360602540, 0),
   431  			Typeflag: TypeReg,
   432  		},
   433  		fm: 0600 | os.ModeSticky,
   434  	}, {
   435  		// hard link.
   436  		h: &Header{
   437  			Name:     "hard.txt",
   438  			Mode:     0644,
   439  			Size:     0,
   440  			Linkname: "file.txt",
   441  			ModTime:  time.Unix(1360600916, 0),
   442  			Typeflag: TypeLink,
   443  		},
   444  		fm: 0644,
   445  	}, {
   446  		// More information.
   447  		h: &Header{
   448  			Name:     "info.txt",
   449  			Mode:     0600,
   450  			Size:     0,
   451  			Uid:      1000,
   452  			Gid:      1000,
   453  			ModTime:  time.Unix(1360602540, 0),
   454  			Uname:    "slartibartfast",
   455  			Gname:    "users",
   456  			Typeflag: TypeReg,
   457  		},
   458  		fm: 0600,
   459  	}}
   460  
   461  	for i, v := range vectors {
   462  		fi := v.h.FileInfo()
   463  		h2, err := FileInfoHeader(fi, "")
   464  		if err != nil {
   465  			t.Error(err)
   466  			continue
   467  		}
   468  		if strings.Contains(fi.Name(), "/") {
   469  			t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
   470  		}
   471  		name := path.Base(v.h.Name)
   472  		if fi.IsDir() {
   473  			name += "/"
   474  		}
   475  		if got, want := h2.Name, name; got != want {
   476  			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
   477  		}
   478  		if got, want := h2.Size, v.h.Size; got != want {
   479  			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
   480  		}
   481  		if got, want := h2.Uid, v.h.Uid; got != want {
   482  			t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
   483  		}
   484  		if got, want := h2.Gid, v.h.Gid; got != want {
   485  			t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
   486  		}
   487  		if got, want := h2.Uname, v.h.Uname; got != want {
   488  			t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
   489  		}
   490  		if got, want := h2.Gname, v.h.Gname; got != want {
   491  			t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
   492  		}
   493  		if got, want := h2.Linkname, v.h.Linkname; got != want {
   494  			t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
   495  		}
   496  		if got, want := h2.Typeflag, v.h.Typeflag; got != want {
   497  			t.Logf("%#v %#v", v.h, fi.Sys())
   498  			t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
   499  		}
   500  		if got, want := h2.Mode, v.h.Mode; got != want {
   501  			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
   502  		}
   503  		if got, want := fi.Mode(), v.fm; got != want {
   504  			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
   505  		}
   506  		if got, want := h2.AccessTime, v.h.AccessTime; got != want {
   507  			t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
   508  		}
   509  		if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
   510  			t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
   511  		}
   512  		if got, want := h2.ModTime, v.h.ModTime; got != want {
   513  			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
   514  		}
   515  		if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
   516  			t.Errorf("i=%d: Sys didn't return original *Header", i)
   517  		}
   518  	}
   519  }
   520  
   521  func TestHeaderAllowedFormats(t *testing.T) {
   522  	vectors := []struct {
   523  		header  *Header           // Input header
   524  		paxHdrs map[string]string // Expected PAX headers that may be needed
   525  		formats Format            // Expected formats that can encode the header
   526  	}{{
   527  		header:  &Header{},
   528  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   529  	}, {
   530  		header:  &Header{Size: 077777777777},
   531  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   532  	}, {
   533  		header:  &Header{Size: 077777777777, Format: FormatUSTAR},
   534  		formats: FormatUSTAR,
   535  	}, {
   536  		header:  &Header{Size: 077777777777, Format: FormatPAX},
   537  		formats: FormatUSTAR | FormatPAX,
   538  	}, {
   539  		header:  &Header{Size: 077777777777, Format: FormatGNU},
   540  		formats: FormatGNU,
   541  	}, {
   542  		header:  &Header{Size: 077777777777 + 1},
   543  		paxHdrs: map[string]string{paxSize: "8589934592"},
   544  		formats: FormatPAX | FormatGNU,
   545  	}, {
   546  		header:  &Header{Size: 077777777777 + 1, Format: FormatPAX},
   547  		paxHdrs: map[string]string{paxSize: "8589934592"},
   548  		formats: FormatPAX,
   549  	}, {
   550  		header:  &Header{Size: 077777777777 + 1, Format: FormatGNU},
   551  		paxHdrs: map[string]string{paxSize: "8589934592"},
   552  		formats: FormatGNU,
   553  	}, {
   554  		header:  &Header{Mode: 07777777},
   555  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   556  	}, {
   557  		header:  &Header{Mode: 07777777 + 1},
   558  		formats: FormatGNU,
   559  	}, {
   560  		header:  &Header{Devmajor: -123},
   561  		formats: FormatGNU,
   562  	}, {
   563  		header:  &Header{Devmajor: 1<<56 - 1},
   564  		formats: FormatGNU,
   565  	}, {
   566  		header:  &Header{Devmajor: 1 << 56},
   567  		formats: FormatUnknown,
   568  	}, {
   569  		header:  &Header{Devmajor: -1 << 56},
   570  		formats: FormatGNU,
   571  	}, {
   572  		header:  &Header{Devmajor: -1<<56 - 1},
   573  		formats: FormatUnknown,
   574  	}, {
   575  		header:  &Header{Name: "用戶名", Devmajor: -1 << 56},
   576  		formats: FormatGNU,
   577  	}, {
   578  		header:  &Header{Size: math.MaxInt64},
   579  		paxHdrs: map[string]string{paxSize: "9223372036854775807"},
   580  		formats: FormatPAX | FormatGNU,
   581  	}, {
   582  		header:  &Header{Size: math.MinInt64},
   583  		paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
   584  		formats: FormatUnknown,
   585  	}, {
   586  		header:  &Header{Uname: "0123456789abcdef0123456789abcdef"},
   587  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   588  	}, {
   589  		header:  &Header{Uname: "0123456789abcdef0123456789abcdefx"},
   590  		paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
   591  		formats: FormatPAX,
   592  	}, {
   593  		header:  &Header{Name: "foobar"},
   594  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   595  	}, {
   596  		header:  &Header{Name: strings.Repeat("a", nameSize)},
   597  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   598  	}, {
   599  		header:  &Header{Name: strings.Repeat("a", nameSize+1)},
   600  		paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
   601  		formats: FormatPAX | FormatGNU,
   602  	}, {
   603  		header:  &Header{Linkname: "用戶名"},
   604  		paxHdrs: map[string]string{paxLinkpath: "用戶名"},
   605  		formats: FormatPAX | FormatGNU,
   606  	}, {
   607  		header:  &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
   608  		paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
   609  		formats: FormatUnknown,
   610  	}, {
   611  		header:  &Header{Linkname: "\x00hello"},
   612  		paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
   613  		formats: FormatUnknown,
   614  	}, {
   615  		header:  &Header{Uid: 07777777},
   616  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   617  	}, {
   618  		header:  &Header{Uid: 07777777 + 1},
   619  		paxHdrs: map[string]string{paxUid: "2097152"},
   620  		formats: FormatPAX | FormatGNU,
   621  	}, {
   622  		header:  &Header{Xattrs: nil},
   623  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   624  	}, {
   625  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}},
   626  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   627  		formats: FormatPAX,
   628  	}, {
   629  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU},
   630  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   631  		formats: FormatUnknown,
   632  	}, {
   633  		header:  &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
   634  		paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"},
   635  		formats: FormatPAX,
   636  	}, {
   637  		header:  &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
   638  		formats: FormatUnknown,
   639  	}, {
   640  		header:  &Header{Xattrs: map[string]string{"foo": ""}},
   641  		paxHdrs: map[string]string{paxSchilyXattr + "foo": ""},
   642  		formats: FormatPAX,
   643  	}, {
   644  		header:  &Header{ModTime: time.Unix(0, 0)},
   645  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   646  	}, {
   647  		header:  &Header{ModTime: time.Unix(077777777777, 0)},
   648  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   649  	}, {
   650  		header:  &Header{ModTime: time.Unix(077777777777+1, 0)},
   651  		paxHdrs: map[string]string{paxMtime: "8589934592"},
   652  		formats: FormatPAX | FormatGNU,
   653  	}, {
   654  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0)},
   655  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   656  		formats: FormatPAX | FormatGNU,
   657  	}, {
   658  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR},
   659  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   660  		formats: FormatUnknown,
   661  	}, {
   662  		header:  &Header{ModTime: time.Unix(-1, 0)},
   663  		paxHdrs: map[string]string{paxMtime: "-1"},
   664  		formats: FormatPAX | FormatGNU,
   665  	}, {
   666  		header:  &Header{ModTime: time.Unix(1, 500)},
   667  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   668  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   669  	}, {
   670  		header:  &Header{ModTime: time.Unix(1, 0)},
   671  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   672  	}, {
   673  		header:  &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
   674  		formats: FormatUSTAR | FormatPAX,
   675  	}, {
   676  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
   677  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   678  		formats: FormatUSTAR,
   679  	}, {
   680  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
   681  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   682  		formats: FormatPAX,
   683  	}, {
   684  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
   685  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   686  		formats: FormatGNU,
   687  	}, {
   688  		header:  &Header{ModTime: time.Unix(-1, 500)},
   689  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   690  		formats: FormatPAX | FormatGNU,
   691  	}, {
   692  		header:  &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
   693  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   694  		formats: FormatGNU,
   695  	}, {
   696  		header:  &Header{AccessTime: time.Unix(0, 0)},
   697  		paxHdrs: map[string]string{paxAtime: "0"},
   698  		formats: FormatPAX | FormatGNU,
   699  	}, {
   700  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
   701  		paxHdrs: map[string]string{paxAtime: "0"},
   702  		formats: FormatUnknown,
   703  	}, {
   704  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
   705  		paxHdrs: map[string]string{paxAtime: "0"},
   706  		formats: FormatPAX,
   707  	}, {
   708  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
   709  		paxHdrs: map[string]string{paxAtime: "0"},
   710  		formats: FormatGNU,
   711  	}, {
   712  		header:  &Header{AccessTime: time.Unix(-123, 0)},
   713  		paxHdrs: map[string]string{paxAtime: "-123"},
   714  		formats: FormatPAX | FormatGNU,
   715  	}, {
   716  		header:  &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
   717  		paxHdrs: map[string]string{paxAtime: "-123"},
   718  		formats: FormatPAX,
   719  	}, {
   720  		header:  &Header{ChangeTime: time.Unix(123, 456)},
   721  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   722  		formats: FormatPAX | FormatGNU,
   723  	}, {
   724  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
   725  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   726  		formats: FormatUnknown,
   727  	}, {
   728  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
   729  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   730  		formats: FormatGNU,
   731  	}, {
   732  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
   733  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   734  		formats: FormatPAX,
   735  	}, {
   736  		header:  &Header{Name: "foo/", Typeflag: TypeDir},
   737  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   738  	}, {
   739  		header:  &Header{Name: "foo/", Typeflag: TypeReg},
   740  		formats: FormatUnknown,
   741  	}, {
   742  		header:  &Header{Name: "foo/", Typeflag: TypeSymlink},
   743  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   744  	}}
   745  
   746  	for i, v := range vectors {
   747  		formats, paxHdrs, err := v.header.allowedFormats()
   748  		if formats != v.formats {
   749  			t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
   750  		}
   751  		if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
   752  			t.Errorf("test %d, allowedFormats():\ngot  %v\nwant %s", i, paxHdrs, v.paxHdrs)
   753  		}
   754  		if (formats != FormatUnknown) && (err != nil) {
   755  			t.Errorf("test %d, unexpected error: %v", i, err)
   756  		}
   757  		if (formats == FormatUnknown) && (err == nil) {
   758  			t.Errorf("test %d, got nil-error, want non-nil error", i)
   759  		}
   760  	}
   761  }
   762  
   763  func Benchmark(b *testing.B) {
   764  	type file struct {
   765  		hdr  *Header
   766  		body []byte
   767  	}
   768  
   769  	vectors := []struct {
   770  		label string
   771  		files []file
   772  	}{{
   773  		"USTAR",
   774  		[]file{{
   775  			&Header{Name: "bar", Mode: 0640, Size: int64(3)},
   776  			[]byte("foo"),
   777  		}, {
   778  			&Header{Name: "world", Mode: 0640, Size: int64(5)},
   779  			[]byte("hello"),
   780  		}},
   781  	}, {
   782  		"GNU",
   783  		[]file{{
   784  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
   785  			[]byte("foo"),
   786  		}, {
   787  			&Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
   788  			[]byte("hello"),
   789  		}},
   790  	}, {
   791  		"PAX",
   792  		[]file{{
   793  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
   794  			[]byte("foo"),
   795  		}, {
   796  			&Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
   797  			[]byte("hello"),
   798  		}},
   799  	}}
   800  
   801  	b.Run("Writer", func(b *testing.B) {
   802  		for _, v := range vectors {
   803  			b.Run(v.label, func(b *testing.B) {
   804  				b.ReportAllocs()
   805  				for i := 0; i < b.N; i++ {
   806  					// Writing to ioutil.Discard because we want to
   807  					// test purely the writer code and not bring in disk performance into this.
   808  					tw := NewWriter(ioutil.Discard)
   809  					for _, file := range v.files {
   810  						if err := tw.WriteHeader(file.hdr); err != nil {
   811  							b.Errorf("unexpected WriteHeader error: %v", err)
   812  						}
   813  						if _, err := tw.Write(file.body); err != nil {
   814  							b.Errorf("unexpected Write error: %v", err)
   815  						}
   816  					}
   817  					if err := tw.Close(); err != nil {
   818  						b.Errorf("unexpected Close error: %v", err)
   819  					}
   820  				}
   821  			})
   822  		}
   823  	})
   824  
   825  	b.Run("Reader", func(b *testing.B) {
   826  		for _, v := range vectors {
   827  			var buf bytes.Buffer
   828  			var r bytes.Reader
   829  
   830  			// Write the archive to a byte buffer.
   831  			tw := NewWriter(&buf)
   832  			for _, file := range v.files {
   833  				tw.WriteHeader(file.hdr)
   834  				tw.Write(file.body)
   835  			}
   836  			tw.Close()
   837  			b.Run(v.label, func(b *testing.B) {
   838  				b.ReportAllocs()
   839  				// Read from the byte buffer.
   840  				for i := 0; i < b.N; i++ {
   841  					r.Reset(buf.Bytes())
   842  					tr := NewReader(&r)
   843  					if _, err := tr.Next(); err != nil {
   844  						b.Errorf("unexpected Next error: %v", err)
   845  					}
   846  					if _, err := io.Copy(ioutil.Discard, tr); err != nil {
   847  						b.Errorf("unexpected Copy error : %v", err)
   848  					}
   849  				}
   850  			})
   851  		}
   852  	})
   853  
   854  }
   855  

View as plain text