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