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 "fmt" 10 "io" 11 "io/ioutil" 12 "math" 13 "os" 14 "reflect" 15 "sort" 16 "strings" 17 "testing" 18 "testing/iotest" 19 "time" 20) 21 22type writerTestEntry struct { 23 header *Header 24 contents string 25} 26 27type writerTest struct { 28 file string // filename of expected output 29 entries []*writerTestEntry 30} 31 32var writerTests = []*writerTest{ 33 // The writer test file was produced with this command: 34 // tar (GNU tar) 1.26 35 // ln -s small.txt link.txt 36 // tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt 37 { 38 file: "testdata/writer.tar", 39 entries: []*writerTestEntry{ 40 { 41 header: &Header{ 42 Name: "small.txt", 43 Mode: 0640, 44 Uid: 73025, 45 Gid: 5000, 46 Size: 5, 47 ModTime: time.Unix(1246508266, 0), 48 Typeflag: '0', 49 Uname: "dsymonds", 50 Gname: "eng", 51 }, 52 contents: "Kilts", 53 }, 54 { 55 header: &Header{ 56 Name: "small2.txt", 57 Mode: 0640, 58 Uid: 73025, 59 Gid: 5000, 60 Size: 11, 61 ModTime: time.Unix(1245217492, 0), 62 Typeflag: '0', 63 Uname: "dsymonds", 64 Gname: "eng", 65 }, 66 contents: "Google.com\n", 67 }, 68 { 69 header: &Header{ 70 Name: "link.txt", 71 Mode: 0777, 72 Uid: 1000, 73 Gid: 1000, 74 Size: 0, 75 ModTime: time.Unix(1314603082, 0), 76 Typeflag: '2', 77 Linkname: "small.txt", 78 Uname: "strings", 79 Gname: "strings", 80 }, 81 // no contents 82 }, 83 }, 84 }, 85 // The truncated test file was produced using these commands: 86 // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt 87 // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar 88 { 89 file: "testdata/writer-big.tar", 90 entries: []*writerTestEntry{ 91 { 92 header: &Header{ 93 Name: "tmp/16gig.txt", 94 Mode: 0640, 95 Uid: 73025, 96 Gid: 5000, 97 Size: 16 << 30, 98 ModTime: time.Unix(1254699560, 0), 99 Typeflag: '0', 100 Uname: "dsymonds", 101 Gname: "eng", 102 }, 103 // fake contents 104 contents: strings.Repeat("\x00", 4<<10), 105 }, 106 }, 107 }, 108 // The truncated test file was produced using these commands: 109 // dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt 110 // tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar 111 { 112 file: "testdata/writer-big-long.tar", 113 entries: []*writerTestEntry{ 114 { 115 header: &Header{ 116 Name: strings.Repeat("longname/", 15) + "16gig.txt", 117 Mode: 0644, 118 Uid: 1000, 119 Gid: 1000, 120 Size: 16 << 30, 121 ModTime: time.Unix(1399583047, 0), 122 Typeflag: '0', 123 Uname: "guillaume", 124 Gname: "guillaume", 125 }, 126 // fake contents 127 contents: strings.Repeat("\x00", 4<<10), 128 }, 129 }, 130 }, 131 // This file was produced using gnu tar 1.17 132 // gnutar -b 4 --format=ustar (longname/)*15 + file.txt 133 { 134 file: "testdata/ustar.tar", 135 entries: []*writerTestEntry{ 136 { 137 header: &Header{ 138 Name: strings.Repeat("longname/", 15) + "file.txt", 139 Mode: 0644, 140 Uid: 0765, 141 Gid: 024, 142 Size: 06, 143 ModTime: time.Unix(1360135598, 0), 144 Typeflag: '0', 145 Uname: "shane", 146 Gname: "staff", 147 }, 148 contents: "hello\n", 149 }, 150 }, 151 }, 152 // This file was produced using gnu tar 1.26 153 // echo "Slartibartfast" > file.txt 154 // ln file.txt hard.txt 155 // tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt 156 { 157 file: "testdata/hardlink.tar", 158 entries: []*writerTestEntry{ 159 { 160 header: &Header{ 161 Name: "file.txt", 162 Mode: 0644, 163 Uid: 1000, 164 Gid: 100, 165 Size: 15, 166 ModTime: time.Unix(1425484303, 0), 167 Typeflag: '0', 168 Uname: "vbatts", 169 Gname: "users", 170 }, 171 contents: "Slartibartfast\n", 172 }, 173 { 174 header: &Header{ 175 Name: "hard.txt", 176 Mode: 0644, 177 Uid: 1000, 178 Gid: 100, 179 Size: 0, 180 ModTime: time.Unix(1425484303, 0), 181 Typeflag: '1', 182 Linkname: "file.txt", 183 Uname: "vbatts", 184 Gname: "users", 185 }, 186 // no contents 187 }, 188 }, 189 }, 190} 191 192// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection. 193func bytestr(offset int, b []byte) string { 194 const rowLen = 32 195 s := fmt.Sprintf("%04x ", offset) 196 for _, ch := range b { 197 switch { 198 case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z': 199 s += fmt.Sprintf(" %c", ch) 200 default: 201 s += fmt.Sprintf(" %02x", ch) 202 } 203 } 204 return s 205} 206 207// Render a pseudo-diff between two blocks of bytes. 208func bytediff(a []byte, b []byte) string { 209 const rowLen = 32 210 s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b)) 211 for offset := 0; len(a)+len(b) > 0; offset += rowLen { 212 na, nb := rowLen, rowLen 213 if na > len(a) { 214 na = len(a) 215 } 216 if nb > len(b) { 217 nb = len(b) 218 } 219 sa := bytestr(offset, a[0:na]) 220 sb := bytestr(offset, b[0:nb]) 221 if sa != sb { 222 s += fmt.Sprintf("-%v\n+%v\n", sa, sb) 223 } 224 a = a[na:] 225 b = b[nb:] 226 } 227 return s 228} 229 230func TestWriter(t *testing.T) { 231testLoop: 232 for i, test := range writerTests { 233 expected, err := ioutil.ReadFile(test.file) 234 if err != nil { 235 t.Errorf("test %d: Unexpected error: %v", i, err) 236 continue 237 } 238 239 buf := new(bytes.Buffer) 240 tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB 241 big := false 242 for j, entry := range test.entries { 243 big = big || entry.header.Size > 1<<10 244 if err := tw.WriteHeader(entry.header); err != nil { 245 t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err) 246 continue testLoop 247 } 248 if _, err := io.WriteString(tw, entry.contents); err != nil { 249 t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err) 250 continue testLoop 251 } 252 } 253 // Only interested in Close failures for the small tests. 254 if err := tw.Close(); err != nil && !big { 255 t.Errorf("test %d: Failed closing archive: %v", i, err) 256 continue testLoop 257 } 258 259 actual := buf.Bytes() 260 if !bytes.Equal(expected, actual) { 261 t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v", 262 i, bytediff(expected, actual)) 263 } 264 if testing.Short() { // The second test is expensive. 265 break 266 } 267 } 268} 269 270func TestPax(t *testing.T) { 271 // Create an archive with a large name 272 fileinfo, err := os.Stat("testdata/small.txt") 273 if err != nil { 274 t.Fatal(err) 275 } 276 hdr, err := FileInfoHeader(fileinfo, "") 277 if err != nil { 278 t.Fatalf("os.Stat: %v", err) 279 } 280 // Force a PAX long name to be written 281 longName := strings.Repeat("ab", 100) 282 contents := strings.Repeat(" ", int(hdr.Size)) 283 hdr.Name = longName 284 var buf bytes.Buffer 285 writer := NewWriter(&buf) 286 if err := writer.WriteHeader(hdr); err != nil { 287 t.Fatal(err) 288 } 289 if _, err = writer.Write([]byte(contents)); err != nil { 290 t.Fatal(err) 291 } 292 if err := writer.Close(); err != nil { 293 t.Fatal(err) 294 } 295 // Simple test to make sure PAX extensions are in effect 296 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 297 t.Fatal("Expected at least one PAX header to be written.") 298 } 299 // Test that we can get a long name back out of the archive. 300 reader := NewReader(&buf) 301 hdr, err = reader.Next() 302 if err != nil { 303 t.Fatal(err) 304 } 305 if hdr.Name != longName { 306 t.Fatal("Couldn't recover long file name") 307 } 308} 309 310func TestPaxSymlink(t *testing.T) { 311 // Create an archive with a large linkname 312 fileinfo, err := os.Stat("testdata/small.txt") 313 if err != nil { 314 t.Fatal(err) 315 } 316 hdr, err := FileInfoHeader(fileinfo, "") 317 hdr.Typeflag = TypeSymlink 318 if err != nil { 319 t.Fatalf("os.Stat:1 %v", err) 320 } 321 // Force a PAX long linkname to be written 322 longLinkname := strings.Repeat("1234567890/1234567890", 10) 323 hdr.Linkname = longLinkname 324 325 hdr.Size = 0 326 var buf bytes.Buffer 327 writer := NewWriter(&buf) 328 if err := writer.WriteHeader(hdr); err != nil { 329 t.Fatal(err) 330 } 331 if err := writer.Close(); err != nil { 332 t.Fatal(err) 333 } 334 // Simple test to make sure PAX extensions are in effect 335 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 336 t.Fatal("Expected at least one PAX header to be written.") 337 } 338 // Test that we can get a long name back out of the archive. 339 reader := NewReader(&buf) 340 hdr, err = reader.Next() 341 if err != nil { 342 t.Fatal(err) 343 } 344 if hdr.Linkname != longLinkname { 345 t.Fatal("Couldn't recover long link name") 346 } 347} 348 349func TestPaxNonAscii(t *testing.T) { 350 // Create an archive with non ascii. These should trigger a pax header 351 // because pax headers have a defined utf-8 encoding. 352 fileinfo, err := os.Stat("testdata/small.txt") 353 if err != nil { 354 t.Fatal(err) 355 } 356 357 hdr, err := FileInfoHeader(fileinfo, "") 358 if err != nil { 359 t.Fatalf("os.Stat:1 %v", err) 360 } 361 362 // some sample data 363 chineseFilename := "文件名" 364 chineseGroupname := "組" 365 chineseUsername := "用戶名" 366 367 hdr.Name = chineseFilename 368 hdr.Gname = chineseGroupname 369 hdr.Uname = chineseUsername 370 371 contents := strings.Repeat(" ", int(hdr.Size)) 372 373 var buf bytes.Buffer 374 writer := NewWriter(&buf) 375 if err := writer.WriteHeader(hdr); err != nil { 376 t.Fatal(err) 377 } 378 if _, err = writer.Write([]byte(contents)); err != nil { 379 t.Fatal(err) 380 } 381 if err := writer.Close(); err != nil { 382 t.Fatal(err) 383 } 384 // Simple test to make sure PAX extensions are in effect 385 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 386 t.Fatal("Expected at least one PAX header to be written.") 387 } 388 // Test that we can get a long name back out of the archive. 389 reader := NewReader(&buf) 390 hdr, err = reader.Next() 391 if err != nil { 392 t.Fatal(err) 393 } 394 if hdr.Name != chineseFilename { 395 t.Fatal("Couldn't recover unicode name") 396 } 397 if hdr.Gname != chineseGroupname { 398 t.Fatal("Couldn't recover unicode group") 399 } 400 if hdr.Uname != chineseUsername { 401 t.Fatal("Couldn't recover unicode user") 402 } 403} 404 405func TestPaxXattrs(t *testing.T) { 406 xattrs := map[string]string{ 407 "user.key": "value", 408 } 409 410 // Create an archive with an xattr 411 fileinfo, err := os.Stat("testdata/small.txt") 412 if err != nil { 413 t.Fatal(err) 414 } 415 hdr, err := FileInfoHeader(fileinfo, "") 416 if err != nil { 417 t.Fatalf("os.Stat: %v", err) 418 } 419 contents := "Kilts" 420 hdr.Xattrs = xattrs 421 var buf bytes.Buffer 422 writer := NewWriter(&buf) 423 if err := writer.WriteHeader(hdr); err != nil { 424 t.Fatal(err) 425 } 426 if _, err = writer.Write([]byte(contents)); err != nil { 427 t.Fatal(err) 428 } 429 if err := writer.Close(); err != nil { 430 t.Fatal(err) 431 } 432 // Test that we can get the xattrs back out of the archive. 433 reader := NewReader(&buf) 434 hdr, err = reader.Next() 435 if err != nil { 436 t.Fatal(err) 437 } 438 if !reflect.DeepEqual(hdr.Xattrs, xattrs) { 439 t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", 440 hdr.Xattrs, xattrs) 441 } 442} 443 444func TestPaxHeadersSorted(t *testing.T) { 445 fileinfo, err := os.Stat("testdata/small.txt") 446 if err != nil { 447 t.Fatal(err) 448 } 449 hdr, err := FileInfoHeader(fileinfo, "") 450 if err != nil { 451 t.Fatalf("os.Stat: %v", err) 452 } 453 contents := strings.Repeat(" ", int(hdr.Size)) 454 455 hdr.Xattrs = map[string]string{ 456 "foo": "foo", 457 "bar": "bar", 458 "baz": "baz", 459 "qux": "qux", 460 } 461 462 var buf bytes.Buffer 463 writer := NewWriter(&buf) 464 if err := writer.WriteHeader(hdr); err != nil { 465 t.Fatal(err) 466 } 467 if _, err = writer.Write([]byte(contents)); err != nil { 468 t.Fatal(err) 469 } 470 if err := writer.Close(); err != nil { 471 t.Fatal(err) 472 } 473 // Simple test to make sure PAX extensions are in effect 474 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 475 t.Fatal("Expected at least one PAX header to be written.") 476 } 477 478 // xattr bar should always appear before others 479 indices := []int{ 480 bytes.Index(buf.Bytes(), []byte("bar=bar")), 481 bytes.Index(buf.Bytes(), []byte("baz=baz")), 482 bytes.Index(buf.Bytes(), []byte("foo=foo")), 483 bytes.Index(buf.Bytes(), []byte("qux=qux")), 484 } 485 if !sort.IntsAreSorted(indices) { 486 t.Fatal("PAX headers are not sorted") 487 } 488} 489 490func TestUSTARLongName(t *testing.T) { 491 // Create an archive with a path that failed to split with USTAR extension in previous versions. 492 fileinfo, err := os.Stat("testdata/small.txt") 493 if err != nil { 494 t.Fatal(err) 495 } 496 hdr, err := FileInfoHeader(fileinfo, "") 497 hdr.Typeflag = TypeDir 498 if err != nil { 499 t.Fatalf("os.Stat:1 %v", err) 500 } 501 // Force a PAX long name to be written. The name was taken from a practical example 502 // that fails and replaced ever char through numbers to anonymize the sample. 503 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/" 504 hdr.Name = longName 505 506 hdr.Size = 0 507 var buf bytes.Buffer 508 writer := NewWriter(&buf) 509 if err := writer.WriteHeader(hdr); err != nil { 510 t.Fatal(err) 511 } 512 if err := writer.Close(); err != nil { 513 t.Fatal(err) 514 } 515 // Test that we can get a long name back out of the archive. 516 reader := NewReader(&buf) 517 hdr, err = reader.Next() 518 if err != nil { 519 t.Fatal(err) 520 } 521 if hdr.Name != longName { 522 t.Fatal("Couldn't recover long name") 523 } 524} 525 526func TestValidTypeflagWithPAXHeader(t *testing.T) { 527 var buffer bytes.Buffer 528 tw := NewWriter(&buffer) 529 530 fileName := strings.Repeat("ab", 100) 531 532 hdr := &Header{ 533 Name: fileName, 534 Size: 4, 535 Typeflag: 0, 536 } 537 if err := tw.WriteHeader(hdr); err != nil { 538 t.Fatalf("Failed to write header: %s", err) 539 } 540 if _, err := tw.Write([]byte("fooo")); err != nil { 541 t.Fatalf("Failed to write the file's data: %s", err) 542 } 543 tw.Close() 544 545 tr := NewReader(&buffer) 546 547 for { 548 header, err := tr.Next() 549 if err == io.EOF { 550 break 551 } 552 if err != nil { 553 t.Fatalf("Failed to read header: %s", err) 554 } 555 if header.Typeflag != 0 { 556 t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag) 557 } 558 } 559} 560 561func TestWriteAfterClose(t *testing.T) { 562 var buffer bytes.Buffer 563 tw := NewWriter(&buffer) 564 565 hdr := &Header{ 566 Name: "small.txt", 567 Size: 5, 568 } 569 if err := tw.WriteHeader(hdr); err != nil { 570 t.Fatalf("Failed to write header: %s", err) 571 } 572 tw.Close() 573 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose { 574 t.Fatalf("Write: got %v; want ErrWriteAfterClose", err) 575 } 576} 577 578func TestSplitUSTARPath(t *testing.T) { 579 var sr = strings.Repeat 580 581 var vectors = []struct { 582 input string // Input path 583 prefix string // Expected output prefix 584 suffix string // Expected output suffix 585 ok bool // Split success? 586 }{ 587 {"", "", "", false}, 588 {"abc", "", "", false}, 589 {"用戶名", "", "", false}, 590 {sr("a", fileNameSize), "", "", false}, 591 {sr("a", fileNameSize) + "/", "", "", false}, 592 {sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true}, 593 {sr("a", fileNamePrefixSize) + "/", "", "", false}, 594 {sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true}, 595 {sr("a", fileNameSize+1), "", "", false}, 596 {sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true}, 597 {sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize), 598 sr("a", fileNamePrefixSize), sr("b", fileNameSize), true}, 599 {sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false}, 600 {sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true}, 601 } 602 603 for _, v := range vectors { 604 prefix, suffix, ok := splitUSTARPath(v.input) 605 if prefix != v.prefix || suffix != v.suffix || ok != v.ok { 606 t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)", 607 v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok) 608 } 609 } 610} 611 612func TestFormatPAXRecord(t *testing.T) { 613 var medName = strings.Repeat("CD", 50) 614 var longName = strings.Repeat("AB", 100) 615 616 var vectors = []struct { 617 inputKey string 618 inputVal string 619 output string 620 }{ 621 {"k", "v", "6 k=v\n"}, 622 {"path", "/etc/hosts", "19 path=/etc/hosts\n"}, 623 {"path", longName, "210 path=" + longName + "\n"}, 624 {"path", medName, "110 path=" + medName + "\n"}, 625 {"foo", "ba", "9 foo=ba\n"}, 626 {"foo", "bar", "11 foo=bar\n"}, 627 {"foo", "b=\nar=\n==\x00", "18 foo=b=\nar=\n==\x00\n"}, 628 {"foo", "hello9 foo=ba\nworld", "27 foo=hello9 foo=ba\nworld\n"}, 629 {"☺☻☹", "日a本b語ç", "27 ☺☻☹=日a本b語ç\n"}, 630 {"\x00hello", "\x00world", "17 \x00hello=\x00world\n"}, 631 } 632 633 for _, v := range vectors { 634 output := formatPAXRecord(v.inputKey, v.inputVal) 635 if output != v.output { 636 t.Errorf("formatPAXRecord(%q, %q): got %q, want %q", 637 v.inputKey, v.inputVal, output, v.output) 638 } 639 } 640} 641 642func TestFitsInBase256(t *testing.T) { 643 var vectors = []struct { 644 input int64 645 width int 646 ok bool 647 }{ 648 {+1, 8, true}, 649 {0, 8, true}, 650 {-1, 8, true}, 651 {1 << 56, 8, false}, 652 {(1 << 56) - 1, 8, true}, 653 {-1 << 56, 8, true}, 654 {(-1 << 56) - 1, 8, false}, 655 {121654, 8, true}, 656 {-9849849, 8, true}, 657 {math.MaxInt64, 9, true}, 658 {0, 9, true}, 659 {math.MinInt64, 9, true}, 660 {math.MaxInt64, 12, true}, 661 {0, 12, true}, 662 {math.MinInt64, 12, true}, 663 } 664 665 for _, v := range vectors { 666 ok := fitsInBase256(v.width, v.input) 667 if ok != v.ok { 668 t.Errorf("checkNumeric(%d, %d): got %v, want %v", v.input, v.width, ok, v.ok) 669 } 670 } 671} 672 673func TestFormatNumeric(t *testing.T) { 674 var vectors = []struct { 675 input int64 676 output string 677 ok bool 678 }{ 679 // Test base-256 (binary) encoded values. 680 {-1, "\xff", true}, 681 {-1, "\xff\xff", true}, 682 {-1, "\xff\xff\xff", true}, 683 {(1 << 0), "0", false}, 684 {(1 << 8) - 1, "\x80\xff", true}, 685 {(1 << 8), "0\x00", false}, 686 {(1 << 16) - 1, "\x80\xff\xff", true}, 687 {(1 << 16), "00\x00", false}, 688 {-1 * (1 << 0), "\xff", true}, 689 {-1*(1<<0) - 1, "0", false}, 690 {-1 * (1 << 8), "\xff\x00", true}, 691 {-1*(1<<8) - 1, "0\x00", false}, 692 {-1 * (1 << 16), "\xff\x00\x00", true}, 693 {-1*(1<<16) - 1, "00\x00", false}, 694 {537795476381659745, "0000000\x00", false}, 695 {537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true}, 696 {-615126028225187231, "0000000\x00", false}, 697 {-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true}, 698 {math.MaxInt64, "0000000\x00", false}, 699 {math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true}, 700 {math.MinInt64, "0000000\x00", false}, 701 {math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true}, 702 {math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true}, 703 {math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true}, 704 } 705 706 for _, v := range vectors { 707 var f formatter 708 output := make([]byte, len(v.output)) 709 f.formatNumeric(output, v.input) 710 ok := (f.err == nil) 711 if ok != v.ok { 712 if v.ok { 713 t.Errorf("formatNumeric(%d): got formatting failure, want success", v.input) 714 } else { 715 t.Errorf("formatNumeric(%d): got formatting success, want failure", v.input) 716 } 717 } 718 if string(output) != v.output { 719 t.Errorf("formatNumeric(%d): got %q, want %q", v.input, output, v.output) 720 } 721 } 722} 723