github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/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 TestFileInfoHeader(t *testing.T) { 23 fi, err := os.Stat("testdata/small.txt") 24 if err != nil { 25 t.Fatal(err) 26 } 27 h, err := FileInfoHeader(fi, "") 28 if err != nil { 29 t.Fatalf("FileInfoHeader: %v", err) 30 } 31 if g, e := h.Name, "small.txt"; g != e { 32 t.Errorf("Name = %q; want %q", g, e) 33 } 34 if g, e := h.Mode, int64(fi.Mode().Perm()); g != e { 35 t.Errorf("Mode = %#o; want %#o", g, e) 36 } 37 if g, e := h.Size, int64(5); g != e { 38 t.Errorf("Size = %v; want %v", g, e) 39 } 40 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { 41 t.Errorf("ModTime = %v; want %v", g, e) 42 } 43 // FileInfoHeader should error when passing nil FileInfo 44 if _, err := FileInfoHeader(nil, ""); err == nil { 45 t.Fatalf("Expected error when passing nil to FileInfoHeader") 46 } 47 } 48 49 func TestFileInfoHeaderDir(t *testing.T) { 50 fi, err := os.Stat("testdata") 51 if err != nil { 52 t.Fatal(err) 53 } 54 h, err := FileInfoHeader(fi, "") 55 if err != nil { 56 t.Fatalf("FileInfoHeader: %v", err) 57 } 58 if g, e := h.Name, "testdata/"; g != e { 59 t.Errorf("Name = %q; want %q", g, e) 60 } 61 // Ignoring c_ISGID for golang.org/issue/4867 62 if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm()); g != e { 63 t.Errorf("Mode = %#o; want %#o", g, e) 64 } 65 if g, e := h.Size, int64(0); g != e { 66 t.Errorf("Size = %v; want %v", g, e) 67 } 68 if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { 69 t.Errorf("ModTime = %v; want %v", g, e) 70 } 71 } 72 73 func TestFileInfoHeaderSymlink(t *testing.T) { 74 testenv.MustHaveSymlink(t) 75 76 tmpdir, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink") 77 if err != nil { 78 t.Fatal(err) 79 } 80 defer os.RemoveAll(tmpdir) 81 82 link := filepath.Join(tmpdir, "link") 83 target := tmpdir 84 err = os.Symlink(target, link) 85 if err != nil { 86 t.Fatal(err) 87 } 88 fi, err := os.Lstat(link) 89 if err != nil { 90 t.Fatal(err) 91 } 92 93 h, err := FileInfoHeader(fi, target) 94 if err != nil { 95 t.Fatal(err) 96 } 97 if g, e := h.Name, fi.Name(); g != e { 98 t.Errorf("Name = %q; want %q", g, e) 99 } 100 if g, e := h.Linkname, target; g != e { 101 t.Errorf("Linkname = %q; want %q", g, e) 102 } 103 if g, e := h.Typeflag, byte(TypeSymlink); g != e { 104 t.Errorf("Typeflag = %v; want %v", g, e) 105 } 106 } 107 108 func TestRoundTrip(t *testing.T) { 109 data := []byte("some file contents") 110 111 var b bytes.Buffer 112 tw := NewWriter(&b) 113 hdr := &Header{ 114 Name: "file.txt", 115 Uid: 1 << 21, // too big for 8 octal digits 116 Size: int64(len(data)), 117 // AddDate to strip monotonic clock reading, 118 // and Round to discard sub-second precision, 119 // both of which are not included in the tar header 120 // and would otherwise break the round-trip check 121 // below. 122 ModTime: time.Now().AddDate(0, 0, 0).Round(1 * time.Second), 123 } 124 if err := tw.WriteHeader(hdr); err != nil { 125 t.Fatalf("tw.WriteHeader: %v", err) 126 } 127 if _, err := tw.Write(data); err != nil { 128 t.Fatalf("tw.Write: %v", err) 129 } 130 if err := tw.Close(); err != nil { 131 t.Fatalf("tw.Close: %v", err) 132 } 133 134 // Read it back. 135 tr := NewReader(&b) 136 rHdr, err := tr.Next() 137 if err != nil { 138 t.Fatalf("tr.Next: %v", err) 139 } 140 if !reflect.DeepEqual(rHdr, hdr) { 141 t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr) 142 } 143 rData, err := ioutil.ReadAll(tr) 144 if err != nil { 145 t.Fatalf("Read: %v", err) 146 } 147 if !bytes.Equal(rData, data) { 148 t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data) 149 } 150 } 151 152 type headerRoundTripTest struct { 153 h *Header 154 fm os.FileMode 155 } 156 157 func TestHeaderRoundTrip(t *testing.T) { 158 vectors := []headerRoundTripTest{{ 159 // regular file. 160 h: &Header{ 161 Name: "test.txt", 162 Mode: 0644, 163 Size: 12, 164 ModTime: time.Unix(1360600916, 0), 165 Typeflag: TypeReg, 166 }, 167 fm: 0644, 168 }, { 169 // symbolic link. 170 h: &Header{ 171 Name: "link.txt", 172 Mode: 0777, 173 Size: 0, 174 ModTime: time.Unix(1360600852, 0), 175 Typeflag: TypeSymlink, 176 }, 177 fm: 0777 | os.ModeSymlink, 178 }, { 179 // character device node. 180 h: &Header{ 181 Name: "dev/null", 182 Mode: 0666, 183 Size: 0, 184 ModTime: time.Unix(1360578951, 0), 185 Typeflag: TypeChar, 186 }, 187 fm: 0666 | os.ModeDevice | os.ModeCharDevice, 188 }, { 189 // block device node. 190 h: &Header{ 191 Name: "dev/sda", 192 Mode: 0660, 193 Size: 0, 194 ModTime: time.Unix(1360578954, 0), 195 Typeflag: TypeBlock, 196 }, 197 fm: 0660 | os.ModeDevice, 198 }, { 199 // directory. 200 h: &Header{ 201 Name: "dir/", 202 Mode: 0755, 203 Size: 0, 204 ModTime: time.Unix(1360601116, 0), 205 Typeflag: TypeDir, 206 }, 207 fm: 0755 | os.ModeDir, 208 }, { 209 // fifo node. 210 h: &Header{ 211 Name: "dev/initctl", 212 Mode: 0600, 213 Size: 0, 214 ModTime: time.Unix(1360578949, 0), 215 Typeflag: TypeFifo, 216 }, 217 fm: 0600 | os.ModeNamedPipe, 218 }, { 219 // setuid. 220 h: &Header{ 221 Name: "bin/su", 222 Mode: 0755 | c_ISUID, 223 Size: 23232, 224 ModTime: time.Unix(1355405093, 0), 225 Typeflag: TypeReg, 226 }, 227 fm: 0755 | os.ModeSetuid, 228 }, { 229 // setguid. 230 h: &Header{ 231 Name: "group.txt", 232 Mode: 0750 | c_ISGID, 233 Size: 0, 234 ModTime: time.Unix(1360602346, 0), 235 Typeflag: TypeReg, 236 }, 237 fm: 0750 | os.ModeSetgid, 238 }, { 239 // sticky. 240 h: &Header{ 241 Name: "sticky.txt", 242 Mode: 0600 | c_ISVTX, 243 Size: 7, 244 ModTime: time.Unix(1360602540, 0), 245 Typeflag: TypeReg, 246 }, 247 fm: 0600 | os.ModeSticky, 248 }, { 249 // hard link. 250 h: &Header{ 251 Name: "hard.txt", 252 Mode: 0644, 253 Size: 0, 254 Linkname: "file.txt", 255 ModTime: time.Unix(1360600916, 0), 256 Typeflag: TypeLink, 257 }, 258 fm: 0644, 259 }, { 260 // More information. 261 h: &Header{ 262 Name: "info.txt", 263 Mode: 0600, 264 Size: 0, 265 Uid: 1000, 266 Gid: 1000, 267 ModTime: time.Unix(1360602540, 0), 268 Uname: "slartibartfast", 269 Gname: "users", 270 Typeflag: TypeReg, 271 }, 272 fm: 0600, 273 }} 274 275 for i, v := range vectors { 276 fi := v.h.FileInfo() 277 h2, err := FileInfoHeader(fi, "") 278 if err != nil { 279 t.Error(err) 280 continue 281 } 282 if strings.Contains(fi.Name(), "/") { 283 t.Errorf("FileInfo of %q contains slash: %q", v.h.Name, fi.Name()) 284 } 285 name := path.Base(v.h.Name) 286 if fi.IsDir() { 287 name += "/" 288 } 289 if got, want := h2.Name, name; got != want { 290 t.Errorf("i=%d: Name: got %v, want %v", i, got, want) 291 } 292 if got, want := h2.Size, v.h.Size; got != want { 293 t.Errorf("i=%d: Size: got %v, want %v", i, got, want) 294 } 295 if got, want := h2.Uid, v.h.Uid; got != want { 296 t.Errorf("i=%d: Uid: got %d, want %d", i, got, want) 297 } 298 if got, want := h2.Gid, v.h.Gid; got != want { 299 t.Errorf("i=%d: Gid: got %d, want %d", i, got, want) 300 } 301 if got, want := h2.Uname, v.h.Uname; got != want { 302 t.Errorf("i=%d: Uname: got %q, want %q", i, got, want) 303 } 304 if got, want := h2.Gname, v.h.Gname; got != want { 305 t.Errorf("i=%d: Gname: got %q, want %q", i, got, want) 306 } 307 if got, want := h2.Linkname, v.h.Linkname; got != want { 308 t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want) 309 } 310 if got, want := h2.Typeflag, v.h.Typeflag; got != want { 311 t.Logf("%#v %#v", v.h, fi.Sys()) 312 t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want) 313 } 314 if got, want := h2.Mode, v.h.Mode; got != want { 315 t.Errorf("i=%d: Mode: got %o, want %o", i, got, want) 316 } 317 if got, want := fi.Mode(), v.fm; got != want { 318 t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want) 319 } 320 if got, want := h2.AccessTime, v.h.AccessTime; got != want { 321 t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want) 322 } 323 if got, want := h2.ChangeTime, v.h.ChangeTime; got != want { 324 t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want) 325 } 326 if got, want := h2.ModTime, v.h.ModTime; got != want { 327 t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want) 328 } 329 if sysh, ok := fi.Sys().(*Header); !ok || sysh != v.h { 330 t.Errorf("i=%d: Sys didn't return original *Header", i) 331 } 332 } 333 } 334 335 func TestHeaderAllowedFormats(t *testing.T) { 336 prettyFormat := func(f int) string { 337 if f == formatUnknown { 338 return "(formatUnknown)" 339 } 340 var fs []string 341 if f&formatUSTAR > 0 { 342 fs = append(fs, "formatUSTAR") 343 } 344 if f&formatPAX > 0 { 345 fs = append(fs, "formatPAX") 346 } 347 if f&formatGNU > 0 { 348 fs = append(fs, "formatGNU") 349 } 350 return "(" + strings.Join(fs, " | ") + ")" 351 } 352 353 vectors := []struct { 354 header *Header // Input header 355 paxHdrs map[string]string // Expected PAX headers that may be needed 356 formats int // Expected formats that can encode the header 357 }{{ 358 header: &Header{}, 359 formats: formatUSTAR | formatPAX | formatGNU, 360 }, { 361 header: &Header{Size: 077777777777}, 362 formats: formatUSTAR | formatPAX | formatGNU, 363 }, { 364 header: &Header{Size: 077777777777 + 1}, 365 paxHdrs: map[string]string{paxSize: "8589934592"}, 366 formats: formatPAX | formatGNU, 367 }, { 368 header: &Header{Mode: 07777777}, 369 formats: formatUSTAR | formatPAX | formatGNU, 370 }, { 371 header: &Header{Mode: 07777777 + 1}, 372 formats: formatGNU, 373 }, { 374 header: &Header{Devmajor: -123}, 375 formats: formatGNU, 376 }, { 377 header: &Header{Devmajor: 1<<56 - 1}, 378 formats: formatGNU, 379 }, { 380 header: &Header{Devmajor: 1 << 56}, 381 formats: formatUnknown, 382 }, { 383 header: &Header{Devmajor: -1 << 56}, 384 formats: formatGNU, 385 }, { 386 header: &Header{Devmajor: -1<<56 - 1}, 387 formats: formatUnknown, 388 }, { 389 header: &Header{Name: "用戶名", Devmajor: -1 << 56}, 390 formats: formatGNU, 391 }, { 392 header: &Header{Size: math.MaxInt64}, 393 paxHdrs: map[string]string{paxSize: "9223372036854775807"}, 394 formats: formatPAX | formatGNU, 395 }, { 396 header: &Header{Size: math.MinInt64}, 397 paxHdrs: map[string]string{paxSize: "-9223372036854775808"}, 398 formats: formatUnknown, 399 }, { 400 header: &Header{Uname: "0123456789abcdef0123456789abcdef"}, 401 formats: formatUSTAR | formatPAX | formatGNU, 402 }, { 403 header: &Header{Uname: "0123456789abcdef0123456789abcdefx"}, 404 paxHdrs: map[string]string{paxUname: "0123456789abcdef0123456789abcdefx"}, 405 formats: formatPAX, 406 }, { 407 header: &Header{Name: "foobar"}, 408 formats: formatUSTAR | formatPAX | formatGNU, 409 }, { 410 header: &Header{Name: strings.Repeat("a", nameSize)}, 411 formats: formatUSTAR | formatPAX | formatGNU, 412 }, { 413 header: &Header{Name: strings.Repeat("a", nameSize+1)}, 414 paxHdrs: map[string]string{paxPath: strings.Repeat("a", nameSize+1)}, 415 formats: formatPAX | formatGNU, 416 }, { 417 header: &Header{Linkname: "用戶名"}, 418 paxHdrs: map[string]string{paxLinkpath: "用戶名"}, 419 formats: formatPAX | formatGNU, 420 }, { 421 header: &Header{Linkname: strings.Repeat("用戶名\x00", nameSize)}, 422 paxHdrs: map[string]string{paxLinkpath: strings.Repeat("用戶名\x00", nameSize)}, 423 formats: formatUnknown, 424 }, { 425 header: &Header{Linkname: "\x00hello"}, 426 paxHdrs: map[string]string{paxLinkpath: "\x00hello"}, 427 formats: formatUnknown, 428 }, { 429 header: &Header{Uid: 07777777}, 430 formats: formatUSTAR | formatPAX | formatGNU, 431 }, { 432 header: &Header{Uid: 07777777 + 1}, 433 paxHdrs: map[string]string{paxUid: "2097152"}, 434 formats: formatPAX | formatGNU, 435 }, { 436 header: &Header{Xattrs: nil}, 437 formats: formatUSTAR | formatPAX | formatGNU, 438 }, { 439 header: &Header{Xattrs: map[string]string{"foo": "bar"}}, 440 paxHdrs: map[string]string{paxXattr + "foo": "bar"}, 441 formats: formatPAX, 442 }, { 443 header: &Header{Xattrs: map[string]string{"用戶名": "\x00hello"}}, 444 paxHdrs: map[string]string{paxXattr + "用戶名": "\x00hello"}, 445 formats: formatPAX, 446 }, { 447 header: &Header{Xattrs: map[string]string{"foo=bar": "baz"}}, 448 formats: formatUnknown, 449 }, { 450 header: &Header{Xattrs: map[string]string{"foo": ""}}, 451 formats: formatUnknown, 452 }, { 453 header: &Header{ModTime: time.Unix(0, 0)}, 454 formats: formatUSTAR | formatPAX | formatGNU, 455 }, { 456 header: &Header{ModTime: time.Unix(077777777777, 0)}, 457 formats: formatUSTAR | formatPAX | formatGNU, 458 }, { 459 header: &Header{ModTime: time.Unix(077777777777+1, 0)}, 460 paxHdrs: map[string]string{paxMtime: "8589934592"}, 461 formats: formatPAX | formatGNU, 462 }, { 463 header: &Header{ModTime: time.Unix(math.MaxInt64, 0)}, 464 paxHdrs: map[string]string{paxMtime: "9223372036854775807"}, 465 formats: formatPAX | formatGNU, 466 }, { 467 header: &Header{ModTime: time.Unix(-1, 0)}, 468 paxHdrs: map[string]string{paxMtime: "-1"}, 469 formats: formatPAX | formatGNU, 470 }, { 471 header: &Header{ModTime: time.Unix(-1, 500)}, 472 paxHdrs: map[string]string{paxMtime: "-0.9999995"}, 473 formats: formatPAX, 474 }, { 475 header: &Header{AccessTime: time.Unix(0, 0)}, 476 paxHdrs: map[string]string{paxAtime: "0"}, 477 formats: formatPAX | formatGNU, 478 }, { 479 header: &Header{AccessTime: time.Unix(-123, 0)}, 480 paxHdrs: map[string]string{paxAtime: "-123"}, 481 formats: formatPAX | formatGNU, 482 }, { 483 header: &Header{ChangeTime: time.Unix(123, 456)}, 484 paxHdrs: map[string]string{paxCtime: "123.000000456"}, 485 formats: formatPAX, 486 }} 487 488 for i, v := range vectors { 489 formats, paxHdrs := v.header.allowedFormats() 490 if formats != v.formats { 491 t.Errorf("test %d, allowedFormats(...): got %v, want %v", i, prettyFormat(formats), prettyFormat(v.formats)) 492 } 493 if formats&formatPAX > 0 && !reflect.DeepEqual(paxHdrs, v.paxHdrs) && !(len(paxHdrs) == 0 && len(v.paxHdrs) == 0) { 494 t.Errorf("test %d, allowedFormats(...):\ngot %v\nwant %s", i, paxHdrs, v.paxHdrs) 495 } 496 } 497 } 498 499 func Benchmark(b *testing.B) { 500 type file struct { 501 hdr *Header 502 body []byte 503 } 504 505 vectors := []struct { 506 label string 507 files []file 508 }{{ 509 "USTAR", 510 []file{{ 511 &Header{Name: "bar", Mode: 0640, Size: int64(3)}, 512 []byte("foo"), 513 }, { 514 &Header{Name: "world", Mode: 0640, Size: int64(5)}, 515 []byte("hello"), 516 }}, 517 }, { 518 "GNU", 519 []file{{ 520 &Header{Name: "bar", Mode: 0640, Size: int64(3), Devmajor: -1}, 521 []byte("foo"), 522 }, { 523 &Header{Name: "world", Mode: 0640, Size: int64(5), Devmajor: -1}, 524 []byte("hello"), 525 }}, 526 }, { 527 "PAX", 528 []file{{ 529 &Header{Name: "bar", Mode: 0640, Size: int64(3), Xattrs: map[string]string{"foo": "bar"}}, 530 []byte("foo"), 531 }, { 532 &Header{Name: "world", Mode: 0640, Size: int64(5), Xattrs: map[string]string{"foo": "bar"}}, 533 []byte("hello"), 534 }}, 535 }} 536 537 b.Run("Writer", func(b *testing.B) { 538 for _, v := range vectors { 539 b.Run(v.label, func(b *testing.B) { 540 b.ReportAllocs() 541 for i := 0; i < b.N; i++ { 542 // Writing to ioutil.Discard because we want to 543 // test purely the writer code and not bring in disk performance into this. 544 tw := NewWriter(ioutil.Discard) 545 for _, file := range v.files { 546 if err := tw.WriteHeader(file.hdr); err != nil { 547 b.Errorf("unexpected WriteHeader error: %v", err) 548 } 549 if _, err := tw.Write(file.body); err != nil { 550 b.Errorf("unexpected Write error: %v", err) 551 } 552 } 553 if err := tw.Close(); err != nil { 554 b.Errorf("unexpected Close error: %v", err) 555 } 556 } 557 }) 558 } 559 }) 560 561 b.Run("Reader", func(b *testing.B) { 562 for _, v := range vectors { 563 var buf bytes.Buffer 564 var r bytes.Reader 565 566 // Write the archive to a byte buffer. 567 tw := NewWriter(&buf) 568 for _, file := range v.files { 569 tw.WriteHeader(file.hdr) 570 tw.Write(file.body) 571 } 572 tw.Close() 573 b.Run(v.label, func(b *testing.B) { 574 b.ReportAllocs() 575 // Read from the byte buffer. 576 for i := 0; i < b.N; i++ { 577 r.Reset(buf.Bytes()) 578 tr := NewReader(&r) 579 if _, err := tr.Next(); err != nil { 580 b.Errorf("unexpected Next error: %v", err) 581 } 582 if _, err := io.Copy(ioutil.Discard, tr); err != nil { 583 b.Errorf("unexpected Copy error : %v", err) 584 } 585 } 586 }) 587 } 588 }) 589 590 }