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