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