...

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

View as plain text