github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/archive/tar/writer_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tar 6 7 import ( 8 "bytes" 9 "encoding/hex" 10 "errors" 11 "io" 12 "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(str) == (wantCnt, wantErr) 58 str string 59 wantCnt int 60 wantErr error 61 } 62 testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr) 63 ops fileOps 64 wantCnt int64 65 wantErr error 66 } 67 testClose struct { // Close() == wantErr 68 wantErr error 69 } 70 testFnc any // testHeader | testWrite | testReadFrom | 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 // TODO(dsnet): Re-enable this test when adding sparse support. 342 // See https://golang.org/issue/22735 343 /* 344 }, { 345 file: "testdata/gnu-nil-sparse-data.tar", 346 tests: []testFnc{ 347 testHeader{Header{ 348 Typeflag: TypeGNUSparse, 349 Name: "sparse.db", 350 Size: 1000, 351 SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}}, 352 }, nil}, 353 testWrite{strings.Repeat("0123456789", 100), 1000, nil}, 354 testClose{}, 355 }, 356 }, { 357 file: "testdata/gnu-nil-sparse-hole.tar", 358 tests: []testFnc{ 359 testHeader{Header{ 360 Typeflag: TypeGNUSparse, 361 Name: "sparse.db", 362 Size: 1000, 363 SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}}, 364 }, nil}, 365 testWrite{strings.Repeat("\x00", 1000), 1000, nil}, 366 testClose{}, 367 }, 368 }, { 369 file: "testdata/pax-nil-sparse-data.tar", 370 tests: []testFnc{ 371 testHeader{Header{ 372 Typeflag: TypeReg, 373 Name: "sparse.db", 374 Size: 1000, 375 SparseHoles: []sparseEntry{{Offset: 1000, Length: 0}}, 376 }, nil}, 377 testWrite{strings.Repeat("0123456789", 100), 1000, nil}, 378 testClose{}, 379 }, 380 }, { 381 file: "testdata/pax-nil-sparse-hole.tar", 382 tests: []testFnc{ 383 testHeader{Header{ 384 Typeflag: TypeReg, 385 Name: "sparse.db", 386 Size: 1000, 387 SparseHoles: []sparseEntry{{Offset: 0, Length: 1000}}, 388 }, nil}, 389 testWrite{strings.Repeat("\x00", 1000), 1000, nil}, 390 testClose{}, 391 }, 392 }, { 393 file: "testdata/gnu-sparse-big.tar", 394 tests: []testFnc{ 395 testHeader{Header{ 396 Typeflag: TypeGNUSparse, 397 Name: "gnu-sparse", 398 Size: 6e10, 399 SparseHoles: []sparseEntry{ 400 {Offset: 0e10, Length: 1e10 - 100}, 401 {Offset: 1e10, Length: 1e10 - 100}, 402 {Offset: 2e10, Length: 1e10 - 100}, 403 {Offset: 3e10, Length: 1e10 - 100}, 404 {Offset: 4e10, Length: 1e10 - 100}, 405 {Offset: 5e10, Length: 1e10 - 100}, 406 }, 407 }, nil}, 408 testReadFrom{fileOps{ 409 int64(1e10 - blockSize), 410 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 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 }, 6e10, nil}, 422 testClose{nil}, 423 }, 424 }, { 425 file: "testdata/pax-sparse-big.tar", 426 tests: []testFnc{ 427 testHeader{Header{ 428 Typeflag: TypeReg, 429 Name: "pax-sparse", 430 Size: 6e10, 431 SparseHoles: []sparseEntry{ 432 {Offset: 0e10, Length: 1e10 - 100}, 433 {Offset: 1e10, Length: 1e10 - 100}, 434 {Offset: 2e10, Length: 1e10 - 100}, 435 {Offset: 3e10, Length: 1e10 - 100}, 436 {Offset: 4e10, Length: 1e10 - 100}, 437 {Offset: 5e10, Length: 1e10 - 100}, 438 }, 439 }, nil}, 440 testReadFrom{fileOps{ 441 int64(1e10 - blockSize), 442 strings.Repeat("\x00", blockSize-100) + strings.Repeat("0123456789", 10), 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 }, 6e10, nil}, 454 testClose{nil}, 455 }, 456 */ 457 }, { 458 file: "testdata/trailing-slash.tar", 459 tests: []testFnc{ 460 testHeader{Header{Name: strings.Repeat("123456789/", 30)}, nil}, 461 testClose{nil}, 462 }, 463 }, { 464 // Automatically promote zero value of Typeflag depending on the name. 465 file: "testdata/file-and-dir.tar", 466 tests: []testFnc{ 467 testHeader{Header{Name: "small.txt", Size: 5}, nil}, 468 testWrite{"Kilts", 5, nil}, 469 testHeader{Header{Name: "dir/"}, nil}, 470 testClose{nil}, 471 }, 472 }} 473 474 equalError := func(x, y error) bool { 475 _, ok1 := x.(headerError) 476 _, ok2 := y.(headerError) 477 if ok1 || ok2 { 478 return ok1 && ok2 479 } 480 return x == y 481 } 482 for _, v := range vectors { 483 t.Run(path.Base(v.file), func(t *testing.T) { 484 const maxSize = 10 << 10 // 10KiB 485 buf := new(bytes.Buffer) 486 tw := NewWriter(iotest.TruncateWriter(buf, maxSize)) 487 488 for i, tf := range v.tests { 489 switch tf := tf.(type) { 490 case testHeader: 491 err := tw.WriteHeader(&tf.hdr) 492 if !equalError(err, tf.wantErr) { 493 t.Fatalf("test %d, WriteHeader() = %v, want %v", i, err, tf.wantErr) 494 } 495 case testWrite: 496 got, err := tw.Write([]byte(tf.str)) 497 if got != tf.wantCnt || !equalError(err, tf.wantErr) { 498 t.Fatalf("test %d, Write() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr) 499 } 500 case testReadFrom: 501 f := &testFile{ops: tf.ops} 502 got, err := tw.readFrom(f) 503 if _, ok := err.(testError); ok { 504 t.Errorf("test %d, ReadFrom(): %v", i, err) 505 } else if got != tf.wantCnt || !equalError(err, tf.wantErr) { 506 t.Errorf("test %d, ReadFrom() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr) 507 } 508 if len(f.ops) > 0 { 509 t.Errorf("test %d, expected %d more operations", i, len(f.ops)) 510 } 511 case testClose: 512 err := tw.Close() 513 if !equalError(err, tf.wantErr) { 514 t.Fatalf("test %d, Close() = %v, want %v", i, err, tf.wantErr) 515 } 516 default: 517 t.Fatalf("test %d, unknown test operation: %T", i, tf) 518 } 519 } 520 521 if v.file != "" { 522 want, err := os.ReadFile(v.file) 523 if err != nil { 524 t.Fatalf("ReadFile() = %v, want nil", err) 525 } 526 got := buf.Bytes() 527 if !bytes.Equal(want, got) { 528 t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want)) 529 } 530 } 531 }) 532 } 533 } 534 535 func TestPax(t *testing.T) { 536 // Create an archive with a large name 537 fileinfo, err := os.Stat("testdata/small.txt") 538 if err != nil { 539 t.Fatal(err) 540 } 541 hdr, err := FileInfoHeader(fileinfo, "") 542 if err != nil { 543 t.Fatalf("os.Stat: %v", err) 544 } 545 // Force a PAX long name to be written 546 longName := strings.Repeat("ab", 100) 547 contents := strings.Repeat(" ", int(hdr.Size)) 548 hdr.Name = longName 549 var buf bytes.Buffer 550 writer := NewWriter(&buf) 551 if err := writer.WriteHeader(hdr); err != nil { 552 t.Fatal(err) 553 } 554 if _, err = writer.Write([]byte(contents)); err != nil { 555 t.Fatal(err) 556 } 557 if err := writer.Close(); err != nil { 558 t.Fatal(err) 559 } 560 // Simple test to make sure PAX extensions are in effect 561 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 562 t.Fatal("Expected at least one PAX header to be written.") 563 } 564 // Test that we can get a long name back out of the archive. 565 reader := NewReader(&buf) 566 hdr, err = reader.Next() 567 if err != nil { 568 t.Fatal(err) 569 } 570 if hdr.Name != longName { 571 t.Fatal("Couldn't recover long file name") 572 } 573 } 574 575 func TestPaxSymlink(t *testing.T) { 576 // Create an archive with a large linkname 577 fileinfo, err := os.Stat("testdata/small.txt") 578 if err != nil { 579 t.Fatal(err) 580 } 581 hdr, err := FileInfoHeader(fileinfo, "") 582 hdr.Typeflag = TypeSymlink 583 if err != nil { 584 t.Fatalf("os.Stat:1 %v", err) 585 } 586 // Force a PAX long linkname to be written 587 longLinkname := strings.Repeat("1234567890/1234567890", 10) 588 hdr.Linkname = longLinkname 589 590 hdr.Size = 0 591 var buf bytes.Buffer 592 writer := NewWriter(&buf) 593 if err := writer.WriteHeader(hdr); err != nil { 594 t.Fatal(err) 595 } 596 if err := writer.Close(); err != nil { 597 t.Fatal(err) 598 } 599 // Simple test to make sure PAX extensions are in effect 600 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 601 t.Fatal("Expected at least one PAX header to be written.") 602 } 603 // Test that we can get a long name back out of the archive. 604 reader := NewReader(&buf) 605 hdr, err = reader.Next() 606 if err != nil { 607 t.Fatal(err) 608 } 609 if hdr.Linkname != longLinkname { 610 t.Fatal("Couldn't recover long link name") 611 } 612 } 613 614 func TestPaxNonAscii(t *testing.T) { 615 // Create an archive with non ascii. These should trigger a pax header 616 // because pax headers have a defined utf-8 encoding. 617 fileinfo, err := os.Stat("testdata/small.txt") 618 if err != nil { 619 t.Fatal(err) 620 } 621 622 hdr, err := FileInfoHeader(fileinfo, "") 623 if err != nil { 624 t.Fatalf("os.Stat:1 %v", err) 625 } 626 627 // some sample data 628 chineseFilename := "文件名" 629 chineseGroupname := "組" 630 chineseUsername := "用戶名" 631 632 hdr.Name = chineseFilename 633 hdr.Gname = chineseGroupname 634 hdr.Uname = chineseUsername 635 636 contents := strings.Repeat(" ", int(hdr.Size)) 637 638 var buf bytes.Buffer 639 writer := NewWriter(&buf) 640 if err := writer.WriteHeader(hdr); err != nil { 641 t.Fatal(err) 642 } 643 if _, err = writer.Write([]byte(contents)); err != nil { 644 t.Fatal(err) 645 } 646 if err := writer.Close(); err != nil { 647 t.Fatal(err) 648 } 649 // Simple test to make sure PAX extensions are in effect 650 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 651 t.Fatal("Expected at least one PAX header to be written.") 652 } 653 // Test that we can get a long name back out of the archive. 654 reader := NewReader(&buf) 655 hdr, err = reader.Next() 656 if err != nil { 657 t.Fatal(err) 658 } 659 if hdr.Name != chineseFilename { 660 t.Fatal("Couldn't recover unicode name") 661 } 662 if hdr.Gname != chineseGroupname { 663 t.Fatal("Couldn't recover unicode group") 664 } 665 if hdr.Uname != chineseUsername { 666 t.Fatal("Couldn't recover unicode user") 667 } 668 } 669 670 func TestPaxXattrs(t *testing.T) { 671 xattrs := map[string]string{ 672 "user.key": "value", 673 } 674 675 // Create an archive with an xattr 676 fileinfo, err := os.Stat("testdata/small.txt") 677 if err != nil { 678 t.Fatal(err) 679 } 680 hdr, err := FileInfoHeader(fileinfo, "") 681 if err != nil { 682 t.Fatalf("os.Stat: %v", err) 683 } 684 contents := "Kilts" 685 hdr.Xattrs = xattrs 686 var buf bytes.Buffer 687 writer := NewWriter(&buf) 688 if err := writer.WriteHeader(hdr); err != nil { 689 t.Fatal(err) 690 } 691 if _, err = writer.Write([]byte(contents)); err != nil { 692 t.Fatal(err) 693 } 694 if err := writer.Close(); err != nil { 695 t.Fatal(err) 696 } 697 // Test that we can get the xattrs back out of the archive. 698 reader := NewReader(&buf) 699 hdr, err = reader.Next() 700 if err != nil { 701 t.Fatal(err) 702 } 703 if !reflect.DeepEqual(hdr.Xattrs, xattrs) { 704 t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", 705 hdr.Xattrs, xattrs) 706 } 707 } 708 709 func TestPaxHeadersSorted(t *testing.T) { 710 fileinfo, err := os.Stat("testdata/small.txt") 711 if err != nil { 712 t.Fatal(err) 713 } 714 hdr, err := FileInfoHeader(fileinfo, "") 715 if err != nil { 716 t.Fatalf("os.Stat: %v", err) 717 } 718 contents := strings.Repeat(" ", int(hdr.Size)) 719 720 hdr.Xattrs = map[string]string{ 721 "foo": "foo", 722 "bar": "bar", 723 "baz": "baz", 724 "qux": "qux", 725 } 726 727 var buf bytes.Buffer 728 writer := NewWriter(&buf) 729 if err := writer.WriteHeader(hdr); err != nil { 730 t.Fatal(err) 731 } 732 if _, err = writer.Write([]byte(contents)); err != nil { 733 t.Fatal(err) 734 } 735 if err := writer.Close(); err != nil { 736 t.Fatal(err) 737 } 738 // Simple test to make sure PAX extensions are in effect 739 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 740 t.Fatal("Expected at least one PAX header to be written.") 741 } 742 743 // xattr bar should always appear before others 744 indices := []int{ 745 bytes.Index(buf.Bytes(), []byte("bar=bar")), 746 bytes.Index(buf.Bytes(), []byte("baz=baz")), 747 bytes.Index(buf.Bytes(), []byte("foo=foo")), 748 bytes.Index(buf.Bytes(), []byte("qux=qux")), 749 } 750 if !sort.IntsAreSorted(indices) { 751 t.Fatal("PAX headers are not sorted") 752 } 753 } 754 755 func TestUSTARLongName(t *testing.T) { 756 // Create an archive with a path that failed to split with USTAR extension in previous versions. 757 fileinfo, err := os.Stat("testdata/small.txt") 758 if err != nil { 759 t.Fatal(err) 760 } 761 hdr, err := FileInfoHeader(fileinfo, "") 762 hdr.Typeflag = TypeDir 763 if err != nil { 764 t.Fatalf("os.Stat:1 %v", err) 765 } 766 // Force a PAX long name to be written. The name was taken from a practical example 767 // that fails and replaced ever char through numbers to anonymize the sample. 768 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/" 769 hdr.Name = longName 770 771 hdr.Size = 0 772 var buf bytes.Buffer 773 writer := NewWriter(&buf) 774 if err := writer.WriteHeader(hdr); err != nil { 775 t.Fatal(err) 776 } 777 if err := writer.Close(); err != nil { 778 t.Fatal(err) 779 } 780 // Test that we can get a long name back out of the archive. 781 reader := NewReader(&buf) 782 hdr, err = reader.Next() 783 if err != nil && err != ErrInsecurePath { 784 t.Fatal(err) 785 } 786 if hdr.Name != longName { 787 t.Fatal("Couldn't recover long name") 788 } 789 } 790 791 func TestValidTypeflagWithPAXHeader(t *testing.T) { 792 var buffer bytes.Buffer 793 tw := NewWriter(&buffer) 794 795 fileName := strings.Repeat("ab", 100) 796 797 hdr := &Header{ 798 Name: fileName, 799 Size: 4, 800 Typeflag: 0, 801 } 802 if err := tw.WriteHeader(hdr); err != nil { 803 t.Fatalf("Failed to write header: %s", err) 804 } 805 if _, err := tw.Write([]byte("fooo")); err != nil { 806 t.Fatalf("Failed to write the file's data: %s", err) 807 } 808 tw.Close() 809 810 tr := NewReader(&buffer) 811 812 for { 813 header, err := tr.Next() 814 if err == io.EOF { 815 break 816 } 817 if err != nil { 818 t.Fatalf("Failed to read header: %s", err) 819 } 820 if header.Typeflag != TypeReg { 821 t.Fatalf("Typeflag should've been %d, found %d", TypeReg, header.Typeflag) 822 } 823 } 824 } 825 826 // failOnceWriter fails exactly once and then always reports success. 827 type failOnceWriter bool 828 829 func (w *failOnceWriter) Write(b []byte) (int, error) { 830 if !*w { 831 return 0, io.ErrShortWrite 832 } 833 *w = true 834 return len(b), nil 835 } 836 837 func TestWriterErrors(t *testing.T) { 838 t.Run("HeaderOnly", func(t *testing.T) { 839 tw := NewWriter(new(bytes.Buffer)) 840 hdr := &Header{Name: "dir/", Typeflag: TypeDir} 841 if err := tw.WriteHeader(hdr); err != nil { 842 t.Fatalf("WriteHeader() = %v, want nil", err) 843 } 844 if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong { 845 t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong) 846 } 847 }) 848 849 t.Run("NegativeSize", func(t *testing.T) { 850 tw := NewWriter(new(bytes.Buffer)) 851 hdr := &Header{Name: "small.txt", Size: -1} 852 if err := tw.WriteHeader(hdr); err == nil { 853 t.Fatalf("WriteHeader() = nil, want non-nil error") 854 } 855 }) 856 857 t.Run("BeforeHeader", func(t *testing.T) { 858 tw := NewWriter(new(bytes.Buffer)) 859 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong { 860 t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong) 861 } 862 }) 863 864 t.Run("AfterClose", func(t *testing.T) { 865 tw := NewWriter(new(bytes.Buffer)) 866 hdr := &Header{Name: "small.txt"} 867 if err := tw.WriteHeader(hdr); err != nil { 868 t.Fatalf("WriteHeader() = %v, want nil", err) 869 } 870 if err := tw.Close(); err != nil { 871 t.Fatalf("Close() = %v, want nil", err) 872 } 873 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose { 874 t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose) 875 } 876 if err := tw.Flush(); err != ErrWriteAfterClose { 877 t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose) 878 } 879 if err := tw.Close(); err != nil { 880 t.Fatalf("Close() = %v, want nil", err) 881 } 882 }) 883 884 t.Run("PrematureFlush", func(t *testing.T) { 885 tw := NewWriter(new(bytes.Buffer)) 886 hdr := &Header{Name: "small.txt", Size: 5} 887 if err := tw.WriteHeader(hdr); err != nil { 888 t.Fatalf("WriteHeader() = %v, want nil", err) 889 } 890 if err := tw.Flush(); err == nil { 891 t.Fatalf("Flush() = %v, want non-nil error", err) 892 } 893 }) 894 895 t.Run("PrematureClose", func(t *testing.T) { 896 tw := NewWriter(new(bytes.Buffer)) 897 hdr := &Header{Name: "small.txt", Size: 5} 898 if err := tw.WriteHeader(hdr); err != nil { 899 t.Fatalf("WriteHeader() = %v, want nil", err) 900 } 901 if err := tw.Close(); err == nil { 902 t.Fatalf("Close() = %v, want non-nil error", err) 903 } 904 }) 905 906 t.Run("Persistence", func(t *testing.T) { 907 tw := NewWriter(new(failOnceWriter)) 908 if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite { 909 t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite) 910 } 911 if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil { 912 t.Errorf("WriteHeader() = got %v, want non-nil error", err) 913 } 914 if _, err := tw.Write(nil); err == nil { 915 t.Errorf("Write() = %v, want non-nil error", err) 916 } 917 if err := tw.Flush(); err == nil { 918 t.Errorf("Flush() = %v, want non-nil error", err) 919 } 920 if err := tw.Close(); err == nil { 921 t.Errorf("Close() = %v, want non-nil error", err) 922 } 923 }) 924 } 925 926 func TestSplitUSTARPath(t *testing.T) { 927 sr := strings.Repeat 928 929 vectors := []struct { 930 input string // Input path 931 prefix string // Expected output prefix 932 suffix string // Expected output suffix 933 ok bool // Split success? 934 }{ 935 {"", "", "", false}, 936 {"abc", "", "", false}, 937 {"用戶名", "", "", false}, 938 {sr("a", nameSize), "", "", false}, 939 {sr("a", nameSize) + "/", "", "", false}, 940 {sr("a", nameSize) + "/a", sr("a", nameSize), "a", true}, 941 {sr("a", prefixSize) + "/", "", "", false}, 942 {sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true}, 943 {sr("a", nameSize+1), "", "", false}, 944 {sr("/", nameSize+1), sr("/", nameSize-1), "/", true}, 945 {sr("a", prefixSize) + "/" + sr("b", nameSize), 946 sr("a", prefixSize), sr("b", nameSize), true}, 947 {sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false}, 948 {sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true}, 949 } 950 951 for _, v := range vectors { 952 prefix, suffix, ok := splitUSTARPath(v.input) 953 if prefix != v.prefix || suffix != v.suffix || ok != v.ok { 954 t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)", 955 v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok) 956 } 957 } 958 } 959 960 // TestIssue12594 tests that the Writer does not attempt to populate the prefix 961 // field when encoding a header in the GNU format. The prefix field is valid 962 // in USTAR and PAX, but not GNU. 963 func TestIssue12594(t *testing.T) { 964 names := []string{ 965 "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", 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/31/32/33/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/333/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/33/34/35/36/37/38/39/40/file.txt", 969 "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt", 970 "/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend", 971 } 972 973 for i, name := range names { 974 var b bytes.Buffer 975 976 tw := NewWriter(&b) 977 if err := tw.WriteHeader(&Header{ 978 Name: name, 979 Uid: 1 << 25, // Prevent USTAR format 980 }); err != nil { 981 t.Errorf("test %d, unexpected WriteHeader error: %v", i, err) 982 } 983 if err := tw.Close(); err != nil { 984 t.Errorf("test %d, unexpected Close error: %v", i, err) 985 } 986 987 // The prefix field should never appear in the GNU format. 988 var blk block 989 copy(blk[:], b.Bytes()) 990 prefix := string(blk.toUSTAR().prefix()) 991 prefix, _, _ = strings.Cut(prefix, "\x00") // Truncate at the NUL terminator 992 if blk.getFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) { 993 t.Errorf("test %d, found prefix in GNU format: %s", i, prefix) 994 } 995 996 tr := NewReader(&b) 997 hdr, err := tr.Next() 998 if err != nil && err != ErrInsecurePath { 999 t.Errorf("test %d, unexpected Next error: %v", i, err) 1000 } 1001 if hdr.Name != name { 1002 t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name) 1003 } 1004 } 1005 } 1006 1007 func TestWriteLongHeader(t *testing.T) { 1008 for _, test := range []struct { 1009 name string 1010 h *Header 1011 }{{ 1012 name: "name too long", 1013 h: &Header{Name: strings.Repeat("a", maxSpecialFileSize)}, 1014 }, { 1015 name: "linkname too long", 1016 h: &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)}, 1017 }, { 1018 name: "uname too long", 1019 h: &Header{Uname: strings.Repeat("a", maxSpecialFileSize)}, 1020 }, { 1021 name: "gname too long", 1022 h: &Header{Gname: strings.Repeat("a", maxSpecialFileSize)}, 1023 }, { 1024 name: "PAX header too long", 1025 h: &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}}, 1026 }} { 1027 w := NewWriter(io.Discard) 1028 if err := w.WriteHeader(test.h); err != ErrFieldTooLong { 1029 t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err) 1030 } 1031 } 1032 } 1033 1034 // testNonEmptyWriter wraps an io.Writer and ensures that 1035 // Write is never called with an empty buffer. 1036 type testNonEmptyWriter struct{ io.Writer } 1037 1038 func (w testNonEmptyWriter) Write(b []byte) (int, error) { 1039 if len(b) == 0 { 1040 return 0, errors.New("unexpected empty Write call") 1041 } 1042 return w.Writer.Write(b) 1043 } 1044 1045 func TestFileWriter(t *testing.T) { 1046 type ( 1047 testWrite struct { // Write(str) == (wantCnt, wantErr) 1048 str string 1049 wantCnt int 1050 wantErr error 1051 } 1052 testReadFrom struct { // ReadFrom(testFile{ops}) == (wantCnt, wantErr) 1053 ops fileOps 1054 wantCnt int64 1055 wantErr error 1056 } 1057 testRemaining struct { // logicalRemaining() == wantLCnt, physicalRemaining() == wantPCnt 1058 wantLCnt int64 1059 wantPCnt int64 1060 } 1061 testFnc any // testWrite | testReadFrom | testRemaining 1062 ) 1063 1064 type ( 1065 makeReg struct { 1066 size int64 1067 wantStr string 1068 } 1069 makeSparse struct { 1070 makeReg makeReg 1071 sph sparseHoles 1072 size int64 1073 } 1074 fileMaker any // makeReg | makeSparse 1075 ) 1076 1077 vectors := []struct { 1078 maker fileMaker 1079 tests []testFnc 1080 }{{ 1081 maker: makeReg{0, ""}, 1082 tests: []testFnc{ 1083 testRemaining{0, 0}, 1084 testWrite{"", 0, nil}, 1085 testWrite{"a", 0, ErrWriteTooLong}, 1086 testReadFrom{fileOps{""}, 0, nil}, 1087 testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong}, 1088 testRemaining{0, 0}, 1089 }, 1090 }, { 1091 maker: makeReg{1, "a"}, 1092 tests: []testFnc{ 1093 testRemaining{1, 1}, 1094 testWrite{"", 0, nil}, 1095 testWrite{"a", 1, nil}, 1096 testWrite{"bcde", 0, ErrWriteTooLong}, 1097 testWrite{"", 0, nil}, 1098 testReadFrom{fileOps{""}, 0, nil}, 1099 testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong}, 1100 testRemaining{0, 0}, 1101 }, 1102 }, { 1103 maker: makeReg{5, "hello"}, 1104 tests: []testFnc{ 1105 testRemaining{5, 5}, 1106 testWrite{"hello", 5, nil}, 1107 testRemaining{0, 0}, 1108 }, 1109 }, { 1110 maker: makeReg{5, "\x00\x00\x00\x00\x00"}, 1111 tests: []testFnc{ 1112 testRemaining{5, 5}, 1113 testReadFrom{fileOps{"\x00\x00\x00\x00\x00"}, 5, nil}, 1114 testRemaining{0, 0}, 1115 }, 1116 }, { 1117 maker: makeReg{5, "\x00\x00\x00\x00\x00"}, 1118 tests: []testFnc{ 1119 testRemaining{5, 5}, 1120 testReadFrom{fileOps{"\x00\x00\x00\x00\x00extra"}, 5, ErrWriteTooLong}, 1121 testRemaining{0, 0}, 1122 }, 1123 }, { 1124 maker: makeReg{5, "abc\x00\x00"}, 1125 tests: []testFnc{ 1126 testRemaining{5, 5}, 1127 testWrite{"abc", 3, nil}, 1128 testRemaining{2, 2}, 1129 testReadFrom{fileOps{"\x00\x00"}, 2, nil}, 1130 testRemaining{0, 0}, 1131 }, 1132 }, { 1133 maker: makeReg{5, "\x00\x00abc"}, 1134 tests: []testFnc{ 1135 testRemaining{5, 5}, 1136 testWrite{"\x00\x00", 2, nil}, 1137 testRemaining{3, 3}, 1138 testWrite{"abc", 3, nil}, 1139 testReadFrom{fileOps{"z"}, 0, ErrWriteTooLong}, 1140 testWrite{"z", 0, ErrWriteTooLong}, 1141 testRemaining{0, 0}, 1142 }, 1143 }, { 1144 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1145 tests: []testFnc{ 1146 testRemaining{8, 5}, 1147 testWrite{"ab\x00\x00\x00cde", 8, nil}, 1148 testWrite{"a", 0, ErrWriteTooLong}, 1149 testRemaining{0, 0}, 1150 }, 1151 }, { 1152 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1153 tests: []testFnc{ 1154 testWrite{"ab\x00\x00\x00cdez", 8, ErrWriteTooLong}, 1155 testRemaining{0, 0}, 1156 }, 1157 }, { 1158 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1159 tests: []testFnc{ 1160 testWrite{"ab\x00", 3, nil}, 1161 testRemaining{5, 3}, 1162 testWrite{"\x00\x00cde", 5, nil}, 1163 testWrite{"a", 0, ErrWriteTooLong}, 1164 testRemaining{0, 0}, 1165 }, 1166 }, { 1167 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1168 tests: []testFnc{ 1169 testWrite{"ab", 2, nil}, 1170 testRemaining{6, 3}, 1171 testReadFrom{fileOps{int64(3), "cde"}, 6, nil}, 1172 testRemaining{0, 0}, 1173 }, 1174 }, { 1175 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1176 tests: []testFnc{ 1177 testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, nil}, 1178 testRemaining{0, 0}, 1179 }, 1180 }, { 1181 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8}, 1182 tests: []testFnc{ 1183 testReadFrom{fileOps{"ab", int64(3), "cdeX"}, 8, ErrWriteTooLong}, 1184 testRemaining{0, 0}, 1185 }, 1186 }, { 1187 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8}, 1188 tests: []testFnc{ 1189 testReadFrom{fileOps{"ab", int64(3), "cd"}, 7, io.ErrUnexpectedEOF}, 1190 testRemaining{1, 0}, 1191 }, 1192 }, { 1193 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8}, 1194 tests: []testFnc{ 1195 testReadFrom{fileOps{"ab", int64(3), "cde"}, 7, errMissData}, 1196 testRemaining{1, 0}, 1197 }, 1198 }, { 1199 maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8}, 1200 tests: []testFnc{ 1201 testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, errUnrefData}, 1202 testRemaining{0, 1}, 1203 }, 1204 }, { 1205 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8}, 1206 tests: []testFnc{ 1207 testWrite{"ab", 2, nil}, 1208 testRemaining{6, 2}, 1209 testWrite{"\x00\x00\x00", 3, nil}, 1210 testRemaining{3, 2}, 1211 testWrite{"cde", 2, errMissData}, 1212 testRemaining{1, 0}, 1213 }, 1214 }, { 1215 maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8}, 1216 tests: []testFnc{ 1217 testWrite{"ab", 2, nil}, 1218 testRemaining{6, 4}, 1219 testWrite{"\x00\x00\x00", 3, nil}, 1220 testRemaining{3, 4}, 1221 testWrite{"cde", 3, errUnrefData}, 1222 testRemaining{0, 1}, 1223 }, 1224 }, { 1225 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1226 tests: []testFnc{ 1227 testRemaining{7, 3}, 1228 testWrite{"\x00\x00abc\x00\x00", 7, nil}, 1229 testRemaining{0, 0}, 1230 }, 1231 }, { 1232 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1233 tests: []testFnc{ 1234 testRemaining{7, 3}, 1235 testReadFrom{fileOps{int64(2), "abc", int64(1), "\x00"}, 7, nil}, 1236 testRemaining{0, 0}, 1237 }, 1238 }, { 1239 maker: makeSparse{makeReg{3, ""}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1240 tests: []testFnc{ 1241 testWrite{"abcdefg", 0, errWriteHole}, 1242 }, 1243 }, { 1244 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1245 tests: []testFnc{ 1246 testWrite{"\x00\x00abcde", 5, errWriteHole}, 1247 }, 1248 }, { 1249 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1250 tests: []testFnc{ 1251 testWrite{"\x00\x00abc\x00\x00z", 7, ErrWriteTooLong}, 1252 testRemaining{0, 0}, 1253 }, 1254 }, { 1255 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1256 tests: []testFnc{ 1257 testWrite{"\x00\x00", 2, nil}, 1258 testRemaining{5, 3}, 1259 testWrite{"abc", 3, nil}, 1260 testRemaining{2, 0}, 1261 testWrite{"\x00\x00", 2, nil}, 1262 testRemaining{0, 0}, 1263 }, 1264 }, { 1265 maker: makeSparse{makeReg{2, "ab"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1266 tests: []testFnc{ 1267 testWrite{"\x00\x00", 2, nil}, 1268 testWrite{"abc", 2, errMissData}, 1269 testWrite{"\x00\x00", 0, errMissData}, 1270 }, 1271 }, { 1272 maker: makeSparse{makeReg{4, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7}, 1273 tests: []testFnc{ 1274 testWrite{"\x00\x00", 2, nil}, 1275 testWrite{"abc", 3, nil}, 1276 testWrite{"\x00\x00", 2, errUnrefData}, 1277 }, 1278 }} 1279 1280 for i, v := range vectors { 1281 var wantStr string 1282 bb := new(strings.Builder) 1283 w := testNonEmptyWriter{bb} 1284 var fw fileWriter 1285 switch maker := v.maker.(type) { 1286 case makeReg: 1287 fw = ®FileWriter{w, maker.size} 1288 wantStr = maker.wantStr 1289 case makeSparse: 1290 if !validateSparseEntries(maker.sph, maker.size) { 1291 t.Fatalf("invalid sparse map: %v", maker.sph) 1292 } 1293 spd := invertSparseEntries(maker.sph, maker.size) 1294 fw = ®FileWriter{w, maker.makeReg.size} 1295 fw = &sparseFileWriter{fw, spd, 0} 1296 wantStr = maker.makeReg.wantStr 1297 default: 1298 t.Fatalf("test %d, unknown make operation: %T", i, maker) 1299 } 1300 1301 for j, tf := range v.tests { 1302 switch tf := tf.(type) { 1303 case testWrite: 1304 got, err := fw.Write([]byte(tf.str)) 1305 if got != tf.wantCnt || err != tf.wantErr { 1306 t.Errorf("test %d.%d, Write(%s):\ngot (%d, %v)\nwant (%d, %v)", i, j, tf.str, got, err, tf.wantCnt, tf.wantErr) 1307 } 1308 case testReadFrom: 1309 f := &testFile{ops: tf.ops} 1310 got, err := fw.ReadFrom(f) 1311 if _, ok := err.(testError); ok { 1312 t.Errorf("test %d.%d, ReadFrom(): %v", i, j, err) 1313 } else if got != tf.wantCnt || err != tf.wantErr { 1314 t.Errorf("test %d.%d, ReadFrom() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr) 1315 } 1316 if len(f.ops) > 0 { 1317 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops)) 1318 } 1319 case testRemaining: 1320 if got := fw.logicalRemaining(); got != tf.wantLCnt { 1321 t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt) 1322 } 1323 if got := fw.physicalRemaining(); got != tf.wantPCnt { 1324 t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt) 1325 } 1326 default: 1327 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf) 1328 } 1329 } 1330 1331 if got := bb.String(); got != wantStr { 1332 t.Fatalf("test %d, String() = %q, want %q", i, got, wantStr) 1333 } 1334 } 1335 }