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