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