github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/src/archive/tar/tar_test.go (about) 1 // Copyright 2012 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 "internal/testenv" 10 "io" 11 "io/ioutil" 12 "math" 13 "os" 14 "path" 15 "path/filepath" 16 "reflect" 17 "strings" 18 "testing" 19 "time" 20 ) 21 22 func equalSparseEntries(x, y []SparseEntry) bool { 23 return (len(x) == 0 && len(y) == 0) || reflect.DeepEqual(x, y) 24 } 25 26 func TestSparseEntries(t *testing.T) { 27 vectors := []struct { 28 in []SparseEntry 29 size int64 30 31 wantValid bool // Result of validateSparseEntries 32 wantAligned []SparseEntry // Result of alignSparseEntries 33 wantInverted []SparseEntry // Result of invertSparseEntries 34 }{{ 35 in: []SparseEntry{}, size: 0, 36 wantValid: true, 37 wantInverted: []SparseEntry{{0, 0}}, 38 }, { 39 in: []SparseEntry{}, size: 5000, 40 wantValid: true, 41 wantInverted: []SparseEntry{{0, 5000}}, 42 }, { 43 in: []SparseEntry{{0, 5000}}, size: 5000, 44 wantValid: true, 45 wantAligned: []SparseEntry{{0, 5000}}, 46 wantInverted: []SparseEntry{{5000, 0}}, 47 }, { 48 in: []SparseEntry{{1000, 4000}}, size: 5000, 49 wantValid: true, 50 wantAligned: []SparseEntry{{1024, 3976}}, 51 wantInverted: []SparseEntry{{0, 1000}, {5000, 0}}, 52 }, { 53 in: []SparseEntry{{0, 3000}}, size: 5000, 54 wantValid: true, 55 wantAligned: []SparseEntry{{0, 2560}}, 56 wantInverted: []SparseEntry{{3000, 2000}}, 57 }, { 58 in: []SparseEntry{{3000, 2000}}, size: 5000, 59 wantValid: true, 60 wantAligned: []SparseEntry{{3072, 1928}}, 61 wantInverted: []SparseEntry{{0, 3000}, {5000, 0}}, 62 }, { 63 in: []SparseEntry{{2000, 2000}}, size: 5000, 64 wantValid: true, 65 wantAligned: []SparseEntry{{2048, 1536}}, 66 wantInverted: []SparseEntry{{0, 2000}, {4000, 1000}}, 67 }, { 68 in: []SparseEntry{{0, 2000}, {8000, 2000}}, size: 10000, 69 wantValid: true, 70 wantAligned: []SparseEntry{{0, 1536}, {8192, 1808}}, 71 wantInverted: []SparseEntry{{2000, 6000}, {10000, 0}}, 72 }, { 73 in: []SparseEntry{{0, 2000}, {2000, 2000}, {4000, 0}, {4000, 3000}, {7000, 1000}, {8000, 0}, {8000, 2000}}, size: 10000, 74 wantValid: true, 75 wantAligned: []SparseEntry{{0, 1536}, {2048, 1536}, {4096, 2560}, {7168, 512}, {8192, 1808}}, 76 wantInverted: []SparseEntry{{10000, 0}}, 77 }, { 78 in: []SparseEntry{{0, 0}, {1000, 0}, {2000, 0}, {3000, 0}, {4000, 0}, {5000, 0}}, size: 5000, 79 wantValid: true, 80 wantInverted: []SparseEntry{{0, 5000}}, 81 }, { 82 in: []SparseEntry{{1, 0}}, size: 0, 83 wantValid: false, 84 }, { 85 in: []SparseEntry{{-1, 0}}, size: 100, 86 wantValid: false, 87 }, { 88 in: []SparseEntry{{0, -1}}, size: 100, 89 wantValid: false, 90 }, { 91 in: []SparseEntry{{0, 0}}, size: -100, 92 wantValid: false, 93 }, { 94 in: []SparseEntry{{math.MaxInt64, 3}, {6, -5}}, size: 35, 95 wantValid: false, 96 }, { 97 in: []SparseEntry{{1, 3}, {6, -5}}, size: 35, 98 wantValid: false, 99 }, { 100 in: []SparseEntry{{math.MaxInt64, math.MaxInt64}}, size: math.MaxInt64, 101 wantValid: false, 102 }, { 103 in: []SparseEntry{{3, 3}}, size: 5, 104 wantValid: false, 105 }, { 106 in: []SparseEntry{{2, 0}, {1, 0}, {0, 0}}, size: 3, 107 wantValid: false, 108 }, { 109 in: []SparseEntry{{1, 3}, {2, 2}}, size: 10, 110 wantValid: false, 111 }} 112 113 for i, v := range vectors { 114 gotValid := validateSparseEntries(v.in, v.size) 115 if gotValid != v.wantValid { 116 t.Errorf("test %d, validateSparseEntries() = %v, want %v", i, gotValid, v.wantValid) 117 } 118 if !v.wantValid { 119 continue 120 } 121 gotAligned := alignSparseEntries(append([]SparseEntry{}, v.in...), v.size) 122 if !equalSparseEntries(gotAligned, v.wantAligned) { 123 t.Errorf("test %d, alignSparseEntries():\ngot %v\nwant %v", i, gotAligned, v.wantAligned) 124 } 125 gotInverted := invertSparseEntries(append([]SparseEntry{}, v.in...), v.size) 126 if !equalSparseEntries(gotInverted, v.wantInverted) { 127 t.Errorf("test %d, inverseSparseEntries():\ngot %v\nwant %v", i, gotInverted, v.wantInverted) 128 } 129 } 130 } 131 132 func TestFileInfoHeader(t *testing.T) { 133 fi, err := os.Stat("testdata/small.txt") 134 if err != nil { 135 t.Fatal(err) 136 } 137 h, err := FileInfoHeader(fi, "") 138 if err != nil { 139 t.Fatalf("FileInfoHeader: %v", err) 140 } 141 if g, e := h.Name, "small.txt"; g != e { 142 t.Errorf("Name = %q; want %q", g, e) 143 } 144 if g, e := h.Mode, int64(fi.Mode().Perm()); g != e { 145 t.Errorf("Mode = %#o; want %#o", g, e) 146 } 147 if g, e := h.Size, int64(5); g != e { 148 t.Errorf("Size = %v; want %v", g, e) 149 } 150 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { 151 t.Errorf("ModTime = %v; want %v", g, e) 152 } 153 // FileInfoHeader should error when passing nil FileInfo 154 if _, err := FileInfoHeader(nil, ""); err == nil { 155 t.Fatalf("Expected error when passing nil to FileInfoHeader") 156 } 157 } 158 159 func TestFileInfoHeaderDir(t *testing.T) { 160 fi, err := os.Stat("testdata") 161 if err != nil { 162 t.Fatal(err) 163 } 164 h, err := FileInfoHeader(fi, "") 165 if err != nil { 166 t.Fatalf("FileInfoHeader: %v", err) 167 } 168 if g, e := h.Name, "testdata/"; g != e { 169 t.Errorf("Name = %q; want %q", g, e) 170 } 171 // Ignoring c_ISGID for golang.org/issue/4867 172 if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e { 173 t.Errorf("Mode = %#o; want %#o", g, e) 174 } 175 if g, e := h.Size, int64(0); g != e { 176 t.Errorf("Size = %v; want %v", g, e) 177 } 178 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { 179 t.Errorf("ModTime = %v; want %v", g, e) 180 } 181 } 182 183 func TestFileInfoHeaderSymlink(t *testing.T) { 184 testenv.MustHaveSymlink(t) 185 186 tmpdir, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink") 187 if err != nil { 188 t.Fatal(err) 189 } 190 defer os.RemoveAll(tmpdir) 191 192 link := filepath.Join(tmpdir, "link") 193 target := tmpdir 194 err = os.Symlink(target, link) 195 if err != nil { 196 t.Fatal(err) 197 } 198 fi, err := os.Lstat(link) 199 if err != nil { 200 t.Fatal(err) 201 } 202 203 h, err := FileInfoHeader(fi, target) 204 if err != nil { 205 t.Fatal(err) 206 } 207 if g, e := h.Name, fi.Name(); g != e { 208 t.Errorf("Name = %q; want %q", g, e) 209 } 210 if g, e := h.Linkname, target; g != e { 211 t.Errorf("Linkname = %q; want %q", g, e) 212 } 213 if g, e := h.Typeflag, byte(TypeSymlink); g != e { 214 t.Errorf("Typeflag = %v; want %v", g, e) 215 } 216 } 217 218 func TestRoundTrip(t *testing.T) { 219 data := []byte("some file contents") 220 221 var b bytes.Buffer 222 tw := NewWriter(&b) 223 hdr := &Header{ 224 Name: "file.txt", 225 Uid: 1 << 21, // Too big for 8 octal digits 226 Size: int64(len(data)), 227 ModTime: time.Now().Round(time.Second), 228 PAXRecords: map[string]string{"uid": "2097152"}, 229 Format: FormatPAX, 230 } 231 if err := tw.WriteHeader(hdr); err != nil { 232 t.Fatalf("tw.WriteHeader: %v", err) 233 } 234 if _, err := tw.Write(data); err != nil { 235 t.Fatalf("tw.Write: %v", err) 236 } 237 if err := tw.Close(); err != nil { 238 t.Fatalf("tw.Close: %v", err) 239 } 240 241 // Read it back. 242 tr := NewReader(&b) 243 rHdr, err := tr.Next() 244 if err != nil { 245 t.Fatalf("tr.Next: %v", err) 246 } 247 if !reflect.DeepEqual(rHdr, hdr) { 248 t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr) 249 } 250 rData, err := ioutil.ReadAll(tr) 251 if err != nil { 252 t.Fatalf("Read: %v", err) 253 } 254 if !bytes.Equal(rData, data) { 255 t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data) 256 } 257 } 258 259 type headerRoundTripTest struct { 260 h *Header 261 fm os.FileMode 262 } 263 264 func TestHeaderRoundTrip(t *testing.T) { 265 vectors := []headerRoundTripTest{{ 266 // regular file. 267 h: &Header{ 268 Name: "test.txt", 269 Mode: 0644, 270 Size: 12, 271 ModTime: time.Unix(1360600916, 0), 272 Typeflag: TypeReg, 273 }, 274 fm: 0644, 275 }, { 276 // symbolic link. 277 h: &Header{ 278 Name: "link.txt", 279 Mode: 0777, 280 Size: 0, 281 ModTime: time.Unix(1360600852, 0), 282 Typeflag: TypeSymlink, 283 }, 284 fm: 0777 | os.ModeSymlink, 285 }, { 286 // character device node. 287 h: &Header{ 288 Name: "dev/null", 289 Mode: 0666, 290 Size: 0, 291 ModTime: time.Unix(1360578951, 0), 292 Typeflag: TypeChar, 293 }, 294 fm: 0666 | os.ModeDevice | os.ModeCharDevice, 295 }, { 296 // block device node. 297 h: &Header{ 298 Name: "dev/sda", 299 Mode: 0660, 300 Size: 0, 301 ModTime: time.Unix(1360578954, 0), 302 Typeflag: TypeBlock, 303 }, 304 fm: 0660 | os.ModeDevice, 305 }, { 306 // directory. 307 h: &Header{ 308 Name: "dir/", 309 Mode: 0755, 310 Size: 0, 311 ModTime: time.Unix(1360601116, 0), 312 Typeflag: TypeDir, 313 }, 314 fm: 0755 | os.ModeDir, 315 }, { 316 // fifo node. 317 h: &Header{ 318 Name: "dev/initctl", 319 Mode: 0600, 320 Size: 0, 321 ModTime: time.Unix(1360578949, 0), 322 Typeflag: TypeFifo, 323 }, 324 fm: 0600 | os.ModeNamedPipe, 325 }, { 326 // setuid. 327 h: &Header{ 328 Name: "bin/su", 329 Mode: 0755 | c_ISUID, 330 Size: 23232, 331 ModTime: time.Unix(1355405093, 0), 332 Typeflag: TypeReg, 333 }, 334 fm: 0755 | os.ModeSetuid, 335 }, { 336 // setguid. 337 h: &Header{ 338 Name: "group.txt", 339 Mode: 0750 | c_ISGID, 340 Size: 0, 341 ModTime: time.Unix(1360602346, 0), 342 Typeflag: TypeReg, 343 }, 344 fm: 0750 | os.ModeSetgid, 345 }, { 346 // sticky. 347 h: &Header{ 348 Name: "sticky.txt", 349 Mode: 0600 | c_ISVTX, 350 Size: 7, 351 ModTime: time.Unix(1360602540, 0), 352 Typeflag: TypeReg, 353 }, 354 fm: 0600 | os.ModeSticky, 355 }, { 356 // hard link. 357 h: &Header{ 358 Name: "hard.txt", 359 Mode: 0644, 360 Size: 0, 361 Linkname: "file.txt", 362 ModTime: time.Unix(1360600916, 0), 363 Typeflag: TypeLink, 364 }, 365 fm: 0644, 366 }, { 367 // More information. 368 h: &Header{ 369 Name: "info.txt", 370 Mode: 0600, 371 Size: 0, 372 Uid: 1000, 373 Gid: 1000, 374 ModTime: time.Unix(1360602540, 0), 375 Uname: "slartibartfast", 376 Gname: "users", 377 Typeflag: TypeReg, 378 }, 379 fm: 0600, 380 }} 381 382 for i, v := range vectors { 383 fi := v.h.FileInfo() 384 h2, err := FileInfoHeader(fi, "") 385 if err != nil { 386 t.Error(err) 387 continue 388 } 389 if strings.Contains(fi.Name(), "/") { 390 t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name()) 391 } 392 name := path.Base(v.h.Name) 393 if fi.IsDir() { 394 name += "/" 395 } 396 if got, want := h2.Name, name; got != want { 397 t.Errorf("i=%d: Name: got %v, want %v", i, got, want) 398 } 399 if got, want := h2.Size, v.h.Size; got != want { 400 t.Errorf("i=%d: Size: got %v, want %v", i, got, want) 401 } 402 if got, want := h2.Uid, v.h.Uid; got != want { 403 t.Errorf("i=%d: Uid: got %d, want %d", i, got, want) 404 } 405 if got, want := h2.Gid, v.h.Gid; got != want { 406 t.Errorf("i=%d: Gid: got %d, want %d", i, got, want) 407 } 408 if got, want := h2.Uname, v.h.Uname; got != want { 409 t.Errorf("i=%d: Uname: got %q, want %q", i, got, want) 410 } 411 if got, want := h2.Gname, v.h.Gname; got != want { 412 t.Errorf("i=%d: Gname: got %q, want %q", i, got, want) 413 } 414 if got, want := h2.Linkname, v.h.Linkname; got != want { 415 t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want) 416 } 417 if got, want := h2.Typeflag, v.h.Typeflag; got != want { 418 t.Logf("%#v %#v", v.h, fi.Sys()) 419 t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want) 420 } 421 if got, want := h2.Mode, v.h.Mode; got != want { 422 t.Errorf("i=%d: Mode: got %o, want %o", i, got, want) 423 } 424 if got, want := fi.Mode(), v.fm; got != want { 425 t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want) 426 } 427 if got, want := h2.AccessTime, v.h.AccessTime; got != want { 428 t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want) 429 } 430 if got, want := h2.ChangeTime, v.h.ChangeTime; got != want { 431 t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want) 432 } 433 if got, want := h2.ModTime, v.h.ModTime; got != want { 434 t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want) 435 } 436 if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h { 437 t.Errorf("i=%d: Sys didn't return original *Header", i) 438 } 439 } 440 } 441 442 func TestHeaderAllowedFormats(t *testing.T) { 443 vectors := []struct { 444 header *Header // Input header 445 paxHdrs map[string]string // Expected PAX headers that may be needed 446 formats Format // Expected formats that can encode the header 447 }{{ 448 header: &Header{}, 449 formats: FormatUSTAR | FormatPAX | FormatGNU, 450 }, { 451 header: &Header{Size: 077777777777}, 452 formats: FormatUSTAR | FormatPAX | FormatGNU, 453 }, { 454 header: &Header{Size: 077777777777, Format: FormatUSTAR}, 455 formats: FormatUSTAR, 456 }, { 457 header: &Header{Size: 077777777777, Format: FormatPAX}, 458 formats: FormatUSTAR | FormatPAX, 459 }, { 460 header: &Header{Size: 077777777777, Format: FormatGNU}, 461 formats: FormatGNU, 462 }, { 463 header: &Header{Size: 077777777777 + 1}, 464 paxHdrs: map[string]string{paxSize: "8589934592"}, 465 formats: FormatPAX | FormatGNU, 466 }, { 467 header: &Header{Size: 077777777777 + 1, Format: FormatPAX}, 468 paxHdrs: map[string]string{paxSize: "8589934592"}, 469 formats: FormatPAX, 470 }, { 471 header: &Header{Size: 077777777777 + 1, Format: FormatGNU}, 472 paxHdrs: map[string]string{paxSize: "8589934592"}, 473 formats: FormatGNU, 474 }, { 475 header: &Header{Mode: 07777777}, 476 formats: FormatUSTAR | FormatPAX | FormatGNU, 477 }, { 478 header: &Header{Mode: 07777777 + 1}, 479 formats: FormatGNU, 480 }, { 481 header: &Header{Devmajor: -123}, 482 formats: FormatGNU, 483 }, { 484 header: &Header{Devmajor: 1<<56 - 1}, 485 formats: FormatGNU, 486 }, { 487 header: &Header{Devmajor: 1 << 56}, 488 formats: FormatUnknown, 489 }, { 490 header: &Header{Devmajor: -1 << 56}, 491 formats: FormatGNU, 492 }, { 493 header: &Header{Devmajor: -1<<56 - 1}, 494 formats: FormatUnknown, 495 }, { 496 header: &Header{Name: "用戶名", Devmajor: -1 << 56}, 497 formats: FormatGNU, 498 }, { 499 header: &Header{Size: math.MaxInt64}, 500 paxHdrs: map[string]string{paxSize: "9223372036854775807"}, 501 formats: FormatPAX | FormatGNU, 502 }, { 503 header: &Header{Size: math.MinInt64}, 504 paxHdrs: map[string]string{paxSize: "-9223372036854775808"}, 505 formats: FormatUnknown, 506 }, { 507 header: &Header{Uname: "0123456789abcdef0123456789abcdef"}, 508 formats: FormatUSTAR | FormatPAX | FormatGNU, 509 }, { 510 header: &Header{Uname: "0123456789abcdef0123456789abcdefx"}, 511 paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"}, 512 formats: FormatPAX, 513 }, { 514 header: &Header{Name: "foobar"}, 515 formats: FormatUSTAR | FormatPAX | FormatGNU, 516 }, { 517 header: &Header{Name: strings.Repeat("a", nameSize)}, 518 formats: FormatUSTAR | FormatPAX | FormatGNU, 519 }, { 520 header: &Header{Name: strings.Repeat("a", nameSize+1)}, 521 paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)}, 522 formats: FormatPAX | FormatGNU, 523 }, { 524 header: &Header{Linkname: "用戶名"}, 525 paxHdrs: map[string]string{paxLinkpath: "用戶名"}, 526 formats: FormatPAX | FormatGNU, 527 }, { 528 header: &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)}, 529 paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)}, 530 formats: FormatUnknown, 531 }, { 532 header: &Header{Linkname: "\x00hello"}, 533 paxHdrs: map[string]string{paxLinkpath: "\x00hello"}, 534 formats: FormatUnknown, 535 }, { 536 header: &Header{Uid: 07777777}, 537 formats: FormatUSTAR | FormatPAX | FormatGNU, 538 }, { 539 header: &Header{Uid: 07777777 + 1}, 540 paxHdrs: map[string]string{paxUid: "2097152"}, 541 formats: FormatPAX | FormatGNU, 542 }, { 543 header: &Header{Xattrs: nil}, 544 formats: FormatUSTAR | FormatPAX | FormatGNU, 545 }, { 546 header: &Header{Xattrs: map[string]string{"foo": "bar"}}, 547 paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"}, 548 formats: FormatPAX, 549 }, { 550 header: &Header{Xattrs: map[string]string{"foo": "bar"}, Format: FormatGNU}, 551 paxHdrs: map[string]string{paxSchilyXattr + "foo": "bar"}, 552 formats: FormatUnknown, 553 }, { 554 header: &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}}, 555 paxHdrs: map[string]string{paxSchilyXattr + "用戶名": "\x00hello"}, 556 formats: FormatPAX, 557 }, { 558 header: &Header{Xattrs: map[string]string{"foo=bar": "baz"}}, 559 formats: FormatUnknown, 560 }, { 561 header: &Header{Xattrs: map[string]string{"foo": ""}}, 562 paxHdrs: map[string]string{paxSchilyXattr + "foo": ""}, 563 formats: FormatPAX, 564 }, { 565 header: &Header{ModTime: time.Unix(0, 0)}, 566 formats: FormatUSTAR | FormatPAX | FormatGNU, 567 }, { 568 header: &Header{ModTime: time.Unix(077777777777, 0)}, 569 formats: FormatUSTAR | FormatPAX | FormatGNU, 570 }, { 571 header: &Header{ModTime: time.Unix(077777777777+1, 0)}, 572 paxHdrs: map[string]string{paxMtime: "8589934592"}, 573 formats: FormatPAX | FormatGNU, 574 }, { 575 header: &Header{ModTime: time.Unix(math.MaxInt64, 0)}, 576 paxHdrs: map[string]string{paxMtime: "9223372036854775807"}, 577 formats: FormatPAX | FormatGNU, 578 }, { 579 header: &Header{ModTime: time.Unix(math.MaxInt64, 0), Format: FormatUSTAR}, 580 paxHdrs: map[string]string{paxMtime: "9223372036854775807"}, 581 formats: FormatUnknown, 582 }, { 583 header: &Header{ModTime: time.Unix(-1, 0)}, 584 paxHdrs: map[string]string{paxMtime: "-1"}, 585 formats: FormatPAX | FormatGNU, 586 }, { 587 header: &Header{ModTime: time.Unix(1, 500)}, 588 paxHdrs: map[string]string{paxMtime: "1.0000005"}, 589 formats: FormatUSTAR | FormatPAX | FormatGNU, 590 }, { 591 header: &Header{ModTime: time.Unix(1, 0)}, 592 formats: FormatUSTAR | FormatPAX | FormatGNU, 593 }, { 594 header: &Header{ModTime: time.Unix(1, 0), Format: FormatPAX}, 595 formats: FormatUSTAR | FormatPAX, 596 }, { 597 header: &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR}, 598 paxHdrs: map[string]string{paxMtime: "1.0000005"}, 599 formats: FormatUSTAR, 600 }, { 601 header: &Header{ModTime: time.Unix(1, 500), Format: FormatPAX}, 602 paxHdrs: map[string]string{paxMtime: "1.0000005"}, 603 formats: FormatPAX, 604 }, { 605 header: &Header{ModTime: time.Unix(1, 500), Format: FormatGNU}, 606 paxHdrs: map[string]string{paxMtime: "1.0000005"}, 607 formats: FormatGNU, 608 }, { 609 header: &Header{ModTime: time.Unix(-1, 500)}, 610 paxHdrs: map[string]string{paxMtime: "-0.9999995"}, 611 formats: FormatPAX | FormatGNU, 612 }, { 613 header: &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU}, 614 paxHdrs: map[string]string{paxMtime: "-0.9999995"}, 615 formats: FormatGNU, 616 }, { 617 header: &Header{AccessTime: time.Unix(0, 0)}, 618 paxHdrs: map[string]string{paxAtime: "0"}, 619 formats: FormatUSTAR | FormatPAX | FormatGNU, 620 }, { 621 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR}, 622 paxHdrs: map[string]string{paxAtime: "0"}, 623 formats: FormatUnknown, 624 }, { 625 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX}, 626 paxHdrs: map[string]string{paxAtime: "0"}, 627 formats: FormatPAX, 628 }, { 629 header: &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU}, 630 paxHdrs: map[string]string{paxAtime: "0"}, 631 formats: FormatGNU, 632 }, { 633 header: &Header{AccessTime: time.Unix(-123, 0)}, 634 paxHdrs: map[string]string{paxAtime: "-123"}, 635 formats: FormatUSTAR | FormatPAX | FormatGNU, 636 }, { 637 header: &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX}, 638 paxHdrs: map[string]string{paxAtime: "-123"}, 639 formats: FormatPAX, 640 }, { 641 header: &Header{ChangeTime: time.Unix(123, 456)}, 642 paxHdrs: map[string]string{paxCtime: "123.000000456"}, 643 formats: FormatUSTAR | FormatPAX | FormatGNU, 644 }, { 645 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR}, 646 paxHdrs: map[string]string{paxCtime: "123.000000456"}, 647 formats: FormatUnknown, 648 }, { 649 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU}, 650 paxHdrs: map[string]string{paxCtime: "123.000000456"}, 651 formats: FormatGNU, 652 }, { 653 header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX}, 654 paxHdrs: map[string]string{paxCtime: "123.000000456"}, 655 formats: FormatPAX, 656 }, { 657 header: &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}}, 658 formats: FormatPAX, 659 }, { 660 header: &Header{Name: "sparse.db", Size: 1000, Typeflag: TypeGNUSparse, SparseHoles: []SparseEntry{{0, 500}}}, 661 formats: FormatGNU, 662 }, { 663 header: &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatGNU}, 664 formats: FormatUnknown, 665 }, { 666 header: &Header{Name: "sparse.db", Size: 1000, Typeflag: TypeGNUSparse, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatPAX}, 667 formats: FormatUnknown, 668 }, { 669 header: &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}, Format: FormatUSTAR}, 670 formats: FormatUnknown, 671 }} 672 673 for i, v := range vectors { 674 formats, paxHdrs, err := v.header.allowedFormats() 675 if formats != v.formats { 676 t.Errorf("test %d, allowedFormats(): got %v, want %v", i, formats, v.formats) 677 } 678 if formats&FormatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) { 679 t.Errorf("test %d, allowedFormats():\ngot %v\nwant %s", i, paxHdrs, v.paxHdrs) 680 } 681 if (formats != FormatUnknown) && (err != nil) { 682 t.Errorf("test %d, unexpected error: %v", i, err) 683 } 684 if (formats == FormatUnknown) && (err == nil) { 685 t.Errorf("test %d, got nil-error, want non-nil error", i) 686 } 687 } 688 } 689 690 func Benchmark(b *testing.B) { 691 type file struct { 692 hdr *Header 693 body []byte 694 } 695 696 vectors := []struct { 697 label string 698 files []file 699 }{{ 700 "USTAR", 701 []file{{ 702 &Header{Name: "bar", Mode: 0640, Size: int64(3)}, 703 []byte("foo"), 704 }, { 705 &Header{Name: "world", Mode: 0640, Size: int64(5)}, 706 []byte("hello"), 707 }}, 708 }, { 709 "GNU", 710 []file{{ 711 &Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1}, 712 []byte("foo"), 713 }, { 714 &Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1}, 715 []byte("hello"), 716 }}, 717 }, { 718 "PAX", 719 []file{{ 720 &Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}}, 721 []byte("foo"), 722 }, { 723 &Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}}, 724 []byte("hello"), 725 }}, 726 }} 727 728 b.Run("Writer", func(b *testing.B) { 729 for _, v := range vectors { 730 b.Run(v.label, func(b *testing.B) { 731 b.ReportAllocs() 732 for i := 0; i < b.N; i++ { 733 // Writing to ioutil.Discard because we want to 734 // test purely the writer code and not bring in disk performance into this. 735 tw := NewWriter(ioutil.Discard) 736 for _, file := range v.files { 737 if err := tw.WriteHeader(file.hdr); err != nil { 738 b.Errorf("unexpected WriteHeader error: %v", err) 739 } 740 if _, err := tw.Write(file.body); err != nil { 741 b.Errorf("unexpected Write error: %v", err) 742 } 743 } 744 if err := tw.Close(); err != nil { 745 b.Errorf("unexpected Close error: %v", err) 746 } 747 } 748 }) 749 } 750 }) 751 752 b.Run("Reader", func(b *testing.B) { 753 for _, v := range vectors { 754 var buf bytes.Buffer 755 var r bytes.Reader 756 757 // Write the archive to a byte buffer. 758 tw := NewWriter(&buf) 759 for _, file := range v.files { 760 tw.WriteHeader(file.hdr) 761 tw.Write(file.body) 762 } 763 tw.Close() 764 b.Run(v.label, func(b *testing.B) { 765 b.ReportAllocs() 766 // Read from the byte buffer. 767 for i := 0; i < b.N; i++ { 768 r.Reset(buf.Bytes()) 769 tr := NewReader(&r) 770 if _, err := tr.Next(); err != nil { 771 b.Errorf("unexpected Next error: %v", err) 772 } 773 if _, err := io.Copy(ioutil.Discard, tr); err != nil { 774 b.Errorf("unexpected Copy error : %v", err) 775 } 776 } 777 }) 778 } 779 }) 780 781 }