Black Lives Matter. Support the Equal Justice Initiative.

Source file src/archive/tar/writer_test.go

Documentation: archive/tar

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

View as plain text