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