github.com/hongwozai/go-src-1.4.3@v0.0.0-20191127132709-dc3fce3dbccb/src/archive/tar/writer_test.go (about)

     1  // Copyright 2009 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  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"reflect"
    14  	"strings"
    15  	"testing"
    16  	"testing/iotest"
    17  	"time"
    18  )
    19  
    20  type writerTestEntry struct {
    21  	header   *Header
    22  	contents string
    23  }
    24  
    25  type writerTest struct {
    26  	file    string // filename of expected output
    27  	entries []*writerTestEntry
    28  }
    29  
    30  var writerTests = []*writerTest{
    31  	// The writer test file was produced with this command:
    32  	// tar (GNU tar) 1.26
    33  	//   ln -s small.txt link.txt
    34  	//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
    35  	{
    36  		file: "testdata/writer.tar",
    37  		entries: []*writerTestEntry{
    38  			{
    39  				header: &Header{
    40  					Name:     "small.txt",
    41  					Mode:     0640,
    42  					Uid:      73025,
    43  					Gid:      5000,
    44  					Size:     5,
    45  					ModTime:  time.Unix(1246508266, 0),
    46  					Typeflag: '0',
    47  					Uname:    "dsymonds",
    48  					Gname:    "eng",
    49  				},
    50  				contents: "Kilts",
    51  			},
    52  			{
    53  				header: &Header{
    54  					Name:     "small2.txt",
    55  					Mode:     0640,
    56  					Uid:      73025,
    57  					Gid:      5000,
    58  					Size:     11,
    59  					ModTime:  time.Unix(1245217492, 0),
    60  					Typeflag: '0',
    61  					Uname:    "dsymonds",
    62  					Gname:    "eng",
    63  				},
    64  				contents: "Google.com\n",
    65  			},
    66  			{
    67  				header: &Header{
    68  					Name:     "link.txt",
    69  					Mode:     0777,
    70  					Uid:      1000,
    71  					Gid:      1000,
    72  					Size:     0,
    73  					ModTime:  time.Unix(1314603082, 0),
    74  					Typeflag: '2',
    75  					Linkname: "small.txt",
    76  					Uname:    "strings",
    77  					Gname:    "strings",
    78  				},
    79  				// no contents
    80  			},
    81  		},
    82  	},
    83  	// The truncated test file was produced using these commands:
    84  	//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
    85  	//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
    86  	{
    87  		file: "testdata/writer-big.tar",
    88  		entries: []*writerTestEntry{
    89  			{
    90  				header: &Header{
    91  					Name:     "tmp/16gig.txt",
    92  					Mode:     0640,
    93  					Uid:      73025,
    94  					Gid:      5000,
    95  					Size:     16 << 30,
    96  					ModTime:  time.Unix(1254699560, 0),
    97  					Typeflag: '0',
    98  					Uname:    "dsymonds",
    99  					Gname:    "eng",
   100  				},
   101  				// fake contents
   102  				contents: strings.Repeat("\x00", 4<<10),
   103  			},
   104  		},
   105  	},
   106  	// The truncated test file was produced using these commands:
   107  	//   dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
   108  	//   tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
   109  	{
   110  		file: "testdata/writer-big-long.tar",
   111  		entries: []*writerTestEntry{
   112  			{
   113  				header: &Header{
   114  					Name:     strings.Repeat("longname/", 15) + "16gig.txt",
   115  					Mode:     0644,
   116  					Uid:      1000,
   117  					Gid:      1000,
   118  					Size:     16 << 30,
   119  					ModTime:  time.Unix(1399583047, 0),
   120  					Typeflag: '0',
   121  					Uname:    "guillaume",
   122  					Gname:    "guillaume",
   123  				},
   124  				// fake contents
   125  				contents: strings.Repeat("\x00", 4<<10),
   126  			},
   127  		},
   128  	},
   129  	// This file was produced using gnu tar 1.17
   130  	// gnutar  -b 4 --format=ustar (longname/)*15 + file.txt
   131  	{
   132  		file: "testdata/ustar.tar",
   133  		entries: []*writerTestEntry{
   134  			{
   135  				header: &Header{
   136  					Name:     strings.Repeat("longname/", 15) + "file.txt",
   137  					Mode:     0644,
   138  					Uid:      0765,
   139  					Gid:      024,
   140  					Size:     06,
   141  					ModTime:  time.Unix(1360135598, 0),
   142  					Typeflag: '0',
   143  					Uname:    "shane",
   144  					Gname:    "staff",
   145  				},
   146  				contents: "hello\n",
   147  			},
   148  		},
   149  	},
   150  }
   151  
   152  // Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
   153  func bytestr(offset int, b []byte) string {
   154  	const rowLen = 32
   155  	s := fmt.Sprintf("%04x ", offset)
   156  	for _, ch := range b {
   157  		switch {
   158  		case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
   159  			s += fmt.Sprintf("  %c", ch)
   160  		default:
   161  			s += fmt.Sprintf(" %02x", ch)
   162  		}
   163  	}
   164  	return s
   165  }
   166  
   167  // Render a pseudo-diff between two blocks of bytes.
   168  func bytediff(a []byte, b []byte) string {
   169  	const rowLen = 32
   170  	s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
   171  	for offset := 0; len(a)+len(b) > 0; offset += rowLen {
   172  		na, nb := rowLen, rowLen
   173  		if na > len(a) {
   174  			na = len(a)
   175  		}
   176  		if nb > len(b) {
   177  			nb = len(b)
   178  		}
   179  		sa := bytestr(offset, a[0:na])
   180  		sb := bytestr(offset, b[0:nb])
   181  		if sa != sb {
   182  			s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
   183  		}
   184  		a = a[na:]
   185  		b = b[nb:]
   186  	}
   187  	return s
   188  }
   189  
   190  func TestWriter(t *testing.T) {
   191  testLoop:
   192  	for i, test := range writerTests {
   193  		expected, err := ioutil.ReadFile(test.file)
   194  		if err != nil {
   195  			t.Errorf("test %d: Unexpected error: %v", i, err)
   196  			continue
   197  		}
   198  
   199  		buf := new(bytes.Buffer)
   200  		tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
   201  		big := false
   202  		for j, entry := range test.entries {
   203  			big = big || entry.header.Size > 1<<10
   204  			if err := tw.WriteHeader(entry.header); err != nil {
   205  				t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
   206  				continue testLoop
   207  			}
   208  			if _, err := io.WriteString(tw, entry.contents); err != nil {
   209  				t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
   210  				continue testLoop
   211  			}
   212  		}
   213  		// Only interested in Close failures for the small tests.
   214  		if err := tw.Close(); err != nil && !big {
   215  			t.Errorf("test %d: Failed closing archive: %v", i, err)
   216  			continue testLoop
   217  		}
   218  
   219  		actual := buf.Bytes()
   220  		if !bytes.Equal(expected, actual) {
   221  			t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
   222  				i, bytediff(expected, actual))
   223  		}
   224  		if testing.Short() { // The second test is expensive.
   225  			break
   226  		}
   227  	}
   228  }
   229  
   230  func TestPax(t *testing.T) {
   231  	// Create an archive with a large name
   232  	fileinfo, err := os.Stat("testdata/small.txt")
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	hdr, err := FileInfoHeader(fileinfo, "")
   237  	if err != nil {
   238  		t.Fatalf("os.Stat: %v", err)
   239  	}
   240  	// Force a PAX long name to be written
   241  	longName := strings.Repeat("ab", 100)
   242  	contents := strings.Repeat(" ", int(hdr.Size))
   243  	hdr.Name = longName
   244  	var buf bytes.Buffer
   245  	writer := NewWriter(&buf)
   246  	if err := writer.WriteHeader(hdr); err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	if _, err = writer.Write([]byte(contents)); err != nil {
   250  		t.Fatal(err)
   251  	}
   252  	if err := writer.Close(); err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	// Simple test to make sure PAX extensions are in effect
   256  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
   257  		t.Fatal("Expected at least one PAX header to be written.")
   258  	}
   259  	// Test that we can get a long name back out of the archive.
   260  	reader := NewReader(&buf)
   261  	hdr, err = reader.Next()
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	if hdr.Name != longName {
   266  		t.Fatal("Couldn't recover long file name")
   267  	}
   268  }
   269  
   270  func TestPaxSymlink(t *testing.T) {
   271  	// Create an archive with a large linkname
   272  	fileinfo, err := os.Stat("testdata/small.txt")
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	hdr, err := FileInfoHeader(fileinfo, "")
   277  	hdr.Typeflag = TypeSymlink
   278  	if err != nil {
   279  		t.Fatalf("os.Stat:1 %v", err)
   280  	}
   281  	// Force a PAX long linkname to be written
   282  	longLinkname := strings.Repeat("1234567890/1234567890", 10)
   283  	hdr.Linkname = longLinkname
   284  
   285  	hdr.Size = 0
   286  	var buf bytes.Buffer
   287  	writer := NewWriter(&buf)
   288  	if err := writer.WriteHeader(hdr); err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	if err := writer.Close(); err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	// Simple test to make sure PAX extensions are in effect
   295  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
   296  		t.Fatal("Expected at least one PAX header to be written.")
   297  	}
   298  	// Test that we can get a long name back out of the archive.
   299  	reader := NewReader(&buf)
   300  	hdr, err = reader.Next()
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	if hdr.Linkname != longLinkname {
   305  		t.Fatal("Couldn't recover long link name")
   306  	}
   307  }
   308  
   309  func TestPaxNonAscii(t *testing.T) {
   310  	// Create an archive with non ascii. These should trigger a pax header
   311  	// because pax headers have a defined utf-8 encoding.
   312  	fileinfo, err := os.Stat("testdata/small.txt")
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  
   317  	hdr, err := FileInfoHeader(fileinfo, "")
   318  	if err != nil {
   319  		t.Fatalf("os.Stat:1 %v", err)
   320  	}
   321  
   322  	// some sample data
   323  	chineseFilename := "文件名"
   324  	chineseGroupname := "組"
   325  	chineseUsername := "用戶名"
   326  
   327  	hdr.Name = chineseFilename
   328  	hdr.Gname = chineseGroupname
   329  	hdr.Uname = chineseUsername
   330  
   331  	contents := strings.Repeat(" ", int(hdr.Size))
   332  
   333  	var buf bytes.Buffer
   334  	writer := NewWriter(&buf)
   335  	if err := writer.WriteHeader(hdr); err != nil {
   336  		t.Fatal(err)
   337  	}
   338  	if _, err = writer.Write([]byte(contents)); err != nil {
   339  		t.Fatal(err)
   340  	}
   341  	if err := writer.Close(); err != nil {
   342  		t.Fatal(err)
   343  	}
   344  	// Simple test to make sure PAX extensions are in effect
   345  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
   346  		t.Fatal("Expected at least one PAX header to be written.")
   347  	}
   348  	// Test that we can get a long name back out of the archive.
   349  	reader := NewReader(&buf)
   350  	hdr, err = reader.Next()
   351  	if err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	if hdr.Name != chineseFilename {
   355  		t.Fatal("Couldn't recover unicode name")
   356  	}
   357  	if hdr.Gname != chineseGroupname {
   358  		t.Fatal("Couldn't recover unicode group")
   359  	}
   360  	if hdr.Uname != chineseUsername {
   361  		t.Fatal("Couldn't recover unicode user")
   362  	}
   363  }
   364  
   365  func TestPaxXattrs(t *testing.T) {
   366  	xattrs := map[string]string{
   367  		"user.key": "value",
   368  	}
   369  
   370  	// Create an archive with an xattr
   371  	fileinfo, err := os.Stat("testdata/small.txt")
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  	hdr, err := FileInfoHeader(fileinfo, "")
   376  	if err != nil {
   377  		t.Fatalf("os.Stat: %v", err)
   378  	}
   379  	contents := "Kilts"
   380  	hdr.Xattrs = xattrs
   381  	var buf bytes.Buffer
   382  	writer := NewWriter(&buf)
   383  	if err := writer.WriteHeader(hdr); err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	if _, err = writer.Write([]byte(contents)); err != nil {
   387  		t.Fatal(err)
   388  	}
   389  	if err := writer.Close(); err != nil {
   390  		t.Fatal(err)
   391  	}
   392  	// Test that we can get the xattrs back out of the archive.
   393  	reader := NewReader(&buf)
   394  	hdr, err = reader.Next()
   395  	if err != nil {
   396  		t.Fatal(err)
   397  	}
   398  	if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
   399  		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
   400  			hdr.Xattrs, xattrs)
   401  	}
   402  }
   403  
   404  func TestPAXHeader(t *testing.T) {
   405  	medName := strings.Repeat("CD", 50)
   406  	longName := strings.Repeat("AB", 100)
   407  	paxTests := [][2]string{
   408  		{paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"},
   409  		{"a=b", "6 a=b\n"},          // Single digit length
   410  		{"a=names", "11 a=names\n"}, // Test case involving carries
   411  		{paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)},
   412  		{paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}}
   413  
   414  	for _, test := range paxTests {
   415  		key, expected := test[0], test[1]
   416  		if result := paxHeader(key); result != expected {
   417  			t.Fatalf("paxHeader: got %s, expected %s", result, expected)
   418  		}
   419  	}
   420  }
   421  
   422  func TestUSTARLongName(t *testing.T) {
   423  	// Create an archive with a path that failed to split with USTAR extension in previous versions.
   424  	fileinfo, err := os.Stat("testdata/small.txt")
   425  	if err != nil {
   426  		t.Fatal(err)
   427  	}
   428  	hdr, err := FileInfoHeader(fileinfo, "")
   429  	hdr.Typeflag = TypeDir
   430  	if err != nil {
   431  		t.Fatalf("os.Stat:1 %v", err)
   432  	}
   433  	// Force a PAX long name to be written. The name was taken from a practical example
   434  	// that fails and replaced ever char through numbers to anonymize the sample.
   435  	longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
   436  	hdr.Name = longName
   437  
   438  	hdr.Size = 0
   439  	var buf bytes.Buffer
   440  	writer := NewWriter(&buf)
   441  	if err := writer.WriteHeader(hdr); err != nil {
   442  		t.Fatal(err)
   443  	}
   444  	if err := writer.Close(); err != nil {
   445  		t.Fatal(err)
   446  	}
   447  	// Test that we can get a long name back out of the archive.
   448  	reader := NewReader(&buf)
   449  	hdr, err = reader.Next()
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	if hdr.Name != longName {
   454  		t.Fatal("Couldn't recover long name")
   455  	}
   456  }
   457  
   458  func TestValidTypeflagWithPAXHeader(t *testing.T) {
   459  	var buffer bytes.Buffer
   460  	tw := NewWriter(&buffer)
   461  
   462  	fileName := strings.Repeat("ab", 100)
   463  
   464  	hdr := &Header{
   465  		Name:     fileName,
   466  		Size:     4,
   467  		Typeflag: 0,
   468  	}
   469  	if err := tw.WriteHeader(hdr); err != nil {
   470  		t.Fatalf("Failed to write header: %s", err)
   471  	}
   472  	if _, err := tw.Write([]byte("fooo")); err != nil {
   473  		t.Fatalf("Failed to write the file's data: %s", err)
   474  	}
   475  	tw.Close()
   476  
   477  	tr := NewReader(&buffer)
   478  
   479  	for {
   480  		header, err := tr.Next()
   481  		if err == io.EOF {
   482  			break
   483  		}
   484  		if err != nil {
   485  			t.Fatalf("Failed to read header: %s", err)
   486  		}
   487  		if header.Typeflag != 0 {
   488  			t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
   489  		}
   490  	}
   491  }