github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/src/archive/tar/writer_test.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package tar
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"reflect"
    15  	"sort"
    16  	"strings"
    17  	"testing"
    18  	"testing/iotest"
    19  	"time"
    20  )
    21  
    22  func bytediff(a, b []byte) string {
    23  	const (
    24  		uniqueA  = "-  "
    25  		uniqueB  = "+  "
    26  		identity = "   "
    27  	)
    28  	var ss []string
    29  	sa := strings.Split(strings.TrimSpace(hex.Dump(a)), "\n")
    30  	sb := strings.Split(strings.TrimSpace(hex.Dump(b)), "\n")
    31  	for len(sa) > 0 && len(sb) > 0 {
    32  		if sa[0] == sb[0] {
    33  			ss = append(ss, identity+sa[0])
    34  		} else {
    35  			ss = append(ss, uniqueA+sa[0])
    36  			ss = append(ss, uniqueB+sb[0])
    37  		}
    38  		sa, sb = sa[1:], sb[1:]
    39  	}
    40  	for len(sa) > 0 {
    41  		ss = append(ss, uniqueA+sa[0])
    42  		sa = sa[1:]
    43  	}
    44  	for len(sb) > 0 {
    45  		ss = append(ss, uniqueB+sb[0])
    46  		sb = sb[1:]
    47  	}
    48  	return strings.Join(ss, "\n")
    49  }
    50  
    51  func TestWriter(t *testing.T) {
    52  	type (
    53  		testHeader struct { // WriteHeader(&hdr) == wantErr
    54  			hdr     Header
    55  			wantErr error
    56  		}
    57  		testWrite struct { // Write([]byte(str)) == (wantCnt, wantErr)
    58  			str     string
    59  			wantCnt int
    60  			wantErr error
    61  		}
    62  		testFill struct { // fillZeros(cnt) == (wantCnt, wantErr)
    63  			cnt     int64
    64  			wantCnt int64
    65  			wantErr error
    66  		}
    67  		testClose struct { // Close() == wantErr
    68  			wantErr error
    69  		}
    70  		testFnc interface{} // testHeader | testWrite | testFill | testClose
    71  	)
    72  
    73  	vectors := []struct {
    74  		file  string // Optional filename of expected output
    75  		tests []testFnc
    76  	}{{
    77  		// The writer test file was produced with this command:
    78  		// tar (GNU tar) 1.26
    79  		//   ln -s small.txt link.txt
    80  		//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
    81  		file: "testdata/writer.tar",
    82  		tests: []testFnc{
    83  			testHeader{Header{
    84  				Typeflag: TypeReg,
    85  				Name:     "small.txt",
    86  				Size:     5,
    87  				Mode:     0640,
    88  				Uid:      73025,
    89  				Gid:      5000,
    90  				Uname:    "dsymonds",
    91  				Gname:    "eng",
    92  				ModTime:  time.Unix(1246508266, 0),
    93  			}, nil},
    94  			testWrite{"Kilts", 5, nil},
    95  
    96  			testHeader{Header{
    97  				Typeflag: TypeReg,
    98  				Name:     "small2.txt",
    99  				Size:     11,
   100  				Mode:     0640,
   101  				Uid:      73025,
   102  				Uname:    "dsymonds",
   103  				Gname:    "eng",
   104  				Gid:      5000,
   105  				ModTime:  time.Unix(1245217492, 0),
   106  			}, nil},
   107  			testWrite{"Google.com\n", 11, nil},
   108  
   109  			testHeader{Header{
   110  				Typeflag: TypeSymlink,
   111  				Name:     "link.txt",
   112  				Linkname: "small.txt",
   113  				Mode:     0777,
   114  				Uid:      1000,
   115  				Gid:      1000,
   116  				Uname:    "strings",
   117  				Gname:    "strings",
   118  				ModTime:  time.Unix(1314603082, 0),
   119  			}, nil},
   120  			testWrite{"", 0, nil},
   121  
   122  			testClose{nil},
   123  		},
   124  	}, {
   125  		// The truncated test file was produced using these commands:
   126  		//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
   127  		//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
   128  		file: "testdata/writer-big.tar",
   129  		tests: []testFnc{
   130  			testHeader{Header{
   131  				Typeflag: TypeReg,
   132  				Name:     "tmp/16gig.txt",
   133  				Size:     16 << 30,
   134  				Mode:     0640,
   135  				Uid:      73025,
   136  				Gid:      5000,
   137  				Uname:    "dsymonds",
   138  				Gname:    "eng",
   139  				ModTime:  time.Unix(1254699560, 0),
   140  				Format:   FormatGNU,
   141  			}, nil},
   142  		},
   143  	}, {
   144  		// This truncated file was produced using this library.
   145  		// It was verified to work with GNU tar 1.27.1 and BSD tar 3.1.2.
   146  		//  dd if=/dev/zero bs=1G count=16 >> writer-big-long.tar
   147  		//  gnutar -xvf writer-big-long.tar
   148  		//  bsdtar -xvf writer-big-long.tar
   149  		//
   150  		// This file is in PAX format.
   151  		file: "testdata/writer-big-long.tar",
   152  		tests: []testFnc{
   153  			testHeader{Header{
   154  				Typeflag: TypeReg,
   155  				Name:     strings.Repeat("longname/", 15) + "16gig.txt",
   156  				Size:     16 << 30,
   157  				Mode:     0644,
   158  				Uid:      1000,
   159  				Gid:      1000,
   160  				Uname:    "guillaume",
   161  				Gname:    "guillaume",
   162  				ModTime:  time.Unix(1399583047, 0),
   163  			}, nil},
   164  		},
   165  	}, {
   166  		// This file was produced using GNU tar v1.17.
   167  		//	gnutar -b 4 --format=ustar (longname/)*15 + file.txt
   168  		file: "testdata/ustar.tar",
   169  		tests: []testFnc{
   170  			testHeader{Header{
   171  				Typeflag: TypeReg,
   172  				Name:     strings.Repeat("longname/", 15) + "file.txt",
   173  				Size:     6,
   174  				Mode:     0644,
   175  				Uid:      501,
   176  				Gid:      20,
   177  				Uname:    "shane",
   178  				Gname:    "staff",
   179  				ModTime:  time.Unix(1360135598, 0),
   180  			}, nil},
   181  			testWrite{"hello\n", 6, nil},
   182  			testClose{nil},
   183  		},
   184  	}, {
   185  		// This file was produced using GNU tar v1.26:
   186  		//	echo "Slartibartfast" > file.txt
   187  		//	ln file.txt hard.txt
   188  		//	tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
   189  		file: "testdata/hardlink.tar",
   190  		tests: []testFnc{
   191  			testHeader{Header{
   192  				Typeflag: TypeReg,
   193  				Name:     "file.txt",
   194  				Size:     15,
   195  				Mode:     0644,
   196  				Uid:      1000,
   197  				Gid:      100,
   198  				Uname:    "vbatts",
   199  				Gname:    "users",
   200  				ModTime:  time.Unix(1425484303, 0),
   201  			}, nil},
   202  			testWrite{"Slartibartfast\n", 15, nil},
   203  
   204  			testHeader{Header{
   205  				Typeflag: TypeLink,
   206  				Name:     "hard.txt",
   207  				Linkname: "file.txt",
   208  				Mode:     0644,
   209  				Uid:      1000,
   210  				Gid:      100,
   211  				Uname:    "vbatts",
   212  				Gname:    "users",
   213  				ModTime:  time.Unix(1425484303, 0),
   214  			}, nil},
   215  			testWrite{"", 0, nil},
   216  
   217  			testClose{nil},
   218  		},
   219  	}, {
   220  		tests: []testFnc{
   221  			testHeader{Header{
   222  				Typeflag: TypeReg,
   223  				Name:     "bad-null.txt",
   224  				Xattrs:   map[string]string{"null\x00null\x00": "fizzbuzz"},
   225  			}, headerError{}},
   226  		},
   227  	}, {
   228  		tests: []testFnc{
   229  			testHeader{Header{
   230  				Typeflag: TypeReg,
   231  				Name:     "null\x00.txt",
   232  			}, headerError{}},
   233  		},
   234  	}, {
   235  		file: "testdata/pax-records.tar",
   236  		tests: []testFnc{
   237  			testHeader{Header{
   238  				Typeflag: TypeReg,
   239  				Name:     "file",
   240  				Uname:    strings.Repeat("long", 10),
   241  				PAXRecords: map[string]string{
   242  					"path":           "FILE", // Should be ignored
   243  					"GNU.sparse.map": "0,0",  // Should be ignored
   244  					"comment":        "Hello, 世界",
   245  					"GOLANG.pkg":     "tar",
   246  				},
   247  			}, nil},
   248  			testClose{nil},
   249  		},
   250  	}, {
   251  		// Craft a theoretically valid PAX archive with global headers.
   252  		// The GNU and BSD tar tools do not parse these the same way.
   253  		//
   254  		// BSD tar v3.1.2 parses and ignores all global headers;
   255  		// the behavior is verified by researching the source code.
   256  		//
   257  		//	$ bsdtar -tvf pax-global-records.tar
   258  		//	----------  0 0      0           0 Dec 31  1969 file1
   259  		//	----------  0 0      0           0 Dec 31  1969 file2
   260  		//	----------  0 0      0           0 Dec 31  1969 file3
   261  		//	----------  0 0      0           0 May 13  2014 file4
   262  		//
   263  		// GNU tar v1.27.1 applies global headers to subsequent records,
   264  		// but does not do the following properly:
   265  		//	* It does not treat an empty record as deletion.
   266  		//	* It does not use subsequent global headers to update previous ones.
   267  		//
   268  		//	$ gnutar -tvf pax-global-records.tar
   269  		//	---------- 0/0               0 2017-07-13 19:40 global1
   270  		//	---------- 0/0               0 2017-07-13 19:40 file2
   271  		//	gnutar: Substituting `.' for empty member name
   272  		//	---------- 0/0               0 1969-12-31 16:00
   273  		//	gnutar: Substituting `.' for empty member name
   274  		//	---------- 0/0               0 2014-05-13 09:53
   275  		//
   276  		// According to the PAX specification, this should have been the result:
   277  		//	---------- 0/0               0 2017-07-13 19:40 global1
   278  		//	---------- 0/0               0 2017-07-13 19:40 file2
   279  		//	---------- 0/0               0 2017-07-13 19:40 file3
   280  		//	---------- 0/0               0 2014-05-13 09:53 file4
   281  		file: "testdata/pax-global-records.tar",
   282  		tests: []testFnc{
   283  			testHeader{Header{
   284  				Typeflag:   TypeXGlobalHeader,
   285  				PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"},
   286  			}, nil},
   287  			testHeader{Header{
   288  				Typeflag: TypeReg, Name: "file1",
   289  			}, nil},
   290  			testHeader{Header{
   291  				Typeflag:   TypeReg,
   292  				Name:       "file2",
   293  				PAXRecords: map[string]string{"path": "file2"},
   294  			}, nil},
   295  			testHeader{Header{
   296  				Typeflag:   TypeXGlobalHeader,
   297  				PAXRecords: map[string]string{"path": ""}, // Should delete "path", but keep "mtime"
   298  			}, nil},
   299  			testHeader{Header{
   300  				Typeflag: TypeReg, Name: "file3",
   301  			}, nil},
   302  			testHeader{Header{
   303  				Typeflag:   TypeReg,
   304  				Name:       "file4",
   305  				ModTime:    time.Unix(1400000000, 0),
   306  				PAXRecords: map[string]string{"mtime": "1400000000"},
   307  			}, nil},
   308  			testClose{nil},
   309  		},
   310  	}, {
   311  		file: "testdata/gnu-utf8.tar",
   312  		tests: []testFnc{
   313  			testHeader{Header{
   314  				Typeflag: TypeReg,
   315  				Name:     "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
   316  				Mode:     0644,
   317  				Uid:      1000, Gid: 1000,
   318  				Uname:   "☺",
   319  				Gname:   "⚹",
   320  				ModTime: time.Unix(0, 0),
   321  				Format:  FormatGNU,
   322  			}, nil},
   323  			testClose{nil},
   324  		},
   325  	}, {
   326  		file: "testdata/gnu-not-utf8.tar",
   327  		tests: []testFnc{
   328  			testHeader{Header{
   329  				Typeflag: TypeReg,
   330  				Name:     "hi\x80\x81\x82\x83bye",
   331  				Mode:     0644,
   332  				Uid:      1000,
   333  				Gid:      1000,
   334  				Uname:    "rawr",
   335  				Gname:    "dsnet",
   336  				ModTime:  time.Unix(0, 0),
   337  				Format:   FormatGNU,
   338  			}, nil},
   339  			testClose{nil},
   340  		},
   341  	}, {
   342  		file: "testdata/gnu-nil-sparse-data.tar",
   343  		tests: []testFnc{
   344  			testHeader{Header{
   345  				Typeflag:    TypeGNUSparse,
   346  				Name:        "sparse.db",
   347  				Size:        1000,
   348  				SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}},
   349  			}, nil},
   350  			testWrite{strings.Repeat("0123456789", 100), 1000, nil},
   351  			testClose{},
   352  		},
   353  	}, {
   354  		file: "testdata/gnu-nil-sparse-hole.tar",
   355  		tests: []testFnc{
   356  			testHeader{Header{
   357  				Typeflag:    TypeGNUSparse,
   358  				Name:        "sparse.db",
   359  				Size:        1000,
   360  				SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}},
   361  			}, nil},
   362  			testWrite{strings.Repeat("\x00", 1000), 1000, nil},
   363  			testClose{},
   364  		},
   365  	}, {
   366  		file: "testdata/pax-nil-sparse-data.tar",
   367  		tests: []testFnc{
   368  			testHeader{Header{
   369  				Typeflag:    TypeReg,
   370  				Name:        "sparse.db",
   371  				Size:        1000,
   372  				SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}},
   373  			}, nil},
   374  			testWrite{strings.Repeat("0123456789", 100), 1000, nil},
   375  			testClose{},
   376  		},
   377  	}, {
   378  		file: "testdata/pax-nil-sparse-hole.tar",
   379  		tests: []testFnc{
   380  			testHeader{Header{
   381  				Typeflag:    TypeReg,
   382  				Name:        "sparse.db",
   383  				Size:        1000,
   384  				SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}},
   385  			}, nil},
   386  			testWrite{strings.Repeat("\x00", 1000), 1000, nil},
   387  			testClose{},
   388  		},
   389  	}, {
   390  		file: "testdata/gnu-sparse-big.tar",
   391  		tests: []testFnc{
   392  			testHeader{Header{
   393  				Typeflag: TypeGNUSparse,
   394  				Name:     "gnu-sparse",
   395  				Size:     6e10,
   396  				SparseHoles: []SparseEntry{
   397  					{Offset: 0e10, Length: 1e10 - 100},
   398  					{Offset: 1e10, Length: 1e10 - 100},
   399  					{Offset: 2e10, Length: 1e10 - 100},
   400  					{Offset: 3e10, Length: 1e10 - 100},
   401  					{Offset: 4e10, Length: 1e10 - 100},
   402  					{Offset: 5e10, Length: 1e10 - 100},
   403  				},
   404  			}, nil},
   405  			testFill{1e10 - 100, 1e10 - 100, nil},
   406  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   407  			testFill{1e10 - 100, 1e10 - 100, nil},
   408  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   409  			testFill{1e10 - 100, 1e10 - 100, nil},
   410  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   411  			testFill{1e10 - 100, 1e10 - 100, nil},
   412  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   413  			testFill{1e10 - 100, 1e10 - 100, nil},
   414  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   415  			testFill{1e10 - 100, 1e10 - 100, nil},
   416  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   417  			testFill{1e10 - 100, 0, ErrWriteTooLong},
   418  			testWrite{strings.Repeat("0123456789", 10), 0, ErrWriteTooLong},
   419  			testClose{nil},
   420  		},
   421  	}, {
   422  		file: "testdata/pax-sparse-big.tar",
   423  		tests: []testFnc{
   424  			testHeader{Header{
   425  				Typeflag: TypeReg,
   426  				Name:     "pax-sparse",
   427  				Size:     6e10,
   428  				SparseHoles: []SparseEntry{
   429  					{Offset: 0e10, Length: 1e10 - 100},
   430  					{Offset: 1e10, Length: 1e10 - 100},
   431  					{Offset: 2e10, Length: 1e10 - 100},
   432  					{Offset: 3e10, Length: 1e10 - 100},
   433  					{Offset: 4e10, Length: 1e10 - 100},
   434  					{Offset: 5e10, Length: 1e10 - 100},
   435  				},
   436  			}, nil},
   437  			testFill{1e10 - 100, 1e10 - 100, nil},
   438  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   439  			testFill{1e10 - 100, 1e10 - 100, nil},
   440  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   441  			testFill{1e10 - 100, 1e10 - 100, nil},
   442  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   443  			testFill{1e10 - 100, 1e10 - 100, nil},
   444  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   445  			testFill{1e10 - 100, 1e10 - 100, nil},
   446  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   447  			testFill{1e10 - 100, 1e10 - 100, nil},
   448  			testWrite{strings.Repeat("0123456789", 10), 100, nil},
   449  			testFill{1e10 - 100, 0, ErrWriteTooLong},
   450  			testWrite{strings.Repeat("0123456789", 10), 0, ErrWriteTooLong},
   451  			testClose{nil},
   452  		},
   453  	}}
   454  
   455  	equalError := func(x, y error) bool {
   456  		_, ok1 := x.(headerError)
   457  		_, ok2 := y.(headerError)
   458  		if ok1 || ok2 {
   459  			return ok1 && ok2
   460  		}
   461  		return x == y
   462  	}
   463  	for _, v := range vectors {
   464  		t.Run(path.Base(v.file), func(t *testing.T) {
   465  			const maxSize = 10 << 10 // 10KiB
   466  			buf := new(bytes.Buffer)
   467  			tw := NewWriter(iotest.TruncateWriter(buf, maxSize))
   468  
   469  			for i, tf := range v.tests {
   470  				switch tf := tf.(type) {
   471  				case testHeader:
   472  					err := tw.WriteHeader(&tf.hdr)
   473  					if !equalError(err, tf.wantErr) {
   474  						t.Fatalf("test %d, WriteHeader() = %v, want %v", i, err, tf.wantErr)
   475  					}
   476  				case testWrite:
   477  					got, err := tw.Write([]byte(tf.str))
   478  					if got != tf.wantCnt || !equalError(err, tf.wantErr) {
   479  						t.Fatalf("test %d, Write() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
   480  					}
   481  				case testFill:
   482  					got, err := tw.fillZeros(tf.cnt)
   483  					if got != tf.wantCnt || !equalError(err, tf.wantErr) {
   484  						t.Fatalf("test %d, fillZeros() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
   485  					}
   486  				case testClose:
   487  					err := tw.Close()
   488  					if !equalError(err, tf.wantErr) {
   489  						t.Fatalf("test %d, Close() = %v, want %v", i, err, tf.wantErr)
   490  					}
   491  				default:
   492  					t.Fatalf("test %d, unknown test operation: %T", i, tf)
   493  				}
   494  			}
   495  
   496  			if v.file != "" {
   497  				want, err := ioutil.ReadFile(v.file)
   498  				if err != nil {
   499  					t.Fatalf("ReadFile() = %v, want nil", err)
   500  				}
   501  				got := buf.Bytes()
   502  				if !bytes.Equal(want, got) {
   503  					t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want))
   504  				}
   505  			}
   506  		})
   507  	}
   508  }
   509  
   510  func TestPax(t *testing.T) {
   511  	// Create an archive with a large name
   512  	fileinfo, err := os.Stat("testdata/small.txt")
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  	hdr, err := FileInfoHeader(fileinfo, "")
   517  	if err != nil {
   518  		t.Fatalf("os.Stat: %v", err)
   519  	}
   520  	// Force a PAX long name to be written
   521  	longName := strings.Repeat("ab", 100)
   522  	contents := strings.Repeat(" ", int(hdr.Size))
   523  	hdr.Name = longName
   524  	var buf bytes.Buffer
   525  	writer := NewWriter(&buf)
   526  	if err := writer.WriteHeader(hdr); err != nil {
   527  		t.Fatal(err)
   528  	}
   529  	if _, err = writer.Write([]byte(contents)); err != nil {
   530  		t.Fatal(err)
   531  	}
   532  	if err := writer.Close(); err != nil {
   533  		t.Fatal(err)
   534  	}
   535  	// Simple test to make sure PAX extensions are in effect
   536  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   537  		t.Fatal("Expected at least one PAX header to be written.")
   538  	}
   539  	// Test that we can get a long name back out of the archive.
   540  	reader := NewReader(&buf)
   541  	hdr, err = reader.Next()
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  	if hdr.Name != longName {
   546  		t.Fatal("Couldn't recover long file name")
   547  	}
   548  }
   549  
   550  func TestPaxSymlink(t *testing.T) {
   551  	// Create an archive with a large linkname
   552  	fileinfo, err := os.Stat("testdata/small.txt")
   553  	if err != nil {
   554  		t.Fatal(err)
   555  	}
   556  	hdr, err := FileInfoHeader(fileinfo, "")
   557  	hdr.Typeflag = TypeSymlink
   558  	if err != nil {
   559  		t.Fatalf("os.Stat:1 %v", err)
   560  	}
   561  	// Force a PAX long linkname to be written
   562  	longLinkname := strings.Repeat("1234567890/1234567890", 10)
   563  	hdr.Linkname = longLinkname
   564  
   565  	hdr.Size = 0
   566  	var buf bytes.Buffer
   567  	writer := NewWriter(&buf)
   568  	if err := writer.WriteHeader(hdr); err != nil {
   569  		t.Fatal(err)
   570  	}
   571  	if err := writer.Close(); err != nil {
   572  		t.Fatal(err)
   573  	}
   574  	// Simple test to make sure PAX extensions are in effect
   575  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   576  		t.Fatal("Expected at least one PAX header to be written.")
   577  	}
   578  	// Test that we can get a long name back out of the archive.
   579  	reader := NewReader(&buf)
   580  	hdr, err = reader.Next()
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  	if hdr.Linkname != longLinkname {
   585  		t.Fatal("Couldn't recover long link name")
   586  	}
   587  }
   588  
   589  func TestPaxNonAscii(t *testing.T) {
   590  	// Create an archive with non ascii. These should trigger a pax header
   591  	// because pax headers have a defined utf-8 encoding.
   592  	fileinfo, err := os.Stat("testdata/small.txt")
   593  	if err != nil {
   594  		t.Fatal(err)
   595  	}
   596  
   597  	hdr, err := FileInfoHeader(fileinfo, "")
   598  	if err != nil {
   599  		t.Fatalf("os.Stat:1 %v", err)
   600  	}
   601  
   602  	// some sample data
   603  	chineseFilename := "文件名"
   604  	chineseGroupname := "組"
   605  	chineseUsername := "用戶名"
   606  
   607  	hdr.Name = chineseFilename
   608  	hdr.Gname = chineseGroupname
   609  	hdr.Uname = chineseUsername
   610  
   611  	contents := strings.Repeat(" ", int(hdr.Size))
   612  
   613  	var buf bytes.Buffer
   614  	writer := NewWriter(&buf)
   615  	if err := writer.WriteHeader(hdr); err != nil {
   616  		t.Fatal(err)
   617  	}
   618  	if _, err = writer.Write([]byte(contents)); err != nil {
   619  		t.Fatal(err)
   620  	}
   621  	if err := writer.Close(); err != nil {
   622  		t.Fatal(err)
   623  	}
   624  	// Simple test to make sure PAX extensions are in effect
   625  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   626  		t.Fatal("Expected at least one PAX header to be written.")
   627  	}
   628  	// Test that we can get a long name back out of the archive.
   629  	reader := NewReader(&buf)
   630  	hdr, err = reader.Next()
   631  	if err != nil {
   632  		t.Fatal(err)
   633  	}
   634  	if hdr.Name != chineseFilename {
   635  		t.Fatal("Couldn't recover unicode name")
   636  	}
   637  	if hdr.Gname != chineseGroupname {
   638  		t.Fatal("Couldn't recover unicode group")
   639  	}
   640  	if hdr.Uname != chineseUsername {
   641  		t.Fatal("Couldn't recover unicode user")
   642  	}
   643  }
   644  
   645  func TestPaxXattrs(t *testing.T) {
   646  	xattrs := map[string]string{
   647  		"user.key": "value",
   648  	}
   649  
   650  	// Create an archive with an xattr
   651  	fileinfo, err := os.Stat("testdata/small.txt")
   652  	if err != nil {
   653  		t.Fatal(err)
   654  	}
   655  	hdr, err := FileInfoHeader(fileinfo, "")
   656  	if err != nil {
   657  		t.Fatalf("os.Stat: %v", err)
   658  	}
   659  	contents := "Kilts"
   660  	hdr.Xattrs = xattrs
   661  	var buf bytes.Buffer
   662  	writer := NewWriter(&buf)
   663  	if err := writer.WriteHeader(hdr); err != nil {
   664  		t.Fatal(err)
   665  	}
   666  	if _, err = writer.Write([]byte(contents)); err != nil {
   667  		t.Fatal(err)
   668  	}
   669  	if err := writer.Close(); err != nil {
   670  		t.Fatal(err)
   671  	}
   672  	// Test that we can get the xattrs back out of the archive.
   673  	reader := NewReader(&buf)
   674  	hdr, err = reader.Next()
   675  	if err != nil {
   676  		t.Fatal(err)
   677  	}
   678  	if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
   679  		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
   680  			hdr.Xattrs, xattrs)
   681  	}
   682  }
   683  
   684  func TestPaxHeadersSorted(t *testing.T) {
   685  	fileinfo, err := os.Stat("testdata/small.txt")
   686  	if err != nil {
   687  		t.Fatal(err)
   688  	}
   689  	hdr, err := FileInfoHeader(fileinfo, "")
   690  	if err != nil {
   691  		t.Fatalf("os.Stat: %v", err)
   692  	}
   693  	contents := strings.Repeat(" ", int(hdr.Size))
   694  
   695  	hdr.Xattrs = map[string]string{
   696  		"foo": "foo",
   697  		"bar": "bar",
   698  		"baz": "baz",
   699  		"qux": "qux",
   700  	}
   701  
   702  	var buf bytes.Buffer
   703  	writer := NewWriter(&buf)
   704  	if err := writer.WriteHeader(hdr); err != nil {
   705  		t.Fatal(err)
   706  	}
   707  	if _, err = writer.Write([]byte(contents)); err != nil {
   708  		t.Fatal(err)
   709  	}
   710  	if err := writer.Close(); err != nil {
   711  		t.Fatal(err)
   712  	}
   713  	// Simple test to make sure PAX extensions are in effect
   714  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   715  		t.Fatal("Expected at least one PAX header to be written.")
   716  	}
   717  
   718  	// xattr bar should always appear before others
   719  	indices := []int{
   720  		bytes.Index(buf.Bytes(), []byte("bar=bar")),
   721  		bytes.Index(buf.Bytes(), []byte("baz=baz")),
   722  		bytes.Index(buf.Bytes(), []byte("foo=foo")),
   723  		bytes.Index(buf.Bytes(), []byte("qux=qux")),
   724  	}
   725  	if !sort.IntsAreSorted(indices) {
   726  		t.Fatal("PAX headers are not sorted")
   727  	}
   728  }
   729  
   730  func TestUSTARLongName(t *testing.T) {
   731  	// Create an archive with a path that failed to split with USTAR extension in previous versions.
   732  	fileinfo, err := os.Stat("testdata/small.txt")
   733  	if err != nil {
   734  		t.Fatal(err)
   735  	}
   736  	hdr, err := FileInfoHeader(fileinfo, "")
   737  	hdr.Typeflag = TypeDir
   738  	if err != nil {
   739  		t.Fatalf("os.Stat:1 %v", err)
   740  	}
   741  	// Force a PAX long name to be written. The name was taken from a practical example
   742  	// that fails and replaced ever char through numbers to anonymize the sample.
   743  	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/"
   744  	hdr.Name = longName
   745  
   746  	hdr.Size = 0
   747  	var buf bytes.Buffer
   748  	writer := NewWriter(&buf)
   749  	if err := writer.WriteHeader(hdr); err != nil {
   750  		t.Fatal(err)
   751  	}
   752  	if err := writer.Close(); err != nil {
   753  		t.Fatal(err)
   754  	}
   755  	// Test that we can get a long name back out of the archive.
   756  	reader := NewReader(&buf)
   757  	hdr, err = reader.Next()
   758  	if err != nil {
   759  		t.Fatal(err)
   760  	}
   761  	if hdr.Name != longName {
   762  		t.Fatal("Couldn't recover long name")
   763  	}
   764  }
   765  
   766  func TestValidTypeflagWithPAXHeader(t *testing.T) {
   767  	var buffer bytes.Buffer
   768  	tw := NewWriter(&buffer)
   769  
   770  	fileName := strings.Repeat("ab", 100)
   771  
   772  	hdr := &Header{
   773  		Name:     fileName,
   774  		Size:     4,
   775  		Typeflag: 0,
   776  	}
   777  	if err := tw.WriteHeader(hdr); err != nil {
   778  		t.Fatalf("Failed to write header: %s", err)
   779  	}
   780  	if _, err := tw.Write([]byte("fooo")); err != nil {
   781  		t.Fatalf("Failed to write the file's data: %s", err)
   782  	}
   783  	tw.Close()
   784  
   785  	tr := NewReader(&buffer)
   786  
   787  	for {
   788  		header, err := tr.Next()
   789  		if err == io.EOF {
   790  			break
   791  		}
   792  		if err != nil {
   793  			t.Fatalf("Failed to read header: %s", err)
   794  		}
   795  		if header.Typeflag != 0 {
   796  			t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
   797  		}
   798  	}
   799  }
   800  
   801  // failOnceWriter fails exactly once and then always reports success.
   802  type failOnceWriter bool
   803  
   804  func (w *failOnceWriter) Write(b []byte) (int, error) {
   805  	if !*w {
   806  		return 0, io.ErrShortWrite
   807  	}
   808  	*w = true
   809  	return len(b), nil
   810  }
   811  
   812  func TestWriterErrors(t *testing.T) {
   813  	t.Run("HeaderOnly", func(t *testing.T) {
   814  		tw := NewWriter(new(bytes.Buffer))
   815  		hdr := &Header{Name: "dir/", Typeflag: TypeDir}
   816  		if err := tw.WriteHeader(hdr); err != nil {
   817  			t.Fatalf("WriteHeader() = %v, want nil", err)
   818  		}
   819  		if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong {
   820  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   821  		}
   822  	})
   823  
   824  	t.Run("NegativeSize", func(t *testing.T) {
   825  		tw := NewWriter(new(bytes.Buffer))
   826  		hdr := &Header{Name: "small.txt", Size: -1}
   827  		if err := tw.WriteHeader(hdr); err == nil {
   828  			t.Fatalf("WriteHeader() = nil, want non-nil error")
   829  		}
   830  	})
   831  
   832  	t.Run("BeforeHeader", func(t *testing.T) {
   833  		tw := NewWriter(new(bytes.Buffer))
   834  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong {
   835  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   836  		}
   837  	})
   838  
   839  	t.Run("AfterClose", func(t *testing.T) {
   840  		tw := NewWriter(new(bytes.Buffer))
   841  		hdr := &Header{Name: "small.txt"}
   842  		if err := tw.WriteHeader(hdr); err != nil {
   843  			t.Fatalf("WriteHeader() = %v, want nil", err)
   844  		}
   845  		if err := tw.Close(); err != nil {
   846  			t.Fatalf("Close() = %v, want nil", err)
   847  		}
   848  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
   849  			t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose)
   850  		}
   851  		if err := tw.Flush(); err != ErrWriteAfterClose {
   852  			t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose)
   853  		}
   854  		if err := tw.Close(); err != nil {
   855  			t.Fatalf("Close() = %v, want nil", err)
   856  		}
   857  	})
   858  
   859  	t.Run("PrematureFlush", func(t *testing.T) {
   860  		tw := NewWriter(new(bytes.Buffer))
   861  		hdr := &Header{Name: "small.txt", Size: 5}
   862  		if err := tw.WriteHeader(hdr); err != nil {
   863  			t.Fatalf("WriteHeader() = %v, want nil", err)
   864  		}
   865  		if err := tw.Flush(); err == nil {
   866  			t.Fatalf("Flush() = %v, want non-nil error", err)
   867  		}
   868  	})
   869  
   870  	t.Run("PrematureClose", func(t *testing.T) {
   871  		tw := NewWriter(new(bytes.Buffer))
   872  		hdr := &Header{Name: "small.txt", Size: 5}
   873  		if err := tw.WriteHeader(hdr); err != nil {
   874  			t.Fatalf("WriteHeader() = %v, want nil", err)
   875  		}
   876  		if err := tw.Close(); err == nil {
   877  			t.Fatalf("Close() = %v, want non-nil error", err)
   878  		}
   879  	})
   880  
   881  	t.Run("Persistence", func(t *testing.T) {
   882  		tw := NewWriter(new(failOnceWriter))
   883  		if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite {
   884  			t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite)
   885  		}
   886  		if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil {
   887  			t.Errorf("WriteHeader() = got %v, want non-nil error", err)
   888  		}
   889  		if _, err := tw.Write(nil); err == nil {
   890  			t.Errorf("Write() = %v, want non-nil error", err)
   891  		}
   892  		if err := tw.Flush(); err == nil {
   893  			t.Errorf("Flush() = %v, want non-nil error", err)
   894  		}
   895  		if err := tw.Close(); err == nil {
   896  			t.Errorf("Close() = %v, want non-nil error", err)
   897  		}
   898  	})
   899  }
   900  
   901  func TestSplitUSTARPath(t *testing.T) {
   902  	sr := strings.Repeat
   903  
   904  	vectors := []struct {
   905  		input  string // Input path
   906  		prefix string // Expected output prefix
   907  		suffix string // Expected output suffix
   908  		ok     bool   // Split success?
   909  	}{
   910  		{"", "", "", false},
   911  		{"abc", "", "", false},
   912  		{"用戶名", "", "", false},
   913  		{sr("a", nameSize), "", "", false},
   914  		{sr("a", nameSize) + "/", "", "", false},
   915  		{sr("a", nameSize) + "/a", sr("a", nameSize), "a", true},
   916  		{sr("a", prefixSize) + "/", "", "", false},
   917  		{sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true},
   918  		{sr("a", nameSize+1), "", "", false},
   919  		{sr("/", nameSize+1), sr("/", nameSize-1), "/", true},
   920  		{sr("a", prefixSize) + "/" + sr("b", nameSize),
   921  			sr("a", prefixSize), sr("b", nameSize), true},
   922  		{sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false},
   923  		{sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true},
   924  	}
   925  
   926  	for _, v := range vectors {
   927  		prefix, suffix, ok := splitUSTARPath(v.input)
   928  		if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
   929  			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)",
   930  				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
   931  		}
   932  	}
   933  }
   934  
   935  // TestIssue12594 tests that the Writer does not attempt to populate the prefix
   936  // field when encoding a header in the GNU format. The prefix field is valid
   937  // in USTAR and PAX, but not GNU.
   938  func TestIssue12594(t *testing.T) {
   939  	names := []string{
   940  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/file.txt",
   941  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt",
   942  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/333/file.txt",
   943  		"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/file.txt",
   944  		"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt",
   945  		"/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend",
   946  	}
   947  
   948  	for i, name := range names {
   949  		var b bytes.Buffer
   950  
   951  		tw := NewWriter(&b)
   952  		if err := tw.WriteHeader(&Header{
   953  			Name: name,
   954  			Uid:  1 << 25, // Prevent USTAR format
   955  		}); err != nil {
   956  			t.Errorf("test %d, unexpected WriteHeader error: %v", i, err)
   957  		}
   958  		if err := tw.Close(); err != nil {
   959  			t.Errorf("test %d, unexpected Close error: %v", i, err)
   960  		}
   961  
   962  		// The prefix field should never appear in the GNU format.
   963  		var blk block
   964  		copy(blk[:], b.Bytes())
   965  		prefix := string(blk.USTAR().Prefix())
   966  		if i := strings.IndexByte(prefix, 0); i >= 0 {
   967  			prefix = prefix[:i] // Truncate at the NUL terminator
   968  		}
   969  		if blk.GetFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
   970  			t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
   971  		}
   972  
   973  		tr := NewReader(&b)
   974  		hdr, err := tr.Next()
   975  		if err != nil {
   976  			t.Errorf("test %d, unexpected Next error: %v", i, err)
   977  		}
   978  		if hdr.Name != name {
   979  			t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name)
   980  		}
   981  	}
   982  }
   983  
   984  func TestFileWriter(t *testing.T) {
   985  	type (
   986  		testWrite struct { // Write(str) == (wantCnt, wantErr)
   987  			str     string
   988  			wantCnt int
   989  			wantErr error
   990  		}
   991  		testFill struct { // FillZeros(cnt) == (wantCnt, wantErr)
   992  			cnt     int64
   993  			wantCnt int64
   994  			wantErr error
   995  		}
   996  		testRemaining struct { // Remaining() == wantCnt
   997  			wantCnt int64
   998  		}
   999  		testFnc interface{} // testWrite | testFill | testRemaining
  1000  	)
  1001  
  1002  	type (
  1003  		makeReg struct {
  1004  			size    int64
  1005  			wantStr string
  1006  		}
  1007  		makeSparse struct {
  1008  			makeReg makeReg
  1009  			sph     sparseHoles
  1010  			size    int64
  1011  		}
  1012  		fileMaker interface{} // makeReg | makeSparse
  1013  	)
  1014  
  1015  	vectors := []struct {
  1016  		maker fileMaker
  1017  		tests []testFnc
  1018  	}{{
  1019  		maker: makeReg{0, ""},
  1020  		tests: []testFnc{
  1021  			testRemaining{0},
  1022  			testWrite{"", 0, nil},
  1023  			testWrite{"a", 0, ErrWriteTooLong},
  1024  			testFill{0, 0, nil},
  1025  			testFill{1, 0, ErrWriteTooLong},
  1026  			testRemaining{0},
  1027  		},
  1028  	}, {
  1029  		maker: makeReg{1, "a"},
  1030  		tests: []testFnc{
  1031  			testRemaining{1},
  1032  			testWrite{"", 0, nil},
  1033  			testWrite{"a", 1, nil},
  1034  			testWrite{"bcde", 0, ErrWriteTooLong},
  1035  			testWrite{"", 0, nil},
  1036  			testFill{0, 0, nil},
  1037  			testFill{1, 0, ErrWriteTooLong},
  1038  			testRemaining{0},
  1039  		},
  1040  	}, {
  1041  		maker: makeReg{5, "hello"},
  1042  		tests: []testFnc{
  1043  			testRemaining{5},
  1044  			testWrite{"hello", 5, nil},
  1045  			testRemaining{0},
  1046  		},
  1047  	}, {
  1048  		maker: makeReg{5, "\x00\x00\x00\x00\x00"},
  1049  		tests: []testFnc{
  1050  			testRemaining{5},
  1051  			testFill{5, 5, nil},
  1052  			testRemaining{0},
  1053  		},
  1054  	}, {
  1055  		maker: makeReg{5, "\x00\x00\x00\x00\x00"},
  1056  		tests: []testFnc{
  1057  			testRemaining{5},
  1058  			testFill{10, 5, ErrWriteTooLong},
  1059  			testRemaining{0},
  1060  		},
  1061  	}, {
  1062  		maker: makeReg{5, "abc\x00\x00"},
  1063  		tests: []testFnc{
  1064  			testRemaining{5},
  1065  			testWrite{"abc", 3, nil},
  1066  			testRemaining{2},
  1067  			testFill{2, 2, nil},
  1068  			testRemaining{0},
  1069  		},
  1070  	}, {
  1071  		maker: makeReg{5, "\x00\x00abc"},
  1072  		tests: []testFnc{
  1073  			testRemaining{5},
  1074  			testFill{2, 2, nil},
  1075  			testRemaining{3},
  1076  			testWrite{"abc", 3, nil},
  1077  			testFill{1, 0, ErrWriteTooLong},
  1078  			testWrite{"z", 0, ErrWriteTooLong},
  1079  			testRemaining{0},
  1080  		},
  1081  	}, {
  1082  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1083  		tests: []testFnc{
  1084  			testRemaining{8},
  1085  			testWrite{"ab\x00\x00\x00cde", 8, nil},
  1086  			testWrite{"a", 0, ErrWriteTooLong},
  1087  			testRemaining{0},
  1088  		},
  1089  	}, {
  1090  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1091  		tests: []testFnc{
  1092  			testWrite{"ab\x00\x00\x00cdez", 8, ErrWriteTooLong},
  1093  			testRemaining{0},
  1094  		},
  1095  	}, {
  1096  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1097  		tests: []testFnc{
  1098  			testWrite{"ab\x00", 3, nil},
  1099  			testRemaining{5},
  1100  			testWrite{"\x00\x00cde", 5, nil},
  1101  			testWrite{"a", 0, ErrWriteTooLong},
  1102  			testRemaining{0},
  1103  		},
  1104  	}, {
  1105  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1106  		tests: []testFnc{
  1107  			testWrite{"ab", 2, nil},
  1108  			testRemaining{6},
  1109  			testFill{3, 3, nil},
  1110  			testRemaining{3},
  1111  			testWrite{"cde", 3, nil},
  1112  			testRemaining{0},
  1113  		},
  1114  	}, {
  1115  		maker: makeSparse{makeReg{5, "\x00\x00\x00\x00\x00"}, sparseHoles{{2, 3}}, 8},
  1116  		tests: []testFnc{
  1117  			testFill{8, 8, nil},
  1118  			testRemaining{0},
  1119  		},
  1120  	}, {
  1121  		maker: makeSparse{makeReg{5, "\x00\x00\x00\x00\x00"}, sparseHoles{{2, 3}}, 8},
  1122  		tests: []testFnc{
  1123  			testFill{9, 8, ErrWriteTooLong},
  1124  			testRemaining{0},
  1125  		},
  1126  	}, {
  1127  		maker: makeSparse{makeReg{4, "\x00\x00\x00\x00"}, sparseHoles{{2, 3}}, 8},
  1128  		tests: []testFnc{
  1129  			testFill{9, 8, errMissData},
  1130  			testRemaining{0},
  1131  		},
  1132  	}, {
  1133  		maker: makeSparse{makeReg{6, "\x00\x00\x00\x00\x00"}, sparseHoles{{2, 3}}, 8},
  1134  		tests: []testFnc{
  1135  			testFill{9, 8, errUnrefData},
  1136  			testRemaining{0},
  1137  		},
  1138  	}, {
  1139  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1140  		tests: []testFnc{
  1141  			testWrite{"ab", 2, nil},
  1142  			testRemaining{6},
  1143  			testFill{3, 3, nil},
  1144  			testRemaining{3},
  1145  			testWrite{"cde", 2, errMissData},
  1146  			testRemaining{1},
  1147  		},
  1148  	}, {
  1149  		maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
  1150  		tests: []testFnc{
  1151  			testWrite{"ab", 2, nil},
  1152  			testRemaining{6},
  1153  			testFill{3, 3, nil},
  1154  			testRemaining{3},
  1155  			testWrite{"cde", 3, errUnrefData},
  1156  			testRemaining{0},
  1157  		},
  1158  	}, {
  1159  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1160  		tests: []testFnc{
  1161  			testRemaining{7},
  1162  			testWrite{"\x00\x00abc\x00\x00", 7, nil},
  1163  			testRemaining{0},
  1164  		},
  1165  	}, {
  1166  		maker: makeSparse{makeReg{3, ""}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1167  		tests: []testFnc{
  1168  			testWrite{"abcdefg", 0, errWriteHole},
  1169  		},
  1170  	}, {
  1171  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1172  		tests: []testFnc{
  1173  			testWrite{"\x00\x00abcde", 5, errWriteHole},
  1174  		},
  1175  	}, {
  1176  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1177  		tests: []testFnc{
  1178  			testWrite{"\x00\x00abc\x00\x00z", 7, ErrWriteTooLong},
  1179  			testRemaining{0},
  1180  		},
  1181  	}, {
  1182  		maker: makeSparse{makeReg{3, "\x00\x00\x00"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1183  		tests: []testFnc{
  1184  			testFill{7, 7, nil},
  1185  			testFill{1, 0, ErrWriteTooLong},
  1186  		},
  1187  	}, {
  1188  		maker: makeSparse{makeReg{3, "\x00\x00\x00"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1189  		tests: []testFnc{
  1190  			testFill{4, 4, nil},
  1191  			testFill{8, 3, ErrWriteTooLong},
  1192  		},
  1193  	}, {
  1194  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1195  		tests: []testFnc{
  1196  			testFill{2, 2, nil},
  1197  			testRemaining{5},
  1198  			testWrite{"abc", 3, nil},
  1199  			testRemaining{2},
  1200  			testFill{2, 2, nil},
  1201  			testRemaining{0},
  1202  		},
  1203  	}, {
  1204  		maker: makeSparse{makeReg{2, "ab"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1205  		tests: []testFnc{
  1206  			testFill{2, 2, nil},
  1207  			testWrite{"abc", 2, errMissData},
  1208  			testFill{2, 2, errMissData},
  1209  		},
  1210  	}, {
  1211  		maker: makeSparse{makeReg{4, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1212  		tests: []testFnc{
  1213  			testFill{2, 2, nil},
  1214  			testWrite{"abc", 3, nil},
  1215  			testFill{2, 2, errUnrefData},
  1216  		},
  1217  	}}
  1218  
  1219  	for i, v := range vectors {
  1220  		var wantStr string
  1221  		bb := new(bytes.Buffer)
  1222  		var fw fileWriter
  1223  		switch maker := v.maker.(type) {
  1224  		case makeReg:
  1225  			fw = &regFileWriter{bb, maker.size}
  1226  			wantStr = maker.wantStr
  1227  		case makeSparse:
  1228  			if !validateSparseEntries(maker.sph, maker.size) {
  1229  				t.Fatalf("invalid sparse map: %v", maker.sph)
  1230  			}
  1231  			spd := invertSparseEntries(maker.sph, maker.size)
  1232  			fw = &regFileWriter{bb, maker.makeReg.size}
  1233  			fw = &sparseFileWriter{fw, spd, 0}
  1234  			wantStr = maker.makeReg.wantStr
  1235  		default:
  1236  			t.Fatalf("test %d, unknown make operation: %T", i, maker)
  1237  		}
  1238  
  1239  		for j, tf := range v.tests {
  1240  			switch tf := tf.(type) {
  1241  			case testWrite:
  1242  				got, err := fw.Write([]byte(tf.str))
  1243  				if got != tf.wantCnt || err != tf.wantErr {
  1244  					t.Errorf("test %d.%d, Write(%s):\ngot  (%d, %v)\nwant (%d, %v)", i, j, tf.str, got, err, tf.wantCnt, tf.wantErr)
  1245  				}
  1246  			case testFill:
  1247  				got, err := fw.FillZeros(tf.cnt)
  1248  				if got != tf.wantCnt || err != tf.wantErr {
  1249  					t.Errorf("test %d.%d, FillZeros(%d) = (%d, %v), want (%d, %v)", i, j, tf.cnt, got, err, tf.wantCnt, tf.wantErr)
  1250  				}
  1251  			case testRemaining:
  1252  				got := fw.Remaining()
  1253  				if got != tf.wantCnt {
  1254  					t.Errorf("test %d.%d, Remaining() = %d, want %d", i, j, got, tf.wantCnt)
  1255  				}
  1256  			default:
  1257  				t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
  1258  			}
  1259  		}
  1260  
  1261  		if got := bb.String(); got != wantStr {
  1262  			t.Fatalf("test %d, String() = %q, want %q", i, got, wantStr)
  1263  		}
  1264  	}
  1265  }