github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/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 TestFileInfoHeader(t *testing.T) {
    23  	fi, err := os.Stat("testdata/small.txt")
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	h, err := FileInfoHeader(fi, "")
    28  	if err != nil {
    29  		t.Fatalf("FileInfoHeader: %v", err)
    30  	}
    31  	if g, e := h.Name, "small.txt"; g != e {
    32  		t.Errorf("Name = %q; want %q", g, e)
    33  	}
    34  	if g, e := h.Mode, int64(fi.Mode().Perm()); g != e {
    35  		t.Errorf("Mode = %#o; want %#o", g, e)
    36  	}
    37  	if g, e := h.Size, int64(5); g != e {
    38  		t.Errorf("Size = %v; want %v", g, e)
    39  	}
    40  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
    41  		t.Errorf("ModTime = %v; want %v", g, e)
    42  	}
    43  	// FileInfoHeader should error when passing nil FileInfo
    44  	if _, err := FileInfoHeader(nil, ""); err == nil {
    45  		t.Fatalf("Expected error when passing nil to FileInfoHeader")
    46  	}
    47  }
    48  
    49  func TestFileInfoHeaderDir(t *testing.T) {
    50  	fi, err := os.Stat("testdata")
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	h, err := FileInfoHeader(fi, "")
    55  	if err != nil {
    56  		t.Fatalf("FileInfoHeader: %v", err)
    57  	}
    58  	if g, e := h.Name, "testdata/"; g != e {
    59  		t.Errorf("Name = %q; want %q", g, e)
    60  	}
    61  	// Ignoring c_ISGID for golang.org/issue/4867
    62  	if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e {
    63  		t.Errorf("Mode = %#o; want %#o", g, e)
    64  	}
    65  	if g, e := h.Size, int64(0); g != e {
    66  		t.Errorf("Size = %v; want %v", g, e)
    67  	}
    68  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
    69  		t.Errorf("ModTime = %v; want %v", g, e)
    70  	}
    71  }
    72  
    73  func TestFileInfoHeaderSymlink(t *testing.T) {
    74  	testenv.MustHaveSymlink(t)
    75  
    76  	tmpdir, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink")
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	defer os.RemoveAll(tmpdir)
    81  
    82  	link := filepath.Join(tmpdir, "link")
    83  	target := tmpdir
    84  	err = os.Symlink(target, link)
    85  	if err != nil {
    86  		t.Fatal(err)
    87  	}
    88  	fi, err := os.Lstat(link)
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	h, err := FileInfoHeader(fi, target)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	if g, e := h.Name, fi.Name(); g != e {
    98  		t.Errorf("Name = %q; want %q", g, e)
    99  	}
   100  	if g, e := h.Linkname, target; g != e {
   101  		t.Errorf("Linkname = %q; want %q", g, e)
   102  	}
   103  	if g, e := h.Typeflag, byte(TypeSymlink); g != e {
   104  		t.Errorf("Typeflag = %v; want %v", g, e)
   105  	}
   106  }
   107  
   108  func TestRoundTrip(t *testing.T) {
   109  	data := []byte("some file contents")
   110  
   111  	var b bytes.Buffer
   112  	tw := NewWriter(&b)
   113  	hdr := &Header{
   114  		Name: "file.txt",
   115  		Uid:  1 << 21, // too big for 8 octal digits
   116  		Size: int64(len(data)),
   117  		// AddDate to strip monotonic clock reading,
   118  		// and Round to discard sub-second precision,
   119  		// both of which are not included in the tar header
   120  		// and would otherwise break the round-trip check
   121  		// below.
   122  		ModTime: time.Now().AddDate(0, 0, 0).Round(1 * time.Second),
   123  	}
   124  	if err := tw.WriteHeader(hdr); err != nil {
   125  		t.Fatalf("tw.WriteHeader: %v", err)
   126  	}
   127  	if _, err := tw.Write(data); err != nil {
   128  		t.Fatalf("tw.Write: %v", err)
   129  	}
   130  	if err := tw.Close(); err != nil {
   131  		t.Fatalf("tw.Close: %v", err)
   132  	}
   133  
   134  	// Read it back.
   135  	tr := NewReader(&b)
   136  	rHdr, err := tr.Next()
   137  	if err != nil {
   138  		t.Fatalf("tr.Next: %v", err)
   139  	}
   140  	if !reflect.DeepEqual(rHdr, hdr) {
   141  		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
   142  	}
   143  	rData, err := ioutil.ReadAll(tr)
   144  	if err != nil {
   145  		t.Fatalf("Read: %v", err)
   146  	}
   147  	if !bytes.Equal(rData, data) {
   148  		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
   149  	}
   150  }
   151  
   152  type headerRoundTripTest struct {
   153  	h  *Header
   154  	fm os.FileMode
   155  }
   156  
   157  func TestHeaderRoundTrip(t *testing.T) {
   158  	vectors := []headerRoundTripTest{{
   159  		// regular file.
   160  		h: &Header{
   161  			Name:     "test.txt",
   162  			Mode:     0644,
   163  			Size:     12,
   164  			ModTime:  time.Unix(1360600916, 0),
   165  			Typeflag: TypeReg,
   166  		},
   167  		fm: 0644,
   168  	}, {
   169  		// symbolic link.
   170  		h: &Header{
   171  			Name:     "link.txt",
   172  			Mode:     0777,
   173  			Size:     0,
   174  			ModTime:  time.Unix(1360600852, 0),
   175  			Typeflag: TypeSymlink,
   176  		},
   177  		fm: 0777 | os.ModeSymlink,
   178  	}, {
   179  		// character device node.
   180  		h: &Header{
   181  			Name:     "dev/null",
   182  			Mode:     0666,
   183  			Size:     0,
   184  			ModTime:  time.Unix(1360578951, 0),
   185  			Typeflag: TypeChar,
   186  		},
   187  		fm: 0666 | os.ModeDevice | os.ModeCharDevice,
   188  	}, {
   189  		// block device node.
   190  		h: &Header{
   191  			Name:     "dev/sda",
   192  			Mode:     0660,
   193  			Size:     0,
   194  			ModTime:  time.Unix(1360578954, 0),
   195  			Typeflag: TypeBlock,
   196  		},
   197  		fm: 0660 | os.ModeDevice,
   198  	}, {
   199  		// directory.
   200  		h: &Header{
   201  			Name:     "dir/",
   202  			Mode:     0755,
   203  			Size:     0,
   204  			ModTime:  time.Unix(1360601116, 0),
   205  			Typeflag: TypeDir,
   206  		},
   207  		fm: 0755 | os.ModeDir,
   208  	}, {
   209  		// fifo node.
   210  		h: &Header{
   211  			Name:     "dev/initctl",
   212  			Mode:     0600,
   213  			Size:     0,
   214  			ModTime:  time.Unix(1360578949, 0),
   215  			Typeflag: TypeFifo,
   216  		},
   217  		fm: 0600 | os.ModeNamedPipe,
   218  	}, {
   219  		// setuid.
   220  		h: &Header{
   221  			Name:     "bin/su",
   222  			Mode:     0755 | c_ISUID,
   223  			Size:     23232,
   224  			ModTime:  time.Unix(1355405093, 0),
   225  			Typeflag: TypeReg,
   226  		},
   227  		fm: 0755 | os.ModeSetuid,
   228  	}, {
   229  		// setguid.
   230  		h: &Header{
   231  			Name:     "group.txt",
   232  			Mode:     0750 | c_ISGID,
   233  			Size:     0,
   234  			ModTime:  time.Unix(1360602346, 0),
   235  			Typeflag: TypeReg,
   236  		},
   237  		fm: 0750 | os.ModeSetgid,
   238  	}, {
   239  		// sticky.
   240  		h: &Header{
   241  			Name:     "sticky.txt",
   242  			Mode:     0600 | c_ISVTX,
   243  			Size:     7,
   244  			ModTime:  time.Unix(1360602540, 0),
   245  			Typeflag: TypeReg,
   246  		},
   247  		fm: 0600 | os.ModeSticky,
   248  	}, {
   249  		// hard link.
   250  		h: &Header{
   251  			Name:     "hard.txt",
   252  			Mode:     0644,
   253  			Size:     0,
   254  			Linkname: "file.txt",
   255  			ModTime:  time.Unix(1360600916, 0),
   256  			Typeflag: TypeLink,
   257  		},
   258  		fm: 0644,
   259  	}, {
   260  		// More information.
   261  		h: &Header{
   262  			Name:     "info.txt",
   263  			Mode:     0600,
   264  			Size:     0,
   265  			Uid:      1000,
   266  			Gid:      1000,
   267  			ModTime:  time.Unix(1360602540, 0),
   268  			Uname:    "slartibartfast",
   269  			Gname:    "users",
   270  			Typeflag: TypeReg,
   271  		},
   272  		fm: 0600,
   273  	}}
   274  
   275  	for i, v := range vectors {
   276  		fi := v.h.FileInfo()
   277  		h2, err := FileInfoHeader(fi, "")
   278  		if err != nil {
   279  			t.Error(err)
   280  			continue
   281  		}
   282  		if strings.Contains(fi.Name(), "/") {
   283  			t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
   284  		}
   285  		name := path.Base(v.h.Name)
   286  		if fi.IsDir() {
   287  			name += "/"
   288  		}
   289  		if got, want := h2.Name, name; got != want {
   290  			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
   291  		}
   292  		if got, want := h2.Size, v.h.Size; got != want {
   293  			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
   294  		}
   295  		if got, want := h2.Uid, v.h.Uid; got != want {
   296  			t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
   297  		}
   298  		if got, want := h2.Gid, v.h.Gid; got != want {
   299  			t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
   300  		}
   301  		if got, want := h2.Uname, v.h.Uname; got != want {
   302  			t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
   303  		}
   304  		if got, want := h2.Gname, v.h.Gname; got != want {
   305  			t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
   306  		}
   307  		if got, want := h2.Linkname, v.h.Linkname; got != want {
   308  			t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
   309  		}
   310  		if got, want := h2.Typeflag, v.h.Typeflag; got != want {
   311  			t.Logf("%#v %#v", v.h, fi.Sys())
   312  			t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
   313  		}
   314  		if got, want := h2.Mode, v.h.Mode; got != want {
   315  			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
   316  		}
   317  		if got, want := fi.Mode(), v.fm; got != want {
   318  			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
   319  		}
   320  		if got, want := h2.AccessTime, v.h.AccessTime; got != want {
   321  			t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
   322  		}
   323  		if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
   324  			t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
   325  		}
   326  		if got, want := h2.ModTime, v.h.ModTime; got != want {
   327  			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
   328  		}
   329  		if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
   330  			t.Errorf("i=%d: Sys didn't return original *Header", i)
   331  		}
   332  	}
   333  }
   334  
   335  func TestHeaderAllowedFormats(t *testing.T) {
   336  	prettyFormat := func(f int) string {
   337  		if f == formatUnknown {
   338  			return "(formatUnknown)"
   339  		}
   340  		var fs []string
   341  		if f&formatUSTAR > 0 {
   342  			fs = append(fs, "formatUSTAR")
   343  		}
   344  		if f&formatPAX > 0 {
   345  			fs = append(fs, "formatPAX")
   346  		}
   347  		if f&formatGNU > 0 {
   348  			fs = append(fs, "formatGNU")
   349  		}
   350  		return "(" + strings.Join(fs, " | ") + ")"
   351  	}
   352  
   353  	vectors := []struct {
   354  		header  *Header           // Input header
   355  		paxHdrs map[string]string // Expected PAX headers that may be needed
   356  		formats int               // Expected formats that can encode the header
   357  	}{{
   358  		header:  &Header{},
   359  		formats: formatUSTAR | formatPAX | formatGNU,
   360  	}, {
   361  		header:  &Header{Size: 077777777777},
   362  		formats: formatUSTAR | formatPAX | formatGNU,
   363  	}, {
   364  		header:  &Header{Size: 077777777777 + 1},
   365  		paxHdrs: map[string]string{paxSize: "8589934592"},
   366  		formats: formatPAX | formatGNU,
   367  	}, {
   368  		header:  &Header{Mode: 07777777},
   369  		formats: formatUSTAR | formatPAX | formatGNU,
   370  	}, {
   371  		header:  &Header{Mode: 07777777 + 1},
   372  		formats: formatGNU,
   373  	}, {
   374  		header:  &Header{Devmajor: -123},
   375  		formats: formatGNU,
   376  	}, {
   377  		header:  &Header{Devmajor: 1<<56 - 1},
   378  		formats: formatGNU,
   379  	}, {
   380  		header:  &Header{Devmajor: 1 << 56},
   381  		formats: formatUnknown,
   382  	}, {
   383  		header:  &Header{Devmajor: -1 << 56},
   384  		formats: formatGNU,
   385  	}, {
   386  		header:  &Header{Devmajor: -1<<56 - 1},
   387  		formats: formatUnknown,
   388  	}, {
   389  		header:  &Header{Name: "用戶名", Devmajor: -1 << 56},
   390  		formats: formatGNU,
   391  	}, {
   392  		header:  &Header{Size: math.MaxInt64},
   393  		paxHdrs: map[string]string{paxSize: "9223372036854775807"},
   394  		formats: formatPAX | formatGNU,
   395  	}, {
   396  		header:  &Header{Size: math.MinInt64},
   397  		paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
   398  		formats: formatUnknown,
   399  	}, {
   400  		header:  &Header{Uname: "0123456789abcdef0123456789abcdef"},
   401  		formats: formatUSTAR | formatPAX | formatGNU,
   402  	}, {
   403  		header:  &Header{Uname: "0123456789abcdef0123456789abcdefx"},
   404  		paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
   405  		formats: formatPAX,
   406  	}, {
   407  		header:  &Header{Name: "foobar"},
   408  		formats: formatUSTAR | formatPAX | formatGNU,
   409  	}, {
   410  		header:  &Header{Name: strings.Repeat("a", nameSize)},
   411  		formats: formatUSTAR | formatPAX | formatGNU,
   412  	}, {
   413  		header:  &Header{Name: strings.Repeat("a", nameSize+1)},
   414  		paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
   415  		formats: formatPAX | formatGNU,
   416  	}, {
   417  		header:  &Header{Linkname: "用戶名"},
   418  		paxHdrs: map[string]string{paxLinkpath: "用戶名"},
   419  		formats: formatPAX | formatGNU,
   420  	}, {
   421  		header:  &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
   422  		paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
   423  		formats: formatUnknown,
   424  	}, {
   425  		header:  &Header{Linkname: "\x00hello"},
   426  		paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
   427  		formats: formatUnknown,
   428  	}, {
   429  		header:  &Header{Uid: 07777777},
   430  		formats: formatUSTAR | formatPAX | formatGNU,
   431  	}, {
   432  		header:  &Header{Uid: 07777777 + 1},
   433  		paxHdrs: map[string]string{paxUid: "2097152"},
   434  		formats: formatPAX | formatGNU,
   435  	}, {
   436  		header:  &Header{Xattrs: nil},
   437  		formats: formatUSTAR | formatPAX | formatGNU,
   438  	}, {
   439  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}},
   440  		paxHdrs: map[string]string{paxXattr + "foo": "bar"},
   441  		formats: formatPAX,
   442  	}, {
   443  		header:  &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
   444  		paxHdrs: map[string]string{paxXattr + "用戶名": "\x00hello"},
   445  		formats: formatPAX,
   446  	}, {
   447  		header:  &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
   448  		formats: formatUnknown,
   449  	}, {
   450  		header:  &Header{Xattrs: map[string]string{"foo": ""}},
   451  		formats: formatUnknown,
   452  	}, {
   453  		header:  &Header{ModTime: time.Unix(0, 0)},
   454  		formats: formatUSTAR | formatPAX | formatGNU,
   455  	}, {
   456  		header:  &Header{ModTime: time.Unix(077777777777, 0)},
   457  		formats: formatUSTAR | formatPAX | formatGNU,
   458  	}, {
   459  		header:  &Header{ModTime: time.Unix(077777777777+1, 0)},
   460  		paxHdrs: map[string]string{paxMtime: "8589934592"},
   461  		formats: formatPAX | formatGNU,
   462  	}, {
   463  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0)},
   464  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   465  		formats: formatPAX | formatGNU,
   466  	}, {
   467  		header:  &Header{ModTime: time.Unix(-1, 0)},
   468  		paxHdrs: map[string]string{paxMtime: "-1"},
   469  		formats: formatPAX | formatGNU,
   470  	}, {
   471  		header:  &Header{ModTime: time.Unix(-1, 500)},
   472  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   473  		formats: formatPAX,
   474  	}, {
   475  		header:  &Header{AccessTime: time.Unix(0, 0)},
   476  		paxHdrs: map[string]string{paxAtime: "0"},
   477  		formats: formatPAX | formatGNU,
   478  	}, {
   479  		header:  &Header{AccessTime: time.Unix(-123, 0)},
   480  		paxHdrs: map[string]string{paxAtime: "-123"},
   481  		formats: formatPAX | formatGNU,
   482  	}, {
   483  		header:  &Header{ChangeTime: time.Unix(123, 456)},
   484  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   485  		formats: formatPAX,
   486  	}}
   487  
   488  	for i, v := range vectors {
   489  		formats, paxHdrs := v.header.allowedFormats()
   490  		if formats != v.formats {
   491  			t.Errorf("test %d, allowedFormats(...): got %v, want %v", i, prettyFormat(formats), prettyFormat(v.formats))
   492  		}
   493  		if formats&formatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
   494  			t.Errorf("test %d, allowedFormats(...):\ngot  %v\nwant %s", i, paxHdrs, v.paxHdrs)
   495  		}
   496  	}
   497  }
   498  
   499  func Benchmark(b *testing.B) {
   500  	type file struct {
   501  		hdr  *Header
   502  		body []byte
   503  	}
   504  
   505  	vectors := []struct {
   506  		label string
   507  		files []file
   508  	}{{
   509  		"USTAR",
   510  		[]file{{
   511  			&Header{Name: "bar", Mode: 0640, Size: int64(3)},
   512  			[]byte("foo"),
   513  		}, {
   514  			&Header{Name: "world", Mode: 0640, Size: int64(5)},
   515  			[]byte("hello"),
   516  		}},
   517  	}, {
   518  		"GNU",
   519  		[]file{{
   520  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
   521  			[]byte("foo"),
   522  		}, {
   523  			&Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
   524  			[]byte("hello"),
   525  		}},
   526  	}, {
   527  		"PAX",
   528  		[]file{{
   529  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
   530  			[]byte("foo"),
   531  		}, {
   532  			&Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
   533  			[]byte("hello"),
   534  		}},
   535  	}}
   536  
   537  	b.Run("Writer", func(b *testing.B) {
   538  		for _, v := range vectors {
   539  			b.Run(v.label, func(b *testing.B) {
   540  				b.ReportAllocs()
   541  				for i := 0; i < b.N; i++ {
   542  					// Writing to ioutil.Discard because we want to
   543  					// test purely the writer code and not bring in disk performance into this.
   544  					tw := NewWriter(ioutil.Discard)
   545  					for _, file := range v.files {
   546  						if err := tw.WriteHeader(file.hdr); err != nil {
   547  							b.Errorf("unexpected WriteHeader error: %v", err)
   548  						}
   549  						if _, err := tw.Write(file.body); err != nil {
   550  							b.Errorf("unexpected Write error: %v", err)
   551  						}
   552  					}
   553  					if err := tw.Close(); err != nil {
   554  						b.Errorf("unexpected Close error: %v", err)
   555  					}
   556  				}
   557  			})
   558  		}
   559  	})
   560  
   561  	b.Run("Reader", func(b *testing.B) {
   562  		for _, v := range vectors {
   563  			var buf bytes.Buffer
   564  			var r bytes.Reader
   565  
   566  			// Write the archive to a byte buffer.
   567  			tw := NewWriter(&buf)
   568  			for _, file := range v.files {
   569  				tw.WriteHeader(file.hdr)
   570  				tw.Write(file.body)
   571  			}
   572  			tw.Close()
   573  			b.Run(v.label, func(b *testing.B) {
   574  				b.ReportAllocs()
   575  				// Read from the byte buffer.
   576  				for i := 0; i < b.N; i++ {
   577  					r.Reset(buf.Bytes())
   578  					tr := NewReader(&r)
   579  					if _, err := tr.Next(); err != nil {
   580  						b.Errorf("unexpected Next error: %v", err)
   581  					}
   582  					if _, err := io.Copy(ioutil.Discard, tr); err != nil {
   583  						b.Errorf("unexpected Copy error : %v", err)
   584  					}
   585  				}
   586  			})
   587  		}
   588  	})
   589  
   590  }