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