...

Source file src/github.com/vbatts/tar-split/archive/tar/tar_test.go

Documentation: github.com/vbatts/tar-split/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  	"io"
    12  	"math"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"reflect"
    17  	"runtime"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  )
    22  
    23  type testError struct{ error }
    24  
    25  type fileOps []interface{} // []T where T is (string | int64)
    26  
    27  // testFile is an io.ReadWriteSeeker where the IO operations performed
    28  // on it must match the list of operations in ops.
    29  type testFile struct {
    30  	ops fileOps
    31  	pos int64
    32  }
    33  
    34  func (f *testFile) Read(b []byte) (int, error) {
    35  	if len(b) == 0 {
    36  		return 0, nil
    37  	}
    38  	if len(f.ops) == 0 {
    39  		return 0, io.EOF
    40  	}
    41  	s, ok := f.ops[0].(string)
    42  	if !ok {
    43  		return 0, errors.New("unexpected Read operation")
    44  	}
    45  
    46  	n := copy(b, s)
    47  	if len(s) > n {
    48  		f.ops[0] = s[n:]
    49  	} else {
    50  		f.ops = f.ops[1:]
    51  	}
    52  	f.pos += int64(len(b))
    53  	return n, nil
    54  }
    55  
    56  func (f *testFile) Write(b []byte) (int, error) {
    57  	if len(b) == 0 {
    58  		return 0, nil
    59  	}
    60  	if len(f.ops) == 0 {
    61  		return 0, errors.New("unexpected Write operation")
    62  	}
    63  	s, ok := f.ops[0].(string)
    64  	if !ok {
    65  		return 0, errors.New("unexpected Write operation")
    66  	}
    67  
    68  	if !strings.HasPrefix(s, string(b)) {
    69  		return 0, testError{fmt.Errorf("got Write(%q), want Write(%q)", b, s)}
    70  	}
    71  	if len(s) > len(b) {
    72  		f.ops[0] = s[len(b):]
    73  	} else {
    74  		f.ops = f.ops[1:]
    75  	}
    76  	f.pos += int64(len(b))
    77  	return len(b), nil
    78  }
    79  
    80  func (f *testFile) Seek(pos int64, whence int) (int64, error) {
    81  	if pos == 0 && whence == io.SeekCurrent {
    82  		return f.pos, nil
    83  	}
    84  	if len(f.ops) == 0 {
    85  		return 0, errors.New("unexpected Seek operation")
    86  	}
    87  	s, ok := f.ops[0].(int64)
    88  	if !ok {
    89  		return 0, errors.New("unexpected Seek operation")
    90  	}
    91  
    92  	if s != pos || whence != io.SeekCurrent {
    93  		return 0, testError{fmt.Errorf("got Seek(%d, %d), want Seek(%d, %d)", pos, whence, s, io.SeekCurrent)}
    94  	}
    95  	f.pos += s
    96  	f.ops = f.ops[1:]
    97  	return f.pos, nil
    98  }
    99  
   100  func equalSparseEntries(x, y []sparseEntry) bool {
   101  	return (len(x) == 0 && len(y) == 0) || reflect.DeepEqual(x, y)
   102  }
   103  
   104  func TestSparseEntries(t *testing.T) {
   105  	vectors := []struct {
   106  		in   []sparseEntry
   107  		size int64
   108  
   109  		wantValid    bool          // Result of validateSparseEntries
   110  		wantAligned  []sparseEntry // Result of alignSparseEntries
   111  		wantInverted []sparseEntry // Result of invertSparseEntries
   112  	}{{
   113  		in: []sparseEntry{}, size: 0,
   114  		wantValid:    true,
   115  		wantInverted: []sparseEntry{{0, 0}},
   116  	}, {
   117  		in: []sparseEntry{}, size: 5000,
   118  		wantValid:    true,
   119  		wantInverted: []sparseEntry{{0, 5000}},
   120  	}, {
   121  		in: []sparseEntry{{0, 5000}}, size: 5000,
   122  		wantValid:    true,
   123  		wantAligned:  []sparseEntry{{0, 5000}},
   124  		wantInverted: []sparseEntry{{5000, 0}},
   125  	}, {
   126  		in: []sparseEntry{{1000, 4000}}, size: 5000,
   127  		wantValid:    true,
   128  		wantAligned:  []sparseEntry{{1024, 3976}},
   129  		wantInverted: []sparseEntry{{0, 1000}, {5000, 0}},
   130  	}, {
   131  		in: []sparseEntry{{0, 3000}}, size: 5000,
   132  		wantValid:    true,
   133  		wantAligned:  []sparseEntry{{0, 2560}},
   134  		wantInverted: []sparseEntry{{3000, 2000}},
   135  	}, {
   136  		in: []sparseEntry{{3000, 2000}}, size: 5000,
   137  		wantValid:    true,
   138  		wantAligned:  []sparseEntry{{3072, 1928}},
   139  		wantInverted: []sparseEntry{{0, 3000}, {5000, 0}},
   140  	}, {
   141  		in: []sparseEntry{{2000, 2000}}, size: 5000,
   142  		wantValid:    true,
   143  		wantAligned:  []sparseEntry{{2048, 1536}},
   144  		wantInverted: []sparseEntry{{0, 2000}, {4000, 1000}},
   145  	}, {
   146  		in: []sparseEntry{{0, 2000}, {8000, 2000}}, size: 10000,
   147  		wantValid:    true,
   148  		wantAligned:  []sparseEntry{{0, 1536}, {8192, 1808}},
   149  		wantInverted: []sparseEntry{{2000, 6000}, {10000, 0}},
   150  	}, {
   151  		in: []sparseEntry{{0, 2000}, {2000, 2000}, {4000, 0}, {4000, 3000}, {7000, 1000}, {8000, 0}, {8000, 2000}}, size: 10000,
   152  		wantValid:    true,
   153  		wantAligned:  []sparseEntry{{0, 1536}, {2048, 1536}, {4096, 2560}, {7168, 512}, {8192, 1808}},
   154  		wantInverted: []sparseEntry{{10000, 0}},
   155  	}, {
   156  		in: []sparseEntry{{0, 0}, {1000, 0}, {2000, 0}, {3000, 0}, {4000, 0}, {5000, 0}}, size: 5000,
   157  		wantValid:    true,
   158  		wantInverted: []sparseEntry{{0, 5000}},
   159  	}, {
   160  		in: []sparseEntry{{1, 0}}, size: 0,
   161  		wantValid: false,
   162  	}, {
   163  		in: []sparseEntry{{-1, 0}}, size: 100,
   164  		wantValid: false,
   165  	}, {
   166  		in: []sparseEntry{{0, -1}}, size: 100,
   167  		wantValid: false,
   168  	}, {
   169  		in: []sparseEntry{{0, 0}}, size: -100,
   170  		wantValid: false,
   171  	}, {
   172  		in: []sparseEntry{{math.MaxInt64, 3}, {6, -5}}, size: 35,
   173  		wantValid: false,
   174  	}, {
   175  		in: []sparseEntry{{1, 3}, {6, -5}}, size: 35,
   176  		wantValid: false,
   177  	}, {
   178  		in: []sparseEntry{{math.MaxInt64, math.MaxInt64}}, size: math.MaxInt64,
   179  		wantValid: false,
   180  	}, {
   181  		in: []sparseEntry{{3, 3}}, size: 5,
   182  		wantValid: false,
   183  	}, {
   184  		in: []sparseEntry{{2, 0}, {1, 0}, {0, 0}}, size: 3,
   185  		wantValid: false,
   186  	}, {
   187  		in: []sparseEntry{{1, 3}, {2, 2}}, size: 10,
   188  		wantValid: false,
   189  	}}
   190  
   191  	for i, v := range vectors {
   192  		gotValid := validateSparseEntries(v.in, v.size)
   193  		if gotValid != v.wantValid {
   194  			t.Errorf("test %d, validateSparseEntries() = %v, want %v", i, gotValid, v.wantValid)
   195  		}
   196  		if !v.wantValid {
   197  			continue
   198  		}
   199  		gotAligned := alignSparseEntries(append([]sparseEntry{}, v.in...), v.size)
   200  		if !equalSparseEntries(gotAligned, v.wantAligned) {
   201  			t.Errorf("test %d, alignSparseEntries():\ngot  %v\nwant %v", i, gotAligned, v.wantAligned)
   202  		}
   203  		gotInverted := invertSparseEntries(append([]sparseEntry{}, v.in...), v.size)
   204  		if !equalSparseEntries(gotInverted, v.wantInverted) {
   205  			t.Errorf("test %d, inverseSparseEntries():\ngot  %v\nwant %v", i, gotInverted, v.wantInverted)
   206  		}
   207  	}
   208  }
   209  
   210  func TestFileInfoHeader(t *testing.T) {
   211  	fi, err := os.Stat("testdata/small.txt")
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	h, err := FileInfoHeader(fi, "")
   216  	if err != nil {
   217  		t.Fatalf("FileInfoHeader: %v", err)
   218  	}
   219  	if g, e := h.Name, "small.txt"; g != e {
   220  		t.Errorf("Name = %q; want %q", g, e)
   221  	}
   222  	if g, e := h.Mode, int64(fi.Mode().Perm()); g != e {
   223  		t.Errorf("Mode = %#o; want %#o", g, e)
   224  	}
   225  	if g, e := h.Size, int64(5); g != e {
   226  		t.Errorf("Size = %v; want %v", g, e)
   227  	}
   228  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
   229  		t.Errorf("ModTime = %v; want %v", g, e)
   230  	}
   231  	// FileInfoHeader should error when passing nil FileInfo
   232  	if _, err := FileInfoHeader(nil, ""); err == nil {
   233  		t.Fatalf("Expected error when passing nil to FileInfoHeader")
   234  	}
   235  }
   236  
   237  func TestFileInfoHeaderDir(t *testing.T) {
   238  	fi, err := os.Stat("testdata")
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  	h, err := FileInfoHeader(fi, "")
   243  	if err != nil {
   244  		t.Fatalf("FileInfoHeader: %v", err)
   245  	}
   246  	if g, e := h.Name, "testdata/"; g != e {
   247  		t.Errorf("Name = %q; want %q", g, e)
   248  	}
   249  	// Ignoring c_ISGID for golang.org/issue/4867
   250  	if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e {
   251  		t.Errorf("Mode = %#o; want %#o", g, e)
   252  	}
   253  	if g, e := h.Size, int64(0); g != e {
   254  		t.Errorf("Size = %v; want %v", g, e)
   255  	}
   256  	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
   257  		t.Errorf("ModTime = %v; want %v", g, e)
   258  	}
   259  }
   260  
   261  func TestFileInfoHeaderSymlink(t *testing.T) {
   262  	switch runtime.GOOS {
   263  	case "android", "nacl", "plan9", "windows":
   264  		t.Skip("symlinks not supported")
   265  	}
   266  	tmpdir, err := os.MkdirTemp("", "TestFileInfoHeaderSymlink")
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	defer os.RemoveAll(tmpdir)
   271  
   272  	link := filepath.Join(tmpdir, "link")
   273  	target := tmpdir
   274  	err = os.Symlink(target, link)
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	fi, err := os.Lstat(link)
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  
   283  	h, err := FileInfoHeader(fi, target)
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	if g, e := h.Name, fi.Name(); g != e {
   288  		t.Errorf("Name = %q; want %q", g, e)
   289  	}
   290  	if g, e := h.Linkname, target; g != e {
   291  		t.Errorf("Linkname = %q; want %q", g, e)
   292  	}
   293  	if g, e := h.Typeflag, byte(TypeSymlink); g != e {
   294  		t.Errorf("Typeflag = %v; want %v", g, e)
   295  	}
   296  }
   297  
   298  func TestRoundTrip(t *testing.T) {
   299  	data := []byte("some file contents")
   300  
   301  	var b bytes.Buffer
   302  	tw := NewWriter(&b)
   303  	hdr := &Header{
   304  		Name:       "file.txt",
   305  		Uid:        1 << 21, // Too big for 8 octal digits
   306  		Size:       int64(len(data)),
   307  		ModTime:    time.Now().Round(time.Second),
   308  		PAXRecords: map[string]string{"uid": "2097152"},
   309  		Format:     FormatPAX,
   310  		Typeflag:   TypeReg,
   311  	}
   312  	if err := tw.WriteHeader(hdr); err != nil {
   313  		t.Fatalf("tw.WriteHeader: %v", err)
   314  	}
   315  	if _, err := tw.Write(data); err != nil {
   316  		t.Fatalf("tw.Write: %v", err)
   317  	}
   318  	if err := tw.Close(); err != nil {
   319  		t.Fatalf("tw.Close: %v", err)
   320  	}
   321  
   322  	// Read it back.
   323  	tr := NewReader(&b)
   324  	rHdr, err := tr.Next()
   325  	if err != nil {
   326  		t.Fatalf("tr.Next: %v", err)
   327  	}
   328  	if !reflect.DeepEqual(rHdr, hdr) {
   329  		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
   330  	}
   331  	rData, err := io.ReadAll(tr)
   332  	if err != nil {
   333  		t.Fatalf("Read: %v", err)
   334  	}
   335  	if !bytes.Equal(rData, data) {
   336  		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
   337  	}
   338  }
   339  
   340  type headerRoundTripTest struct {
   341  	h  *Header
   342  	fm os.FileMode
   343  }
   344  
   345  func TestHeaderRoundTrip(t *testing.T) {
   346  	vectors := []headerRoundTripTest{{
   347  		// regular file.
   348  		h: &Header{
   349  			Name:     "test.txt",
   350  			Mode:     0644,
   351  			Size:     12,
   352  			ModTime:  time.Unix(1360600916, 0),
   353  			Typeflag: TypeReg,
   354  		},
   355  		fm: 0644,
   356  	}, {
   357  		// symbolic link.
   358  		h: &Header{
   359  			Name:     "link.txt",
   360  			Mode:     0777,
   361  			Size:     0,
   362  			ModTime:  time.Unix(1360600852, 0),
   363  			Typeflag: TypeSymlink,
   364  		},
   365  		fm: 0777 | os.ModeSymlink,
   366  	}, {
   367  		// character device node.
   368  		h: &Header{
   369  			Name:     "dev/null",
   370  			Mode:     0666,
   371  			Size:     0,
   372  			ModTime:  time.Unix(1360578951, 0),
   373  			Typeflag: TypeChar,
   374  		},
   375  		fm: 0666 | os.ModeDevice | os.ModeCharDevice,
   376  	}, {
   377  		// block device node.
   378  		h: &Header{
   379  			Name:     "dev/sda",
   380  			Mode:     0660,
   381  			Size:     0,
   382  			ModTime:  time.Unix(1360578954, 0),
   383  			Typeflag: TypeBlock,
   384  		},
   385  		fm: 0660 | os.ModeDevice,
   386  	}, {
   387  		// directory.
   388  		h: &Header{
   389  			Name:     "dir/",
   390  			Mode:     0755,
   391  			Size:     0,
   392  			ModTime:  time.Unix(1360601116, 0),
   393  			Typeflag: TypeDir,
   394  		},
   395  		fm: 0755 | os.ModeDir,
   396  	}, {
   397  		// fifo node.
   398  		h: &Header{
   399  			Name:     "dev/initctl",
   400  			Mode:     0600,
   401  			Size:     0,
   402  			ModTime:  time.Unix(1360578949, 0),
   403  			Typeflag: TypeFifo,
   404  		},
   405  		fm: 0600 | os.ModeNamedPipe,
   406  	}, {
   407  		// setuid.
   408  		h: &Header{
   409  			Name:     "bin/su",
   410  			Mode:     0755 | c_ISUID,
   411  			Size:     23232,
   412  			ModTime:  time.Unix(1355405093, 0),
   413  			Typeflag: TypeReg,
   414  		},
   415  		fm: 0755 | os.ModeSetuid,
   416  	}, {
   417  		// setguid.
   418  		h: &Header{
   419  			Name:     "group.txt",
   420  			Mode:     0750 | c_ISGID,
   421  			Size:     0,
   422  			ModTime:  time.Unix(1360602346, 0),
   423  			Typeflag: TypeReg,
   424  		},
   425  		fm: 0750 | os.ModeSetgid,
   426  	}, {
   427  		// sticky.
   428  		h: &Header{
   429  			Name:     "sticky.txt",
   430  			Mode:     0600 | c_ISVTX,
   431  			Size:     7,
   432  			ModTime:  time.Unix(1360602540, 0),
   433  			Typeflag: TypeReg,
   434  		},
   435  		fm: 0600 | os.ModeSticky,
   436  	}, {
   437  		// hard link.
   438  		h: &Header{
   439  			Name:     "hard.txt",
   440  			Mode:     0644,
   441  			Size:     0,
   442  			Linkname: "file.txt",
   443  			ModTime:  time.Unix(1360600916, 0),
   444  			Typeflag: TypeLink,
   445  		},
   446  		fm: 0644,
   447  	}, {
   448  		// More information.
   449  		h: &Header{
   450  			Name:     "info.txt",
   451  			Mode:     0600,
   452  			Size:     0,
   453  			Uid:      1000,
   454  			Gid:      1000,
   455  			ModTime:  time.Unix(1360602540, 0),
   456  			Uname:    "slartibartfast",
   457  			Gname:    "users",
   458  			Typeflag: TypeReg,
   459  		},
   460  		fm: 0600,
   461  	}}
   462  
   463  	for i, v := range vectors {
   464  		fi := v.h.FileInfo()
   465  		h2, err := FileInfoHeader(fi, "")
   466  		if err != nil {
   467  			t.Error(err)
   468  			continue
   469  		}
   470  		if strings.Contains(fi.Name(), "/") {
   471  			t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name())
   472  		}
   473  		name := path.Base(v.h.Name)
   474  		if fi.IsDir() {
   475  			name += "/"
   476  		}
   477  		if got, want := h2.Name, name; got != want {
   478  			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
   479  		}
   480  		if got, want := h2.Size, v.h.Size; got != want {
   481  			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
   482  		}
   483  		if got, want := h2.Uid, v.h.Uid; got != want {
   484  			t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
   485  		}
   486  		if got, want := h2.Gid, v.h.Gid; got != want {
   487  			t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
   488  		}
   489  		if got, want := h2.Uname, v.h.Uname; got != want {
   490  			t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
   491  		}
   492  		if got, want := h2.Gname, v.h.Gname; got != want {
   493  			t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
   494  		}
   495  		if got, want := h2.Linkname, v.h.Linkname; got != want {
   496  			t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
   497  		}
   498  		if got, want := h2.Typeflag, v.h.Typeflag; got != want {
   499  			t.Logf("%#v %#v", v.h, fi.Sys())
   500  			t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
   501  		}
   502  		if got, want := h2.Mode, v.h.Mode; got != want {
   503  			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
   504  		}
   505  		if got, want := fi.Mode(), v.fm; got != want {
   506  			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
   507  		}
   508  		if got, want := h2.AccessTime, v.h.AccessTime; got != want {
   509  			t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
   510  		}
   511  		if got, want := h2.ChangeTime, v.h.ChangeTime; got != want {
   512  			t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
   513  		}
   514  		if got, want := h2.ModTime, v.h.ModTime; got != want {
   515  			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
   516  		}
   517  		if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h {
   518  			t.Errorf("i=%d: Sys didn't return original *Header", i)
   519  		}
   520  	}
   521  }
   522  
   523  func TestHeaderAllowedFormats(t *testing.T) {
   524  	vectors := []struct {
   525  		header  *Header           // Input header
   526  		paxHdrs map[string]string // Expected PAX headers that may be needed
   527  		formats Format            // Expected formats that can encode the header
   528  	}{{
   529  		header:  &Header{},
   530  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   531  	}, {
   532  		header:  &Header{Size: 077777777777},
   533  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   534  	}, {
   535  		header:  &Header{Size: 077777777777, Format: FormatUSTAR},
   536  		formats: FormatUSTAR,
   537  	}, {
   538  		header:  &Header{Size: 077777777777, Format: FormatPAX},
   539  		formats: FormatUSTAR | FormatPAX,
   540  	}, {
   541  		header:  &Header{Size: 077777777777, Format: FormatGNU},
   542  		formats: FormatGNU,
   543  	}, {
   544  		header:  &Header{Size: 077777777777 + 1},
   545  		paxHdrs: map[string]string{paxSize: "8589934592"},
   546  		formats: FormatPAX | FormatGNU,
   547  	}, {
   548  		header:  &Header{Size: 077777777777 + 1, Format: FormatPAX},
   549  		paxHdrs: map[string]string{paxSize: "8589934592"},
   550  		formats: FormatPAX,
   551  	}, {
   552  		header:  &Header{Size: 077777777777 + 1, Format: FormatGNU},
   553  		paxHdrs: map[string]string{paxSize: "8589934592"},
   554  		formats: FormatGNU,
   555  	}, {
   556  		header:  &Header{Mode: 07777777},
   557  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   558  	}, {
   559  		header:  &Header{Mode: 07777777 + 1},
   560  		formats: FormatGNU,
   561  	}, {
   562  		header:  &Header{Devmajor: -123},
   563  		formats: FormatGNU,
   564  	}, {
   565  		header:  &Header{Devmajor: 1<<56 - 1},
   566  		formats: FormatGNU,
   567  	}, {
   568  		header:  &Header{Devmajor: 1 << 56},
   569  		formats: FormatUnknown,
   570  	}, {
   571  		header:  &Header{Devmajor: -1 << 56},
   572  		formats: FormatGNU,
   573  	}, {
   574  		header:  &Header{Devmajor: -1<<56 - 1},
   575  		formats: FormatUnknown,
   576  	}, {
   577  		header:  &Header{Name: "用戶名", Devmajor: -1 << 56},
   578  		formats: FormatGNU,
   579  	}, {
   580  		header:  &Header{Size: math.MaxInt64},
   581  		paxHdrs: map[string]string{paxSize: "9223372036854775807"},
   582  		formats: FormatPAX | FormatGNU,
   583  	}, {
   584  		header:  &Header{Size: math.MinInt64},
   585  		paxHdrs: map[string]string{paxSize: "-9223372036854775808"},
   586  		formats: FormatUnknown,
   587  	}, {
   588  		header:  &Header{Uname: "0123456789abcdef0123456789abcdef"},
   589  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   590  	}, {
   591  		header:  &Header{Uname: "0123456789abcdef0123456789abcdefx"},
   592  		paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"},
   593  		formats: FormatPAX,
   594  	}, {
   595  		header:  &Header{Name: "foobar"},
   596  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   597  	}, {
   598  		header:  &Header{Name: strings.Repeat("a", nameSize)},
   599  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   600  	}, {
   601  		header:  &Header{Name: strings.Repeat("a", nameSize+1)},
   602  		paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)},
   603  		formats: FormatPAX | FormatGNU,
   604  	}, {
   605  		header:  &Header{Linkname: "用戶名"},
   606  		paxHdrs: map[string]string{paxLinkpath: "用戶名"},
   607  		formats: FormatPAX | FormatGNU,
   608  	}, {
   609  		header:  &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)},
   610  		paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)},
   611  		formats: FormatUnknown,
   612  	}, {
   613  		header:  &Header{Linkname: "\x00hello"},
   614  		paxHdrs: map[string]string{paxLinkpath: "\x00hello"},
   615  		formats: FormatUnknown,
   616  	}, {
   617  		header:  &Header{Uid: 07777777},
   618  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   619  	}, {
   620  		header:  &Header{Uid: 07777777 + 1},
   621  		paxHdrs: map[string]string{paxUid: "2097152"},
   622  		formats: FormatPAX | FormatGNU,
   623  	}, {
   624  		header:  &Header{Xattrs: nil},
   625  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   626  	}, {
   627  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}},
   628  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   629  		formats: FormatPAX,
   630  	}, {
   631  		header:  &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU},
   632  		paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"},
   633  		formats: FormatUnknown,
   634  	}, {
   635  		header:  &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}},
   636  		paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"},
   637  		formats: FormatPAX,
   638  	}, {
   639  		header:  &Header{Xattrs: map[string]string{"foo=bar": "baz"}},
   640  		formats: FormatUnknown,
   641  	}, {
   642  		header:  &Header{Xattrs: map[string]string{"foo": ""}},
   643  		paxHdrs: map[string]string{paxSchilyXattr + "foo": ""},
   644  		formats: FormatPAX,
   645  	}, {
   646  		header:  &Header{ModTime: time.Unix(0, 0)},
   647  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   648  	}, {
   649  		header:  &Header{ModTime: time.Unix(077777777777, 0)},
   650  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   651  	}, {
   652  		header:  &Header{ModTime: time.Unix(077777777777+1, 0)},
   653  		paxHdrs: map[string]string{paxMtime: "8589934592"},
   654  		formats: FormatPAX | FormatGNU,
   655  	}, {
   656  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0)},
   657  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   658  		formats: FormatPAX | FormatGNU,
   659  	}, {
   660  		header:  &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR},
   661  		paxHdrs: map[string]string{paxMtime: "9223372036854775807"},
   662  		formats: FormatUnknown,
   663  	}, {
   664  		header:  &Header{ModTime: time.Unix(-1, 0)},
   665  		paxHdrs: map[string]string{paxMtime: "-1"},
   666  		formats: FormatPAX | FormatGNU,
   667  	}, {
   668  		header:  &Header{ModTime: time.Unix(1, 500)},
   669  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   670  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   671  	}, {
   672  		header:  &Header{ModTime: time.Unix(1, 0)},
   673  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   674  	}, {
   675  		header:  &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
   676  		formats: FormatUSTAR | FormatPAX,
   677  	}, {
   678  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
   679  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   680  		formats: FormatUSTAR,
   681  	}, {
   682  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
   683  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   684  		formats: FormatPAX,
   685  	}, {
   686  		header:  &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
   687  		paxHdrs: map[string]string{paxMtime: "1.0000005"},
   688  		formats: FormatGNU,
   689  	}, {
   690  		header:  &Header{ModTime: time.Unix(-1, 500)},
   691  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   692  		formats: FormatPAX | FormatGNU,
   693  	}, {
   694  		header:  &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
   695  		paxHdrs: map[string]string{paxMtime: "-0.9999995"},
   696  		formats: FormatGNU,
   697  	}, {
   698  		header:  &Header{AccessTime: time.Unix(0, 0)},
   699  		paxHdrs: map[string]string{paxAtime: "0"},
   700  		formats: FormatPAX | FormatGNU,
   701  	}, {
   702  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
   703  		paxHdrs: map[string]string{paxAtime: "0"},
   704  		formats: FormatUnknown,
   705  	}, {
   706  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
   707  		paxHdrs: map[string]string{paxAtime: "0"},
   708  		formats: FormatPAX,
   709  	}, {
   710  		header:  &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
   711  		paxHdrs: map[string]string{paxAtime: "0"},
   712  		formats: FormatGNU,
   713  	}, {
   714  		header:  &Header{AccessTime: time.Unix(-123, 0)},
   715  		paxHdrs: map[string]string{paxAtime: "-123"},
   716  		formats: FormatPAX | FormatGNU,
   717  	}, {
   718  		header:  &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
   719  		paxHdrs: map[string]string{paxAtime: "-123"},
   720  		formats: FormatPAX,
   721  	}, {
   722  		header:  &Header{ChangeTime: time.Unix(123, 456)},
   723  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   724  		formats: FormatPAX | FormatGNU,
   725  	}, {
   726  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
   727  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   728  		formats: FormatUnknown,
   729  	}, {
   730  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
   731  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   732  		formats: FormatGNU,
   733  	}, {
   734  		header:  &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
   735  		paxHdrs: map[string]string{paxCtime: "123.000000456"},
   736  		formats: FormatPAX,
   737  	}, {
   738  		header:  &Header{Name: "foo/", Typeflag: TypeDir},
   739  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   740  	}, {
   741  		header:  &Header{Name: "foo/", Typeflag: TypeReg},
   742  		formats: FormatUnknown,
   743  	}, {
   744  		header:  &Header{Name: "foo/", Typeflag: TypeSymlink},
   745  		formats: FormatUSTAR | FormatPAX | FormatGNU,
   746  	}}
   747  
   748  	for i, v := range vectors {
   749  		formats, paxHdrs, err := v.header.allowedFormats()
   750  		if formats != v.formats {
   751  			t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats)
   752  		}
   753  		if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) {
   754  			t.Errorf("test %d, allowedFormats():\ngot  %v\nwant %s", i, paxHdrs, v.paxHdrs)
   755  		}
   756  		if (formats != FormatUnknown) && (err != nil) {
   757  			t.Errorf("test %d, unexpected error: %v", i, err)
   758  		}
   759  		if (formats == FormatUnknown) && (err == nil) {
   760  			t.Errorf("test %d, got nil-error, want non-nil error", i)
   761  		}
   762  	}
   763  }
   764  
   765  func Benchmark(b *testing.B) {
   766  	type file struct {
   767  		hdr  *Header
   768  		body []byte
   769  	}
   770  
   771  	vectors := []struct {
   772  		label string
   773  		files []file
   774  	}{{
   775  		"USTAR",
   776  		[]file{{
   777  			&Header{Name: "bar", Mode: 0640, Size: int64(3)},
   778  			[]byte("foo"),
   779  		}, {
   780  			&Header{Name: "world", Mode: 0640, Size: int64(5)},
   781  			[]byte("hello"),
   782  		}},
   783  	}, {
   784  		"GNU",
   785  		[]file{{
   786  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1},
   787  			[]byte("foo"),
   788  		}, {
   789  			&Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1},
   790  			[]byte("hello"),
   791  		}},
   792  	}, {
   793  		"PAX",
   794  		[]file{{
   795  			&Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}},
   796  			[]byte("foo"),
   797  		}, {
   798  			&Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}},
   799  			[]byte("hello"),
   800  		}},
   801  	}}
   802  
   803  	b.Run("Writer", func(b *testing.B) {
   804  		for _, v := range vectors {
   805  			b.Run(v.label, func(b *testing.B) {
   806  				b.ReportAllocs()
   807  				for i := 0; i < b.N; i++ {
   808  					// Writing to io.Discard because we want to
   809  					// test purely the writer code and not bring in disk performance into this.
   810  					tw := NewWriter(io.Discard)
   811  					for _, file := range v.files {
   812  						if err := tw.WriteHeader(file.hdr); err != nil {
   813  							b.Errorf("unexpected WriteHeader error: %v", err)
   814  						}
   815  						if _, err := tw.Write(file.body); err != nil {
   816  							b.Errorf("unexpected Write error: %v", err)
   817  						}
   818  					}
   819  					if err := tw.Close(); err != nil {
   820  						b.Errorf("unexpected Close error: %v", err)
   821  					}
   822  				}
   823  			})
   824  		}
   825  	})
   826  
   827  	b.Run("Reader", func(b *testing.B) {
   828  		for _, v := range vectors {
   829  			var buf bytes.Buffer
   830  			var r bytes.Reader
   831  
   832  			// Write the archive to a byte buffer.
   833  			tw := NewWriter(&buf)
   834  			for _, file := range v.files {
   835  				_ = tw.WriteHeader(file.hdr)
   836  				_, _ = tw.Write(file.body)
   837  			}
   838  			tw.Close()
   839  			b.Run(v.label, func(b *testing.B) {
   840  				b.ReportAllocs()
   841  				// Read from the byte buffer.
   842  				for i := 0; i < b.N; i++ {
   843  					r.Reset(buf.Bytes())
   844  					tr := NewReader(&r)
   845  					if _, err := tr.Next(); err != nil {
   846  						b.Errorf("unexpected Next error: %v", err)
   847  					}
   848  					if _, err := io.Copy(io.Discard, tr); err != nil {
   849  						b.Errorf("unexpected Copy error : %v", err)
   850  					}
   851  				}
   852  			})
   853  		}
   854  	})
   855  
   856  }
   857  

View as plain text