| // Copyright 2009 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | package tar | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"io/ioutil" | 
 | 	"math" | 
 | 	"os" | 
 | 	"reflect" | 
 | 	"sort" | 
 | 	"strings" | 
 | 	"testing" | 
 | 	"testing/iotest" | 
 | 	"time" | 
 | ) | 
 |  | 
 | type writerTestEntry struct { | 
 | 	header   *Header | 
 | 	contents string | 
 | } | 
 |  | 
 | type writerTest struct { | 
 | 	file    string // filename of expected output | 
 | 	entries []*writerTestEntry | 
 | } | 
 |  | 
 | var writerTests = []*writerTest{ | 
 | 	// The writer test file was produced with this command: | 
 | 	// tar (GNU tar) 1.26 | 
 | 	//   ln -s small.txt link.txt | 
 | 	//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt | 
 | 	{ | 
 | 		file: "testdata/writer.tar", | 
 | 		entries: []*writerTestEntry{ | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     "small.txt", | 
 | 					Mode:     0640, | 
 | 					Uid:      73025, | 
 | 					Gid:      5000, | 
 | 					Size:     5, | 
 | 					ModTime:  time.Unix(1246508266, 0), | 
 | 					Typeflag: '0', | 
 | 					Uname:    "dsymonds", | 
 | 					Gname:    "eng", | 
 | 				}, | 
 | 				contents: "Kilts", | 
 | 			}, | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     "small2.txt", | 
 | 					Mode:     0640, | 
 | 					Uid:      73025, | 
 | 					Gid:      5000, | 
 | 					Size:     11, | 
 | 					ModTime:  time.Unix(1245217492, 0), | 
 | 					Typeflag: '0', | 
 | 					Uname:    "dsymonds", | 
 | 					Gname:    "eng", | 
 | 				}, | 
 | 				contents: "Google.com\n", | 
 | 			}, | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     "link.txt", | 
 | 					Mode:     0777, | 
 | 					Uid:      1000, | 
 | 					Gid:      1000, | 
 | 					Size:     0, | 
 | 					ModTime:  time.Unix(1314603082, 0), | 
 | 					Typeflag: '2', | 
 | 					Linkname: "small.txt", | 
 | 					Uname:    "strings", | 
 | 					Gname:    "strings", | 
 | 				}, | 
 | 				// no contents | 
 | 			}, | 
 | 		}, | 
 | 	}, | 
 | 	// The truncated test file was produced using these commands: | 
 | 	//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt | 
 | 	//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar | 
 | 	{ | 
 | 		file: "testdata/writer-big.tar", | 
 | 		entries: []*writerTestEntry{ | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     "tmp/16gig.txt", | 
 | 					Mode:     0640, | 
 | 					Uid:      73025, | 
 | 					Gid:      5000, | 
 | 					Size:     16 << 30, | 
 | 					ModTime:  time.Unix(1254699560, 0), | 
 | 					Typeflag: '0', | 
 | 					Uname:    "dsymonds", | 
 | 					Gname:    "eng", | 
 | 				}, | 
 | 				// fake contents | 
 | 				contents: strings.Repeat("\x00", 4<<10), | 
 | 			}, | 
 | 		}, | 
 | 	}, | 
 | 	// The truncated test file was produced using these commands: | 
 | 	//   dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt | 
 | 	//   tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar | 
 | 	{ | 
 | 		file: "testdata/writer-big-long.tar", | 
 | 		entries: []*writerTestEntry{ | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     strings.Repeat("longname/", 15) + "16gig.txt", | 
 | 					Mode:     0644, | 
 | 					Uid:      1000, | 
 | 					Gid:      1000, | 
 | 					Size:     16 << 30, | 
 | 					ModTime:  time.Unix(1399583047, 0), | 
 | 					Typeflag: '0', | 
 | 					Uname:    "guillaume", | 
 | 					Gname:    "guillaume", | 
 | 				}, | 
 | 				// fake contents | 
 | 				contents: strings.Repeat("\x00", 4<<10), | 
 | 			}, | 
 | 		}, | 
 | 	}, | 
 | 	// This file was produced using gnu tar 1.17 | 
 | 	// gnutar  -b 4 --format=ustar (longname/)*15 + file.txt | 
 | 	{ | 
 | 		file: "testdata/ustar.tar", | 
 | 		entries: []*writerTestEntry{ | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     strings.Repeat("longname/", 15) + "file.txt", | 
 | 					Mode:     0644, | 
 | 					Uid:      0765, | 
 | 					Gid:      024, | 
 | 					Size:     06, | 
 | 					ModTime:  time.Unix(1360135598, 0), | 
 | 					Typeflag: '0', | 
 | 					Uname:    "shane", | 
 | 					Gname:    "staff", | 
 | 				}, | 
 | 				contents: "hello\n", | 
 | 			}, | 
 | 		}, | 
 | 	}, | 
 | 	// This file was produced using gnu tar 1.26 | 
 | 	// echo "Slartibartfast" > file.txt | 
 | 	// ln file.txt hard.txt | 
 | 	// tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt | 
 | 	{ | 
 | 		file: "testdata/hardlink.tar", | 
 | 		entries: []*writerTestEntry{ | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     "file.txt", | 
 | 					Mode:     0644, | 
 | 					Uid:      1000, | 
 | 					Gid:      100, | 
 | 					Size:     15, | 
 | 					ModTime:  time.Unix(1425484303, 0), | 
 | 					Typeflag: '0', | 
 | 					Uname:    "vbatts", | 
 | 					Gname:    "users", | 
 | 				}, | 
 | 				contents: "Slartibartfast\n", | 
 | 			}, | 
 | 			{ | 
 | 				header: &Header{ | 
 | 					Name:     "hard.txt", | 
 | 					Mode:     0644, | 
 | 					Uid:      1000, | 
 | 					Gid:      100, | 
 | 					Size:     0, | 
 | 					ModTime:  time.Unix(1425484303, 0), | 
 | 					Typeflag: '1', | 
 | 					Linkname: "file.txt", | 
 | 					Uname:    "vbatts", | 
 | 					Gname:    "users", | 
 | 				}, | 
 | 				// no contents | 
 | 			}, | 
 | 		}, | 
 | 	}, | 
 | } | 
 |  | 
 | // Render byte array in a two-character hexadecimal string, spaced for easy visual inspection. | 
 | func bytestr(offset int, b []byte) string { | 
 | 	const rowLen = 32 | 
 | 	s := fmt.Sprintf("%04x ", offset) | 
 | 	for _, ch := range b { | 
 | 		switch { | 
 | 		case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z': | 
 | 			s += fmt.Sprintf("  %c", ch) | 
 | 		default: | 
 | 			s += fmt.Sprintf(" %02x", ch) | 
 | 		} | 
 | 	} | 
 | 	return s | 
 | } | 
 |  | 
 | // Render a pseudo-diff between two blocks of bytes. | 
 | func bytediff(a []byte, b []byte) string { | 
 | 	const rowLen = 32 | 
 | 	s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b)) | 
 | 	for offset := 0; len(a)+len(b) > 0; offset += rowLen { | 
 | 		na, nb := rowLen, rowLen | 
 | 		if na > len(a) { | 
 | 			na = len(a) | 
 | 		} | 
 | 		if nb > len(b) { | 
 | 			nb = len(b) | 
 | 		} | 
 | 		sa := bytestr(offset, a[0:na]) | 
 | 		sb := bytestr(offset, b[0:nb]) | 
 | 		if sa != sb { | 
 | 			s += fmt.Sprintf("-%v\n+%v\n", sa, sb) | 
 | 		} | 
 | 		a = a[na:] | 
 | 		b = b[nb:] | 
 | 	} | 
 | 	return s | 
 | } | 
 |  | 
 | func TestWriter(t *testing.T) { | 
 | testLoop: | 
 | 	for i, test := range writerTests { | 
 | 		expected, err := ioutil.ReadFile(test.file) | 
 | 		if err != nil { | 
 | 			t.Errorf("test %d: Unexpected error: %v", i, err) | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		buf := new(bytes.Buffer) | 
 | 		tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB | 
 | 		big := false | 
 | 		for j, entry := range test.entries { | 
 | 			big = big || entry.header.Size > 1<<10 | 
 | 			if err := tw.WriteHeader(entry.header); err != nil { | 
 | 				t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err) | 
 | 				continue testLoop | 
 | 			} | 
 | 			if _, err := io.WriteString(tw, entry.contents); err != nil { | 
 | 				t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err) | 
 | 				continue testLoop | 
 | 			} | 
 | 		} | 
 | 		// Only interested in Close failures for the small tests. | 
 | 		if err := tw.Close(); err != nil && !big { | 
 | 			t.Errorf("test %d: Failed closing archive: %v", i, err) | 
 | 			continue testLoop | 
 | 		} | 
 |  | 
 | 		actual := buf.Bytes() | 
 | 		if !bytes.Equal(expected, actual) { | 
 | 			t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v", | 
 | 				i, bytediff(expected, actual)) | 
 | 		} | 
 | 		if testing.Short() { // The second test is expensive. | 
 | 			break | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func TestPax(t *testing.T) { | 
 | 	// Create an archive with a large name | 
 | 	fileinfo, err := os.Stat("testdata/small.txt") | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	hdr, err := FileInfoHeader(fileinfo, "") | 
 | 	if err != nil { | 
 | 		t.Fatalf("os.Stat: %v", err) | 
 | 	} | 
 | 	// Force a PAX long name to be written | 
 | 	longName := strings.Repeat("ab", 100) | 
 | 	contents := strings.Repeat(" ", int(hdr.Size)) | 
 | 	hdr.Name = longName | 
 | 	var buf bytes.Buffer | 
 | 	writer := NewWriter(&buf) | 
 | 	if err := writer.WriteHeader(hdr); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if _, err = writer.Write([]byte(contents)); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if err := writer.Close(); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	// Simple test to make sure PAX extensions are in effect | 
 | 	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { | 
 | 		t.Fatal("Expected at least one PAX header to be written.") | 
 | 	} | 
 | 	// Test that we can get a long name back out of the archive. | 
 | 	reader := NewReader(&buf) | 
 | 	hdr, err = reader.Next() | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if hdr.Name != longName { | 
 | 		t.Fatal("Couldn't recover long file name") | 
 | 	} | 
 | } | 
 |  | 
 | func TestPaxSymlink(t *testing.T) { | 
 | 	// Create an archive with a large linkname | 
 | 	fileinfo, err := os.Stat("testdata/small.txt") | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	hdr, err := FileInfoHeader(fileinfo, "") | 
 | 	hdr.Typeflag = TypeSymlink | 
 | 	if err != nil { | 
 | 		t.Fatalf("os.Stat:1 %v", err) | 
 | 	} | 
 | 	// Force a PAX long linkname to be written | 
 | 	longLinkname := strings.Repeat("1234567890/1234567890", 10) | 
 | 	hdr.Linkname = longLinkname | 
 |  | 
 | 	hdr.Size = 0 | 
 | 	var buf bytes.Buffer | 
 | 	writer := NewWriter(&buf) | 
 | 	if err := writer.WriteHeader(hdr); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if err := writer.Close(); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	// Simple test to make sure PAX extensions are in effect | 
 | 	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { | 
 | 		t.Fatal("Expected at least one PAX header to be written.") | 
 | 	} | 
 | 	// Test that we can get a long name back out of the archive. | 
 | 	reader := NewReader(&buf) | 
 | 	hdr, err = reader.Next() | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if hdr.Linkname != longLinkname { | 
 | 		t.Fatal("Couldn't recover long link name") | 
 | 	} | 
 | } | 
 |  | 
 | func TestPaxNonAscii(t *testing.T) { | 
 | 	// Create an archive with non ascii. These should trigger a pax header | 
 | 	// because pax headers have a defined utf-8 encoding. | 
 | 	fileinfo, err := os.Stat("testdata/small.txt") | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 |  | 
 | 	hdr, err := FileInfoHeader(fileinfo, "") | 
 | 	if err != nil { | 
 | 		t.Fatalf("os.Stat:1 %v", err) | 
 | 	} | 
 |  | 
 | 	// some sample data | 
 | 	chineseFilename := "文件名" | 
 | 	chineseGroupname := "組" | 
 | 	chineseUsername := "用戶名" | 
 |  | 
 | 	hdr.Name = chineseFilename | 
 | 	hdr.Gname = chineseGroupname | 
 | 	hdr.Uname = chineseUsername | 
 |  | 
 | 	contents := strings.Repeat(" ", int(hdr.Size)) | 
 |  | 
 | 	var buf bytes.Buffer | 
 | 	writer := NewWriter(&buf) | 
 | 	if err := writer.WriteHeader(hdr); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if _, err = writer.Write([]byte(contents)); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if err := writer.Close(); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	// Simple test to make sure PAX extensions are in effect | 
 | 	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { | 
 | 		t.Fatal("Expected at least one PAX header to be written.") | 
 | 	} | 
 | 	// Test that we can get a long name back out of the archive. | 
 | 	reader := NewReader(&buf) | 
 | 	hdr, err = reader.Next() | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if hdr.Name != chineseFilename { | 
 | 		t.Fatal("Couldn't recover unicode name") | 
 | 	} | 
 | 	if hdr.Gname != chineseGroupname { | 
 | 		t.Fatal("Couldn't recover unicode group") | 
 | 	} | 
 | 	if hdr.Uname != chineseUsername { | 
 | 		t.Fatal("Couldn't recover unicode user") | 
 | 	} | 
 | } | 
 |  | 
 | func TestPaxXattrs(t *testing.T) { | 
 | 	xattrs := map[string]string{ | 
 | 		"user.key": "value", | 
 | 	} | 
 |  | 
 | 	// Create an archive with an xattr | 
 | 	fileinfo, err := os.Stat("testdata/small.txt") | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	hdr, err := FileInfoHeader(fileinfo, "") | 
 | 	if err != nil { | 
 | 		t.Fatalf("os.Stat: %v", err) | 
 | 	} | 
 | 	contents := "Kilts" | 
 | 	hdr.Xattrs = xattrs | 
 | 	var buf bytes.Buffer | 
 | 	writer := NewWriter(&buf) | 
 | 	if err := writer.WriteHeader(hdr); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if _, err = writer.Write([]byte(contents)); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if err := writer.Close(); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	// Test that we can get the xattrs back out of the archive. | 
 | 	reader := NewReader(&buf) | 
 | 	hdr, err = reader.Next() | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if !reflect.DeepEqual(hdr.Xattrs, xattrs) { | 
 | 		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", | 
 | 			hdr.Xattrs, xattrs) | 
 | 	} | 
 | } | 
 |  | 
 | func TestPaxHeadersSorted(t *testing.T) { | 
 | 	fileinfo, err := os.Stat("testdata/small.txt") | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	hdr, err := FileInfoHeader(fileinfo, "") | 
 | 	if err != nil { | 
 | 		t.Fatalf("os.Stat: %v", err) | 
 | 	} | 
 | 	contents := strings.Repeat(" ", int(hdr.Size)) | 
 |  | 
 | 	hdr.Xattrs = map[string]string{ | 
 | 		"foo": "foo", | 
 | 		"bar": "bar", | 
 | 		"baz": "baz", | 
 | 		"qux": "qux", | 
 | 	} | 
 |  | 
 | 	var buf bytes.Buffer | 
 | 	writer := NewWriter(&buf) | 
 | 	if err := writer.WriteHeader(hdr); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if _, err = writer.Write([]byte(contents)); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if err := writer.Close(); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	// Simple test to make sure PAX extensions are in effect | 
 | 	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { | 
 | 		t.Fatal("Expected at least one PAX header to be written.") | 
 | 	} | 
 |  | 
 | 	// xattr bar should always appear before others | 
 | 	indices := []int{ | 
 | 		bytes.Index(buf.Bytes(), []byte("bar=bar")), | 
 | 		bytes.Index(buf.Bytes(), []byte("baz=baz")), | 
 | 		bytes.Index(buf.Bytes(), []byte("foo=foo")), | 
 | 		bytes.Index(buf.Bytes(), []byte("qux=qux")), | 
 | 	} | 
 | 	if !sort.IntsAreSorted(indices) { | 
 | 		t.Fatal("PAX headers are not sorted") | 
 | 	} | 
 | } | 
 |  | 
 | func TestUSTARLongName(t *testing.T) { | 
 | 	// Create an archive with a path that failed to split with USTAR extension in previous versions. | 
 | 	fileinfo, err := os.Stat("testdata/small.txt") | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	hdr, err := FileInfoHeader(fileinfo, "") | 
 | 	hdr.Typeflag = TypeDir | 
 | 	if err != nil { | 
 | 		t.Fatalf("os.Stat:1 %v", err) | 
 | 	} | 
 | 	// Force a PAX long name to be written. The name was taken from a practical example | 
 | 	// that fails and replaced ever char through numbers to anonymize the sample. | 
 | 	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/" | 
 | 	hdr.Name = longName | 
 |  | 
 | 	hdr.Size = 0 | 
 | 	var buf bytes.Buffer | 
 | 	writer := NewWriter(&buf) | 
 | 	if err := writer.WriteHeader(hdr); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if err := writer.Close(); err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	// Test that we can get a long name back out of the archive. | 
 | 	reader := NewReader(&buf) | 
 | 	hdr, err = reader.Next() | 
 | 	if err != nil { | 
 | 		t.Fatal(err) | 
 | 	} | 
 | 	if hdr.Name != longName { | 
 | 		t.Fatal("Couldn't recover long name") | 
 | 	} | 
 | } | 
 |  | 
 | func TestValidTypeflagWithPAXHeader(t *testing.T) { | 
 | 	var buffer bytes.Buffer | 
 | 	tw := NewWriter(&buffer) | 
 |  | 
 | 	fileName := strings.Repeat("ab", 100) | 
 |  | 
 | 	hdr := &Header{ | 
 | 		Name:     fileName, | 
 | 		Size:     4, | 
 | 		Typeflag: 0, | 
 | 	} | 
 | 	if err := tw.WriteHeader(hdr); err != nil { | 
 | 		t.Fatalf("Failed to write header: %s", err) | 
 | 	} | 
 | 	if _, err := tw.Write([]byte("fooo")); err != nil { | 
 | 		t.Fatalf("Failed to write the file's data: %s", err) | 
 | 	} | 
 | 	tw.Close() | 
 |  | 
 | 	tr := NewReader(&buffer) | 
 |  | 
 | 	for { | 
 | 		header, err := tr.Next() | 
 | 		if err == io.EOF { | 
 | 			break | 
 | 		} | 
 | 		if err != nil { | 
 | 			t.Fatalf("Failed to read header: %s", err) | 
 | 		} | 
 | 		if header.Typeflag != 0 { | 
 | 			t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func TestWriteAfterClose(t *testing.T) { | 
 | 	var buffer bytes.Buffer | 
 | 	tw := NewWriter(&buffer) | 
 |  | 
 | 	hdr := &Header{ | 
 | 		Name: "small.txt", | 
 | 		Size: 5, | 
 | 	} | 
 | 	if err := tw.WriteHeader(hdr); err != nil { | 
 | 		t.Fatalf("Failed to write header: %s", err) | 
 | 	} | 
 | 	tw.Close() | 
 | 	if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose { | 
 | 		t.Fatalf("Write: got %v; want ErrWriteAfterClose", err) | 
 | 	} | 
 | } | 
 |  | 
 | func TestSplitUSTARPath(t *testing.T) { | 
 | 	var sr = strings.Repeat | 
 |  | 
 | 	var vectors = []struct { | 
 | 		input  string // Input path | 
 | 		prefix string // Expected output prefix | 
 | 		suffix string // Expected output suffix | 
 | 		ok     bool   // Split success? | 
 | 	}{ | 
 | 		{"", "", "", false}, | 
 | 		{"abc", "", "", false}, | 
 | 		{"用戶名", "", "", false}, | 
 | 		{sr("a", fileNameSize), "", "", false}, | 
 | 		{sr("a", fileNameSize) + "/", "", "", false}, | 
 | 		{sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true}, | 
 | 		{sr("a", fileNamePrefixSize) + "/", "", "", false}, | 
 | 		{sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true}, | 
 | 		{sr("a", fileNameSize+1), "", "", false}, | 
 | 		{sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true}, | 
 | 		{sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize), | 
 | 			sr("a", fileNamePrefixSize), sr("b", fileNameSize), true}, | 
 | 		{sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false}, | 
 | 		{sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true}, | 
 | 	} | 
 |  | 
 | 	for _, v := range vectors { | 
 | 		prefix, suffix, ok := splitUSTARPath(v.input) | 
 | 		if prefix != v.prefix || suffix != v.suffix || ok != v.ok { | 
 | 			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)", | 
 | 				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func TestFormatPAXRecord(t *testing.T) { | 
 | 	var medName = strings.Repeat("CD", 50) | 
 | 	var longName = strings.Repeat("AB", 100) | 
 |  | 
 | 	var vectors = []struct { | 
 | 		inputKey string | 
 | 		inputVal string | 
 | 		output   string | 
 | 	}{ | 
 | 		{"k", "v", "6 k=v\n"}, | 
 | 		{"path", "/etc/hosts", "19 path=/etc/hosts\n"}, | 
 | 		{"path", longName, "210 path=" + longName + "\n"}, | 
 | 		{"path", medName, "110 path=" + medName + "\n"}, | 
 | 		{"foo", "ba", "9 foo=ba\n"}, | 
 | 		{"foo", "bar", "11 foo=bar\n"}, | 
 | 		{"foo", "b=\nar=\n==\x00", "18 foo=b=\nar=\n==\x00\n"}, | 
 | 		{"foo", "hello9 foo=ba\nworld", "27 foo=hello9 foo=ba\nworld\n"}, | 
 | 		{"☺☻☹", "日a本b語ç", "27 ☺☻☹=日a本b語ç\n"}, | 
 | 		{"\x00hello", "\x00world", "17 \x00hello=\x00world\n"}, | 
 | 	} | 
 |  | 
 | 	for _, v := range vectors { | 
 | 		output := formatPAXRecord(v.inputKey, v.inputVal) | 
 | 		if output != v.output { | 
 | 			t.Errorf("formatPAXRecord(%q, %q): got %q, want %q", | 
 | 				v.inputKey, v.inputVal, output, v.output) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func TestFitsInBase256(t *testing.T) { | 
 | 	var vectors = []struct { | 
 | 		input int64 | 
 | 		width int | 
 | 		ok    bool | 
 | 	}{ | 
 | 		{+1, 8, true}, | 
 | 		{0, 8, true}, | 
 | 		{-1, 8, true}, | 
 | 		{1 << 56, 8, false}, | 
 | 		{(1 << 56) - 1, 8, true}, | 
 | 		{-1 << 56, 8, true}, | 
 | 		{(-1 << 56) - 1, 8, false}, | 
 | 		{121654, 8, true}, | 
 | 		{-9849849, 8, true}, | 
 | 		{math.MaxInt64, 9, true}, | 
 | 		{0, 9, true}, | 
 | 		{math.MinInt64, 9, true}, | 
 | 		{math.MaxInt64, 12, true}, | 
 | 		{0, 12, true}, | 
 | 		{math.MinInt64, 12, true}, | 
 | 	} | 
 |  | 
 | 	for _, v := range vectors { | 
 | 		ok := fitsInBase256(v.width, v.input) | 
 | 		if ok != v.ok { | 
 | 			t.Errorf("checkNumeric(%d, %d): got %v, want %v", v.input, v.width, ok, v.ok) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func TestFormatNumeric(t *testing.T) { | 
 | 	var vectors = []struct { | 
 | 		input  int64 | 
 | 		output string | 
 | 		ok     bool | 
 | 	}{ | 
 | 		// Test base-256 (binary) encoded values. | 
 | 		{-1, "\xff", true}, | 
 | 		{-1, "\xff\xff", true}, | 
 | 		{-1, "\xff\xff\xff", true}, | 
 | 		{(1 << 0), "0", false}, | 
 | 		{(1 << 8) - 1, "\x80\xff", true}, | 
 | 		{(1 << 8), "0\x00", false}, | 
 | 		{(1 << 16) - 1, "\x80\xff\xff", true}, | 
 | 		{(1 << 16), "00\x00", false}, | 
 | 		{-1 * (1 << 0), "\xff", true}, | 
 | 		{-1*(1<<0) - 1, "0", false}, | 
 | 		{-1 * (1 << 8), "\xff\x00", true}, | 
 | 		{-1*(1<<8) - 1, "0\x00", false}, | 
 | 		{-1 * (1 << 16), "\xff\x00\x00", true}, | 
 | 		{-1*(1<<16) - 1, "00\x00", false}, | 
 | 		{537795476381659745, "0000000\x00", false}, | 
 | 		{537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true}, | 
 | 		{-615126028225187231, "0000000\x00", false}, | 
 | 		{-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true}, | 
 | 		{math.MaxInt64, "0000000\x00", false}, | 
 | 		{math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true}, | 
 | 		{math.MinInt64, "0000000\x00", false}, | 
 | 		{math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true}, | 
 | 		{math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true}, | 
 | 		{math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true}, | 
 | 	} | 
 |  | 
 | 	for _, v := range vectors { | 
 | 		var f formatter | 
 | 		output := make([]byte, len(v.output)) | 
 | 		f.formatNumeric(output, v.input) | 
 | 		ok := (f.err == nil) | 
 | 		if ok != v.ok { | 
 | 			if v.ok { | 
 | 				t.Errorf("formatNumeric(%d): got formatting failure, want success", v.input) | 
 | 			} else { | 
 | 				t.Errorf("formatNumeric(%d): got formatting success, want failure", v.input) | 
 | 			} | 
 | 		} | 
 | 		if string(output) != v.output { | 
 | 			t.Errorf("formatNumeric(%d): got %q, want %q", v.input, output, v.output) | 
 | 		} | 
 | 	} | 
 | } |