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