github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/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  		// TODO(dsnet): Re-enable this test when adding sparse support.
   343  		// See https://golang.org/issue/22735
   344  		/*
   345  			}, {
   346  				file: "testdata/gnu-nil-sparse-data.tar",
   347  				tests: []testFnc{
   348  					testHeader{Header{
   349  						Typeflag:    TypeGNUSparse,
   350  						Name:        "sparse.db",
   351  						Size:        1000,
   352  						SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}},
   353  					}, nil},
   354  					testWrite{strings.Repeat("0123456789", 100), 1000, nil},
   355  					testClose{},
   356  				},
   357  			}, {
   358  				file: "testdata/gnu-nil-sparse-hole.tar",
   359  				tests: []testFnc{
   360  					testHeader{Header{
   361  						Typeflag:    TypeGNUSparse,
   362  						Name:        "sparse.db",
   363  						Size:        1000,
   364  						SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}},
   365  					}, nil},
   366  					testWrite{strings.Repeat("\x00", 1000), 1000, nil},
   367  					testClose{},
   368  				},
   369  			}, {
   370  				file: "testdata/pax-nil-sparse-data.tar",
   371  				tests: []testFnc{
   372  					testHeader{Header{
   373  						Typeflag:    TypeReg,
   374  						Name:        "sparse.db",
   375  						Size:        1000,
   376  						SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}},
   377  					}, nil},
   378  					testWrite{strings.Repeat("0123456789", 100), 1000, nil},
   379  					testClose{},
   380  				},
   381  			}, {
   382  				file: "testdata/pax-nil-sparse-hole.tar",
   383  				tests: []testFnc{
   384  					testHeader{Header{
   385  						Typeflag:    TypeReg,
   386  						Name:        "sparse.db",
   387  						Size:        1000,
   388  						SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}},
   389  					}, nil},
   390  					testWrite{strings.Repeat("\x00", 1000), 1000, nil},
   391  					testClose{},
   392  				},
   393  			}, {
   394  				file: "testdata/gnu-sparse-big.tar",
   395  				tests: []testFnc{
   396  					testHeader{Header{
   397  						Typeflag: TypeGNUSparse,
   398  						Name:     "gnu-sparse",
   399  						Size:     6e10,
   400  						SparseHoles: []sparseEntry{
   401  							{Offset: 0e10, Length: 1e10 - 100},
   402  							{Offset: 1e10, Length: 1e10 - 100},
   403  							{Offset: 2e10, Length: 1e10 - 100},
   404  							{Offset: 3e10, Length: 1e10 - 100},
   405  							{Offset: 4e10, Length: 1e10 - 100},
   406  							{Offset: 5e10, Length: 1e10 - 100},
   407  						},
   408  					}, nil},
   409  					testReadFrom{fileOps{
   410  						int64(1e10 - blockSize),
   411  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   412  						int64(1e10 - blockSize),
   413  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   414  						int64(1e10 - blockSize),
   415  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   416  						int64(1e10 - blockSize),
   417  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   418  						int64(1e10 - blockSize),
   419  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   420  						int64(1e10 - blockSize),
   421  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   422  					}, 6e10, nil},
   423  					testClose{nil},
   424  				},
   425  			}, {
   426  				file: "testdata/pax-sparse-big.tar",
   427  				tests: []testFnc{
   428  					testHeader{Header{
   429  						Typeflag: TypeReg,
   430  						Name:     "pax-sparse",
   431  						Size:     6e10,
   432  						SparseHoles: []sparseEntry{
   433  							{Offset: 0e10, Length: 1e10 - 100},
   434  							{Offset: 1e10, Length: 1e10 - 100},
   435  							{Offset: 2e10, Length: 1e10 - 100},
   436  							{Offset: 3e10, Length: 1e10 - 100},
   437  							{Offset: 4e10, Length: 1e10 - 100},
   438  							{Offset: 5e10, Length: 1e10 - 100},
   439  						},
   440  					}, nil},
   441  					testReadFrom{fileOps{
   442  						int64(1e10 - blockSize),
   443  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   444  						int64(1e10 - blockSize),
   445  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   446  						int64(1e10 - blockSize),
   447  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   448  						int64(1e10 - blockSize),
   449  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   450  						int64(1e10 - blockSize),
   451  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   452  						int64(1e10 - blockSize),
   453  						strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10),
   454  					}, 6e10, nil},
   455  					testClose{nil},
   456  				},
   457  		*/
   458  	}, {
   459  		file: "testdata/trailing-slash.tar",
   460  		tests: []testFnc{
   461  			testHeader{Header{Name: strings.Repeat("123456789/", 30)}, nil},
   462  			testClose{nil},
   463  		},
   464  	}, {
   465  		// Automatically promote zero value of Typeflag depending on the name.
   466  		file: "testdata/file-and-dir.tar",
   467  		tests: []testFnc{
   468  			testHeader{Header{Name: "small.txt", Size: 5}, nil},
   469  			testWrite{"Kilts", 5, nil},
   470  			testHeader{Header{Name: "dir/"}, nil},
   471  			testClose{nil},
   472  		},
   473  	}}
   474  
   475  	equalError := func(x, y error) bool {
   476  		_, ok1 := x.(headerError)
   477  		_, ok2 := y.(headerError)
   478  		if ok1 || ok2 {
   479  			return ok1 && ok2
   480  		}
   481  		return x == y
   482  	}
   483  	for _, v := range vectors {
   484  		t.Run(path.Base(v.file), func(t *testing.T) {
   485  			const maxSize = 10 << 10 // 10KiB
   486  			buf := new(bytes.Buffer)
   487  			tw := NewWriter(iotest.TruncateWriter(buf, maxSize))
   488  
   489  			for i, tf := range v.tests {
   490  				switch tf := tf.(type) {
   491  				case testHeader:
   492  					err := tw.WriteHeader(&tf.hdr)
   493  					if !equalError(err, tf.wantErr) {
   494  						t.Fatalf("test %d, WriteHeader() = %v, want %v", i, err, tf.wantErr)
   495  					}
   496  				case testWrite:
   497  					got, err := tw.Write([]byte(tf.str))
   498  					if got != tf.wantCnt || !equalError(err, tf.wantErr) {
   499  						t.Fatalf("test %d, Write() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
   500  					}
   501  				case testReadFrom:
   502  					f := &testFile{ops: tf.ops}
   503  					got, err := tw.readFrom(f)
   504  					if _, ok := err.(testError); ok {
   505  						t.Errorf("test %d, ReadFrom(): %v", i, err)
   506  					} else if got != tf.wantCnt || !equalError(err, tf.wantErr) {
   507  						t.Errorf("test %d, ReadFrom() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
   508  					}
   509  					if len(f.ops) > 0 {
   510  						t.Errorf("test %d, expected %d more operations", i, len(f.ops))
   511  					}
   512  				case testClose:
   513  					err := tw.Close()
   514  					if !equalError(err, tf.wantErr) {
   515  						t.Fatalf("test %d, Close() = %v, want %v", i, err, tf.wantErr)
   516  					}
   517  				default:
   518  					t.Fatalf("test %d, unknown test operation: %T", i, tf)
   519  				}
   520  			}
   521  
   522  			if v.file != "" {
   523  				want, err := ioutil.ReadFile(v.file)
   524  				if err != nil {
   525  					t.Fatalf("ReadFile() = %v, want nil", err)
   526  				}
   527  				got := buf.Bytes()
   528  				if !bytes.Equal(want, got) {
   529  					t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want))
   530  				}
   531  			}
   532  		})
   533  	}
   534  }
   535  
   536  func TestPax(t *testing.T) {
   537  	// Create an archive with a large name
   538  	fileinfo, err := os.Stat("testdata/small.txt")
   539  	if err != nil {
   540  		t.Fatal(err)
   541  	}
   542  	hdr, err := FileInfoHeader(fileinfo, "")
   543  	if err != nil {
   544  		t.Fatalf("os.Stat: %v", err)
   545  	}
   546  	// Force a PAX long name to be written
   547  	longName := strings.Repeat("ab", 100)
   548  	contents := strings.Repeat(" ", int(hdr.Size))
   549  	hdr.Name = longName
   550  	var buf bytes.Buffer
   551  	writer := NewWriter(&buf)
   552  	if err := writer.WriteHeader(hdr); err != nil {
   553  		t.Fatal(err)
   554  	}
   555  	if _, err = writer.Write([]byte(contents)); err != nil {
   556  		t.Fatal(err)
   557  	}
   558  	if err := writer.Close(); err != nil {
   559  		t.Fatal(err)
   560  	}
   561  	// Simple test to make sure PAX extensions are in effect
   562  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   563  		t.Fatal("Expected at least one PAX header to be written.")
   564  	}
   565  	// Test that we can get a long name back out of the archive.
   566  	reader := NewReader(&buf)
   567  	hdr, err = reader.Next()
   568  	if err != nil {
   569  		t.Fatal(err)
   570  	}
   571  	if hdr.Name != longName {
   572  		t.Fatal("Couldn't recover long file name")
   573  	}
   574  }
   575  
   576  func TestPaxSymlink(t *testing.T) {
   577  	// Create an archive with a large linkname
   578  	fileinfo, err := os.Stat("testdata/small.txt")
   579  	if err != nil {
   580  		t.Fatal(err)
   581  	}
   582  	hdr, err := FileInfoHeader(fileinfo, "")
   583  	hdr.Typeflag = TypeSymlink
   584  	if err != nil {
   585  		t.Fatalf("os.Stat:1 %v", err)
   586  	}
   587  	// Force a PAX long linkname to be written
   588  	longLinkname := strings.Repeat("1234567890/1234567890", 10)
   589  	hdr.Linkname = longLinkname
   590  
   591  	hdr.Size = 0
   592  	var buf bytes.Buffer
   593  	writer := NewWriter(&buf)
   594  	if err := writer.WriteHeader(hdr); err != nil {
   595  		t.Fatal(err)
   596  	}
   597  	if err := writer.Close(); err != nil {
   598  		t.Fatal(err)
   599  	}
   600  	// Simple test to make sure PAX extensions are in effect
   601  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   602  		t.Fatal("Expected at least one PAX header to be written.")
   603  	}
   604  	// Test that we can get a long name back out of the archive.
   605  	reader := NewReader(&buf)
   606  	hdr, err = reader.Next()
   607  	if err != nil {
   608  		t.Fatal(err)
   609  	}
   610  	if hdr.Linkname != longLinkname {
   611  		t.Fatal("Couldn't recover long link name")
   612  	}
   613  }
   614  
   615  func TestPaxNonAscii(t *testing.T) {
   616  	// Create an archive with non ascii. These should trigger a pax header
   617  	// because pax headers have a defined utf-8 encoding.
   618  	fileinfo, err := os.Stat("testdata/small.txt")
   619  	if err != nil {
   620  		t.Fatal(err)
   621  	}
   622  
   623  	hdr, err := FileInfoHeader(fileinfo, "")
   624  	if err != nil {
   625  		t.Fatalf("os.Stat:1 %v", err)
   626  	}
   627  
   628  	// some sample data
   629  	chineseFilename := "文件名"
   630  	chineseGroupname := "組"
   631  	chineseUsername := "用戶名"
   632  
   633  	hdr.Name = chineseFilename
   634  	hdr.Gname = chineseGroupname
   635  	hdr.Uname = chineseUsername
   636  
   637  	contents := strings.Repeat(" ", int(hdr.Size))
   638  
   639  	var buf bytes.Buffer
   640  	writer := NewWriter(&buf)
   641  	if err := writer.WriteHeader(hdr); err != nil {
   642  		t.Fatal(err)
   643  	}
   644  	if _, err = writer.Write([]byte(contents)); err != nil {
   645  		t.Fatal(err)
   646  	}
   647  	if err := writer.Close(); err != nil {
   648  		t.Fatal(err)
   649  	}
   650  	// Simple test to make sure PAX extensions are in effect
   651  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   652  		t.Fatal("Expected at least one PAX header to be written.")
   653  	}
   654  	// Test that we can get a long name back out of the archive.
   655  	reader := NewReader(&buf)
   656  	hdr, err = reader.Next()
   657  	if err != nil {
   658  		t.Fatal(err)
   659  	}
   660  	if hdr.Name != chineseFilename {
   661  		t.Fatal("Couldn't recover unicode name")
   662  	}
   663  	if hdr.Gname != chineseGroupname {
   664  		t.Fatal("Couldn't recover unicode group")
   665  	}
   666  	if hdr.Uname != chineseUsername {
   667  		t.Fatal("Couldn't recover unicode user")
   668  	}
   669  }
   670  
   671  func TestPaxXattrs(t *testing.T) {
   672  	xattrs := map[string]string{
   673  		"user.key": "value",
   674  	}
   675  
   676  	// Create an archive with an xattr
   677  	fileinfo, err := os.Stat("testdata/small.txt")
   678  	if err != nil {
   679  		t.Fatal(err)
   680  	}
   681  	hdr, err := FileInfoHeader(fileinfo, "")
   682  	if err != nil {
   683  		t.Fatalf("os.Stat: %v", err)
   684  	}
   685  	contents := "Kilts"
   686  	hdr.Xattrs = xattrs
   687  	var buf bytes.Buffer
   688  	writer := NewWriter(&buf)
   689  	if err := writer.WriteHeader(hdr); err != nil {
   690  		t.Fatal(err)
   691  	}
   692  	if _, err = writer.Write([]byte(contents)); err != nil {
   693  		t.Fatal(err)
   694  	}
   695  	if err := writer.Close(); err != nil {
   696  		t.Fatal(err)
   697  	}
   698  	// Test that we can get the xattrs back out of the archive.
   699  	reader := NewReader(&buf)
   700  	hdr, err = reader.Next()
   701  	if err != nil {
   702  		t.Fatal(err)
   703  	}
   704  	if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
   705  		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
   706  			hdr.Xattrs, xattrs)
   707  	}
   708  }
   709  
   710  func TestPaxHeadersSorted(t *testing.T) {
   711  	fileinfo, err := os.Stat("testdata/small.txt")
   712  	if err != nil {
   713  		t.Fatal(err)
   714  	}
   715  	hdr, err := FileInfoHeader(fileinfo, "")
   716  	if err != nil {
   717  		t.Fatalf("os.Stat: %v", err)
   718  	}
   719  	contents := strings.Repeat(" ", int(hdr.Size))
   720  
   721  	hdr.Xattrs = map[string]string{
   722  		"foo": "foo",
   723  		"bar": "bar",
   724  		"baz": "baz",
   725  		"qux": "qux",
   726  	}
   727  
   728  	var buf bytes.Buffer
   729  	writer := NewWriter(&buf)
   730  	if err := writer.WriteHeader(hdr); err != nil {
   731  		t.Fatal(err)
   732  	}
   733  	if _, err = writer.Write([]byte(contents)); err != nil {
   734  		t.Fatal(err)
   735  	}
   736  	if err := writer.Close(); err != nil {
   737  		t.Fatal(err)
   738  	}
   739  	// Simple test to make sure PAX extensions are in effect
   740  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   741  		t.Fatal("Expected at least one PAX header to be written.")
   742  	}
   743  
   744  	// xattr bar should always appear before others
   745  	indices := []int{
   746  		bytes.Index(buf.Bytes(), []byte("bar=bar")),
   747  		bytes.Index(buf.Bytes(), []byte("baz=baz")),
   748  		bytes.Index(buf.Bytes(), []byte("foo=foo")),
   749  		bytes.Index(buf.Bytes(), []byte("qux=qux")),
   750  	}
   751  	if !sort.IntsAreSorted(indices) {
   752  		t.Fatal("PAX headers are not sorted")
   753  	}
   754  }
   755  
   756  func TestUSTARLongName(t *testing.T) {
   757  	// Create an archive with a path that failed to split with USTAR extension in previous versions.
   758  	fileinfo, err := os.Stat("testdata/small.txt")
   759  	if err != nil {
   760  		t.Fatal(err)
   761  	}
   762  	hdr, err := FileInfoHeader(fileinfo, "")
   763  	hdr.Typeflag = TypeDir
   764  	if err != nil {
   765  		t.Fatalf("os.Stat:1 %v", err)
   766  	}
   767  	// Force a PAX long name to be written. The name was taken from a practical example
   768  	// that fails and replaced ever char through numbers to anonymize the sample.
   769  	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/"
   770  	hdr.Name = longName
   771  
   772  	hdr.Size = 0
   773  	var buf bytes.Buffer
   774  	writer := NewWriter(&buf)
   775  	if err := writer.WriteHeader(hdr); err != nil {
   776  		t.Fatal(err)
   777  	}
   778  	if err := writer.Close(); err != nil {
   779  		t.Fatal(err)
   780  	}
   781  	// Test that we can get a long name back out of the archive.
   782  	reader := NewReader(&buf)
   783  	hdr, err = reader.Next()
   784  	if err != nil {
   785  		t.Fatal(err)
   786  	}
   787  	if hdr.Name != longName {
   788  		t.Fatal("Couldn't recover long name")
   789  	}
   790  }
   791  
   792  func TestValidTypeflagWithPAXHeader(t *testing.T) {
   793  	var buffer bytes.Buffer
   794  	tw := NewWriter(&buffer)
   795  
   796  	fileName := strings.Repeat("ab", 100)
   797  
   798  	hdr := &Header{
   799  		Name:     fileName,
   800  		Size:     4,
   801  		Typeflag: 0,
   802  	}
   803  	if err := tw.WriteHeader(hdr); err != nil {
   804  		t.Fatalf("Failed to write header: %s", err)
   805  	}
   806  	if _, err := tw.Write([]byte("fooo")); err != nil {
   807  		t.Fatalf("Failed to write the file's data: %s", err)
   808  	}
   809  	tw.Close()
   810  
   811  	tr := NewReader(&buffer)
   812  
   813  	for {
   814  		header, err := tr.Next()
   815  		if err == io.EOF {
   816  			break
   817  		}
   818  		if err != nil {
   819  			t.Fatalf("Failed to read header: %s", err)
   820  		}
   821  		if header.Typeflag != TypeReg {
   822  			t.Fatalf("Typeflag should've been %d, found %d", TypeReg, header.Typeflag)
   823  		}
   824  	}
   825  }
   826  
   827  // failOnceWriter fails exactly once and then always reports success.
   828  type failOnceWriter bool
   829  
   830  func (w *failOnceWriter) Write(b []byte) (int, error) {
   831  	if !*w {
   832  		return 0, io.ErrShortWrite
   833  	}
   834  	*w = true
   835  	return len(b), nil
   836  }
   837  
   838  func TestWriterErrors(t *testing.T) {
   839  	t.Run("HeaderOnly", func(t *testing.T) {
   840  		tw := NewWriter(new(bytes.Buffer))
   841  		hdr := &Header{Name: "dir/", Typeflag: TypeDir}
   842  		if err := tw.WriteHeader(hdr); err != nil {
   843  			t.Fatalf("WriteHeader() = %v, want nil", err)
   844  		}
   845  		if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong {
   846  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   847  		}
   848  	})
   849  
   850  	t.Run("NegativeSize", func(t *testing.T) {
   851  		tw := NewWriter(new(bytes.Buffer))
   852  		hdr := &Header{Name: "small.txt", Size: -1}
   853  		if err := tw.WriteHeader(hdr); err == nil {
   854  			t.Fatalf("WriteHeader() = nil, want non-nil error")
   855  		}
   856  	})
   857  
   858  	t.Run("BeforeHeader", func(t *testing.T) {
   859  		tw := NewWriter(new(bytes.Buffer))
   860  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong {
   861  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   862  		}
   863  	})
   864  
   865  	t.Run("AfterClose", func(t *testing.T) {
   866  		tw := NewWriter(new(bytes.Buffer))
   867  		hdr := &Header{Name: "small.txt"}
   868  		if err := tw.WriteHeader(hdr); err != nil {
   869  			t.Fatalf("WriteHeader() = %v, want nil", err)
   870  		}
   871  		if err := tw.Close(); err != nil {
   872  			t.Fatalf("Close() = %v, want nil", err)
   873  		}
   874  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
   875  			t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose)
   876  		}
   877  		if err := tw.Flush(); err != ErrWriteAfterClose {
   878  			t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose)
   879  		}
   880  		if err := tw.Close(); err != nil {
   881  			t.Fatalf("Close() = %v, want nil", err)
   882  		}
   883  	})
   884  
   885  	t.Run("PrematureFlush", func(t *testing.T) {
   886  		tw := NewWriter(new(bytes.Buffer))
   887  		hdr := &Header{Name: "small.txt", Size: 5}
   888  		if err := tw.WriteHeader(hdr); err != nil {
   889  			t.Fatalf("WriteHeader() = %v, want nil", err)
   890  		}
   891  		if err := tw.Flush(); err == nil {
   892  			t.Fatalf("Flush() = %v, want non-nil error", err)
   893  		}
   894  	})
   895  
   896  	t.Run("PrematureClose", func(t *testing.T) {
   897  		tw := NewWriter(new(bytes.Buffer))
   898  		hdr := &Header{Name: "small.txt", Size: 5}
   899  		if err := tw.WriteHeader(hdr); err != nil {
   900  			t.Fatalf("WriteHeader() = %v, want nil", err)
   901  		}
   902  		if err := tw.Close(); err == nil {
   903  			t.Fatalf("Close() = %v, want non-nil error", err)
   904  		}
   905  	})
   906  
   907  	t.Run("Persistence", func(t *testing.T) {
   908  		tw := NewWriter(new(failOnceWriter))
   909  		if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite {
   910  			t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite)
   911  		}
   912  		if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil {
   913  			t.Errorf("WriteHeader() = got %v, want non-nil error", err)
   914  		}
   915  		if _, err := tw.Write(nil); err == nil {
   916  			t.Errorf("Write() = %v, want non-nil error", err)
   917  		}
   918  		if err := tw.Flush(); err == nil {
   919  			t.Errorf("Flush() = %v, want non-nil error", err)
   920  		}
   921  		if err := tw.Close(); err == nil {
   922  			t.Errorf("Close() = %v, want non-nil error", err)
   923  		}
   924  	})
   925  }
   926  
   927  func TestSplitUSTARPath(t *testing.T) {
   928  	sr := strings.Repeat
   929  
   930  	vectors := []struct {
   931  		input  string // Input path
   932  		prefix string // Expected output prefix
   933  		suffix string // Expected output suffix
   934  		ok     bool   // Split success?
   935  	}{
   936  		{"", "", "", false},
   937  		{"abc", "", "", false},
   938  		{"用戶名", "", "", false},
   939  		{sr("a", nameSize), "", "", false},
   940  		{sr("a", nameSize) + "/", "", "", false},
   941  		{sr("a", nameSize) + "/a", sr("a", nameSize), "a", true},
   942  		{sr("a", prefixSize) + "/", "", "", false},
   943  		{sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true},
   944  		{sr("a", nameSize+1), "", "", false},
   945  		{sr("/", nameSize+1), sr("/", nameSize-1), "/", true},
   946  		{sr("a", prefixSize) + "/" + sr("b", nameSize),
   947  			sr("a", prefixSize), sr("b", nameSize), true},
   948  		{sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false},
   949  		{sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true},
   950  	}
   951  
   952  	for _, v := range vectors {
   953  		prefix, suffix, ok := splitUSTARPath(v.input)
   954  		if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
   955  			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)",
   956  				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
   957  		}
   958  	}
   959  }
   960  
   961  // TestIssue12594 tests that the Writer does not attempt to populate the prefix
   962  // field when encoding a header in the GNU format. The prefix field is valid
   963  // in USTAR and PAX, but not GNU.
   964  func TestIssue12594(t *testing.T) {
   965  	names := []string{
   966  		"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",
   967  		"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",
   968  		"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",
   969  		"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",
   970  		"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt",
   971  		"/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend",
   972  	}
   973  
   974  	for i, name := range names {
   975  		var b bytes.Buffer
   976  
   977  		tw := NewWriter(&b)
   978  		if err := tw.WriteHeader(&Header{
   979  			Name: name,
   980  			Uid:  1 << 25, // Prevent USTAR format
   981  		}); err != nil {
   982  			t.Errorf("test %d, unexpected WriteHeader error: %v", i, err)
   983  		}
   984  		if err := tw.Close(); err != nil {
   985  			t.Errorf("test %d, unexpected Close error: %v", i, err)
   986  		}
   987  
   988  		// The prefix field should never appear in the GNU format.
   989  		var blk block
   990  		copy(blk[:], b.Bytes())
   991  		prefix := string(blk.USTAR().Prefix())
   992  		if i := strings.IndexByte(prefix, 0); i >= 0 {
   993  			prefix = prefix[:i] // Truncate at the NUL terminator
   994  		}
   995  		if blk.GetFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
   996  			t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
   997  		}
   998  
   999  		tr := NewReader(&b)
  1000  		hdr, err := tr.Next()
  1001  		if err != nil {
  1002  			t.Errorf("test %d, unexpected Next error: %v", i, err)
  1003  		}
  1004  		if hdr.Name != name {
  1005  			t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name)
  1006  		}
  1007  	}
  1008  }
  1009  
  1010  // testNonEmptyWriter wraps an io.Writer and ensures that
  1011  // Write is never called with an empty buffer.
  1012  type testNonEmptyWriter struct{ io.Writer }
  1013  
  1014  func (w testNonEmptyWriter) Write(b []byte) (int, error) {
  1015  	if len(b) == 0 {
  1016  		return 0, errors.New("unexpected empty Write call")
  1017  	}
  1018  	return w.Writer.Write(b)
  1019  }
  1020  
  1021  func TestFileWriter(t *testing.T) {
  1022  	type (
  1023  		testWrite struct { // Write(str) == (wantCnt, wantErr)
  1024  			str     string
  1025  			wantCnt int
  1026  			wantErr error
  1027  		}
  1028  		testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr)
  1029  			ops     fileOps
  1030  			wantCnt int64
  1031  			wantErr error
  1032  		}
  1033  		testRemaining struct { // LogicalRemaining() == wantLCnt, PhysicalRemaining() == wantPCnt
  1034  			wantLCnt int64
  1035  			wantPCnt int64
  1036  		}
  1037  		testFnc interface{} // testWrite | testReadFrom | testRemaining
  1038  	)
  1039  
  1040  	type (
  1041  		makeReg struct {
  1042  			size    int64
  1043  			wantStr string
  1044  		}
  1045  		makeSparse struct {
  1046  			makeReg makeReg
  1047  			sph     sparseHoles
  1048  			size    int64
  1049  		}
  1050  		fileMaker interface{} // makeReg | makeSparse
  1051  	)
  1052  
  1053  	vectors := []struct {
  1054  		maker fileMaker
  1055  		tests []testFnc
  1056  	}{{
  1057  		maker: makeReg{0, ""},
  1058  		tests: []testFnc{
  1059  			testRemaining{0, 0},
  1060  			testWrite{"", 0, nil},
  1061  			testWrite{"a", 0, ErrWriteTooLong},
  1062  			testReadFrom{fileOps{""}, 0, nil},
  1063  			testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong},
  1064  			testRemaining{0, 0},
  1065  		},
  1066  	}, {
  1067  		maker: makeReg{1, "a"},
  1068  		tests: []testFnc{
  1069  			testRemaining{1, 1},
  1070  			testWrite{"", 0, nil},
  1071  			testWrite{"a", 1, nil},
  1072  			testWrite{"bcde", 0, ErrWriteTooLong},
  1073  			testWrite{"", 0, nil},
  1074  			testReadFrom{fileOps{""}, 0, nil},
  1075  			testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong},
  1076  			testRemaining{0, 0},
  1077  		},
  1078  	}, {
  1079  		maker: makeReg{5, "hello"},
  1080  		tests: []testFnc{
  1081  			testRemaining{5, 5},
  1082  			testWrite{"hello", 5, nil},
  1083  			testRemaining{0, 0},
  1084  		},
  1085  	}, {
  1086  		maker: makeReg{5, "\x00\x00\x00\x00\x00"},
  1087  		tests: []testFnc{
  1088  			testRemaining{5, 5},
  1089  			testReadFrom{fileOps{"\x00\x00\x00\x00\x00"}, 5, nil},
  1090  			testRemaining{0, 0},
  1091  		},
  1092  	}, {
  1093  		maker: makeReg{5, "\x00\x00\x00\x00\x00"},
  1094  		tests: []testFnc{
  1095  			testRemaining{5, 5},
  1096  			testReadFrom{fileOps{"\x00\x00\x00\x00\x00extra"}, 5, ErrWriteTooLong},
  1097  			testRemaining{0, 0},
  1098  		},
  1099  	}, {
  1100  		maker: makeReg{5, "abc\x00\x00"},
  1101  		tests: []testFnc{
  1102  			testRemaining{5, 5},
  1103  			testWrite{"abc", 3, nil},
  1104  			testRemaining{2, 2},
  1105  			testReadFrom{fileOps{"\x00\x00"}, 2, nil},
  1106  			testRemaining{0, 0},
  1107  		},
  1108  	}, {
  1109  		maker: makeReg{5, "\x00\x00abc"},
  1110  		tests: []testFnc{
  1111  			testRemaining{5, 5},
  1112  			testWrite{"\x00\x00", 2, nil},
  1113  			testRemaining{3, 3},
  1114  			testWrite{"abc", 3, nil},
  1115  			testReadFrom{fileOps{"z"}, 0, ErrWriteTooLong},
  1116  			testWrite{"z", 0, ErrWriteTooLong},
  1117  			testRemaining{0, 0},
  1118  		},
  1119  	}, {
  1120  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1121  		tests: []testFnc{
  1122  			testRemaining{8, 5},
  1123  			testWrite{"ab\x00\x00\x00cde", 8, nil},
  1124  			testWrite{"a", 0, ErrWriteTooLong},
  1125  			testRemaining{0, 0},
  1126  		},
  1127  	}, {
  1128  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1129  		tests: []testFnc{
  1130  			testWrite{"ab\x00\x00\x00cdez", 8, ErrWriteTooLong},
  1131  			testRemaining{0, 0},
  1132  		},
  1133  	}, {
  1134  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1135  		tests: []testFnc{
  1136  			testWrite{"ab\x00", 3, nil},
  1137  			testRemaining{5, 3},
  1138  			testWrite{"\x00\x00cde", 5, nil},
  1139  			testWrite{"a", 0, ErrWriteTooLong},
  1140  			testRemaining{0, 0},
  1141  		},
  1142  	}, {
  1143  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1144  		tests: []testFnc{
  1145  			testWrite{"ab", 2, nil},
  1146  			testRemaining{6, 3},
  1147  			testReadFrom{fileOps{int64(3), "cde"}, 6, nil},
  1148  			testRemaining{0, 0},
  1149  		},
  1150  	}, {
  1151  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1152  		tests: []testFnc{
  1153  			testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, nil},
  1154  			testRemaining{0, 0},
  1155  		},
  1156  	}, {
  1157  		maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
  1158  		tests: []testFnc{
  1159  			testReadFrom{fileOps{"ab", int64(3), "cdeX"}, 8, ErrWriteTooLong},
  1160  			testRemaining{0, 0},
  1161  		},
  1162  	}, {
  1163  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1164  		tests: []testFnc{
  1165  			testReadFrom{fileOps{"ab", int64(3), "cd"}, 7, io.ErrUnexpectedEOF},
  1166  			testRemaining{1, 0},
  1167  		},
  1168  	}, {
  1169  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1170  		tests: []testFnc{
  1171  			testReadFrom{fileOps{"ab", int64(3), "cde"}, 7, errMissData},
  1172  			testRemaining{1, 0},
  1173  		},
  1174  	}, {
  1175  		maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
  1176  		tests: []testFnc{
  1177  			testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, errUnrefData},
  1178  			testRemaining{0, 1},
  1179  		},
  1180  	}, {
  1181  		maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
  1182  		tests: []testFnc{
  1183  			testWrite{"ab", 2, nil},
  1184  			testRemaining{6, 2},
  1185  			testWrite{"\x00\x00\x00", 3, nil},
  1186  			testRemaining{3, 2},
  1187  			testWrite{"cde", 2, errMissData},
  1188  			testRemaining{1, 0},
  1189  		},
  1190  	}, {
  1191  		maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
  1192  		tests: []testFnc{
  1193  			testWrite{"ab", 2, nil},
  1194  			testRemaining{6, 4},
  1195  			testWrite{"\x00\x00\x00", 3, nil},
  1196  			testRemaining{3, 4},
  1197  			testWrite{"cde", 3, errUnrefData},
  1198  			testRemaining{0, 1},
  1199  		},
  1200  	}, {
  1201  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1202  		tests: []testFnc{
  1203  			testRemaining{7, 3},
  1204  			testWrite{"\x00\x00abc\x00\x00", 7, nil},
  1205  			testRemaining{0, 0},
  1206  		},
  1207  	}, {
  1208  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1209  		tests: []testFnc{
  1210  			testRemaining{7, 3},
  1211  			testReadFrom{fileOps{int64(2), "abc", int64(1), "\x00"}, 7, nil},
  1212  			testRemaining{0, 0},
  1213  		},
  1214  	}, {
  1215  		maker: makeSparse{makeReg{3, ""}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1216  		tests: []testFnc{
  1217  			testWrite{"abcdefg", 0, errWriteHole},
  1218  		},
  1219  	}, {
  1220  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1221  		tests: []testFnc{
  1222  			testWrite{"\x00\x00abcde", 5, errWriteHole},
  1223  		},
  1224  	}, {
  1225  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1226  		tests: []testFnc{
  1227  			testWrite{"\x00\x00abc\x00\x00z", 7, ErrWriteTooLong},
  1228  			testRemaining{0, 0},
  1229  		},
  1230  	}, {
  1231  		maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1232  		tests: []testFnc{
  1233  			testWrite{"\x00\x00", 2, nil},
  1234  			testRemaining{5, 3},
  1235  			testWrite{"abc", 3, nil},
  1236  			testRemaining{2, 0},
  1237  			testWrite{"\x00\x00", 2, nil},
  1238  			testRemaining{0, 0},
  1239  		},
  1240  	}, {
  1241  		maker: makeSparse{makeReg{2, "ab"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1242  		tests: []testFnc{
  1243  			testWrite{"\x00\x00", 2, nil},
  1244  			testWrite{"abc", 2, errMissData},
  1245  			testWrite{"\x00\x00", 0, errMissData},
  1246  		},
  1247  	}, {
  1248  		maker: makeSparse{makeReg{4, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
  1249  		tests: []testFnc{
  1250  			testWrite{"\x00\x00", 2, nil},
  1251  			testWrite{"abc", 3, nil},
  1252  			testWrite{"\x00\x00", 2, errUnrefData},
  1253  		},
  1254  	}}
  1255  
  1256  	for i, v := range vectors {
  1257  		var wantStr string
  1258  		bb := new(bytes.Buffer)
  1259  		w := testNonEmptyWriter{bb}
  1260  		var fw fileWriter
  1261  		switch maker := v.maker.(type) {
  1262  		case makeReg:
  1263  			fw = &regFileWriter{w, maker.size}
  1264  			wantStr = maker.wantStr
  1265  		case makeSparse:
  1266  			if !validateSparseEntries(maker.sph, maker.size) {
  1267  				t.Fatalf("invalid sparse map: %v", maker.sph)
  1268  			}
  1269  			spd := invertSparseEntries(maker.sph, maker.size)
  1270  			fw = &regFileWriter{w, maker.makeReg.size}
  1271  			fw = &sparseFileWriter{fw, spd, 0}
  1272  			wantStr = maker.makeReg.wantStr
  1273  		default:
  1274  			t.Fatalf("test %d, unknown make operation: %T", i, maker)
  1275  		}
  1276  
  1277  		for j, tf := range v.tests {
  1278  			switch tf := tf.(type) {
  1279  			case testWrite:
  1280  				got, err := fw.Write([]byte(tf.str))
  1281  				if got != tf.wantCnt || err != tf.wantErr {
  1282  					t.Errorf("test %d.%d, Write(%s):\ngot  (%d, %v)\nwant (%d, %v)", i, j, tf.str, got, err, tf.wantCnt, tf.wantErr)
  1283  				}
  1284  			case testReadFrom:
  1285  				f := &testFile{ops: tf.ops}
  1286  				got, err := fw.ReadFrom(f)
  1287  				if _, ok := err.(testError); ok {
  1288  					t.Errorf("test %d.%d, ReadFrom(): %v", i, j, err)
  1289  				} else if got != tf.wantCnt || err != tf.wantErr {
  1290  					t.Errorf("test %d.%d, ReadFrom() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)
  1291  				}
  1292  				if len(f.ops) > 0 {
  1293  					t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
  1294  				}
  1295  			case testRemaining:
  1296  				if got := fw.LogicalRemaining(); got != tf.wantLCnt {
  1297  					t.Errorf("test %d.%d, LogicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
  1298  				}
  1299  				if got := fw.PhysicalRemaining(); got != tf.wantPCnt {
  1300  					t.Errorf("test %d.%d, PhysicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
  1301  				}
  1302  			default:
  1303  				t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
  1304  			}
  1305  		}
  1306  
  1307  		if got := bb.String(); got != wantStr {
  1308  			t.Fatalf("test %d, String() = %q, want %q", i, got, wantStr)
  1309  		}
  1310  	}
  1311  }