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