github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/pkg/tarsum/tarsum_test.go (about) 1 package tarsum 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "crypto/md5" 8 "crypto/rand" 9 "crypto/sha1" 10 "crypto/sha256" 11 "crypto/sha512" 12 "encoding/hex" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "os" 17 "strings" 18 "testing" 19 ) 20 21 type testLayer struct { 22 filename string 23 options *sizedOptions 24 jsonfile string 25 gzip bool 26 tarsum string 27 version Version 28 hash THash 29 } 30 31 var testLayers = []testLayer{ 32 { 33 filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", 34 jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", 35 version: Version0, 36 tarsum: "tarsum+sha256:4095cc12fa5fdb1ab2760377e1cd0c4ecdd3e61b4f9b82319d96fcea6c9a41c6"}, 37 { 38 filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", 39 jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", 40 version: VersionDev, 41 tarsum: "tarsum.dev+sha256:db56e35eec6ce65ba1588c20ba6b1ea23743b59e81fb6b7f358ccbde5580345c"}, 42 { 43 filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", 44 jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", 45 gzip: true, 46 tarsum: "tarsum+sha256:4095cc12fa5fdb1ab2760377e1cd0c4ecdd3e61b4f9b82319d96fcea6c9a41c6"}, 47 { 48 // Tests existing version of TarSum when xattrs are present 49 filename: "testdata/xattr/layer.tar", 50 jsonfile: "testdata/xattr/json", 51 version: Version0, 52 tarsum: "tarsum+sha256:07e304a8dbcb215b37649fde1a699f8aeea47e60815707f1cdf4d55d25ff6ab4"}, 53 { 54 // Tests next version of TarSum when xattrs are present 55 filename: "testdata/xattr/layer.tar", 56 jsonfile: "testdata/xattr/json", 57 version: VersionDev, 58 tarsum: "tarsum.dev+sha256:6c58917892d77b3b357b0f9ad1e28e1f4ae4de3a8006bd3beb8beda214d8fd16"}, 59 { 60 filename: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar", 61 jsonfile: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json", 62 tarsum: "tarsum+sha256:c66bd5ec9f87b8f4c6135ca37684618f486a3dd1d113b138d0a177bfa39c2571"}, 63 { 64 options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) 65 tarsum: "tarsum+sha256:8bf12d7e67c51ee2e8306cba569398b1b9f419969521a12ffb9d8875e8836738"}, 66 { 67 // this tar has two files with the same path 68 filename: "testdata/collision/collision-0.tar", 69 tarsum: "tarsum+sha256:08653904a68d3ab5c59e65ef58c49c1581caa3c34744f8d354b3f575ea04424a"}, 70 { 71 // this tar has the same two files (with the same path), but reversed order. ensuring is has different hash than above 72 filename: "testdata/collision/collision-1.tar", 73 tarsum: "tarsum+sha256:b51c13fbefe158b5ce420d2b930eef54c5cd55c50a2ee4abdddea8fa9f081e0d"}, 74 { 75 // this tar has newer of collider-0.tar, ensuring is has different hash 76 filename: "testdata/collision/collision-2.tar", 77 tarsum: "tarsum+sha256:381547080919bb82691e995508ae20ed33ce0f6948d41cafbeb70ce20c73ee8e"}, 78 { 79 // this tar has newer of collider-1.tar, ensuring is has different hash 80 filename: "testdata/collision/collision-3.tar", 81 tarsum: "tarsum+sha256:f886e431c08143164a676805205979cd8fa535dfcef714db5515650eea5a7c0f"}, 82 { 83 options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) 84 tarsum: "tarsum+md5:0d7529ec7a8360155b48134b8e599f53", 85 hash: md5THash, 86 }, 87 { 88 options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) 89 tarsum: "tarsum+sha1:f1fee39c5925807ff75ef1925e7a23be444ba4df", 90 hash: sha1Hash, 91 }, 92 { 93 options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) 94 tarsum: "tarsum+sha224:6319390c0b061d639085d8748b14cd55f697cf9313805218b21cf61c", 95 hash: sha224Hash, 96 }, 97 { 98 options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) 99 tarsum: "tarsum+sha384:a578ce3ce29a2ae03b8ed7c26f47d0f75b4fc849557c62454be4b5ffd66ba021e713b48ce71e947b43aab57afd5a7636", 100 hash: sha384Hash, 101 }, 102 { 103 options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) 104 tarsum: "tarsum+sha512:e9bfb90ca5a4dfc93c46ee061a5cf9837de6d2fdf82544d6460d3147290aecfabf7b5e415b9b6e72db9b8941f149d5d69fb17a394cbfaf2eac523bd9eae21855", 105 hash: sha512Hash, 106 }, 107 } 108 109 type sizedOptions struct { 110 num int64 111 size int64 112 isRand bool 113 realFile bool 114 } 115 116 // make a tar: 117 // * num is the number of files the tar should have 118 // * size is the bytes per file 119 // * isRand is whether the contents of the files should be a random chunk (otherwise it's all zeros) 120 // * realFile will write to a TempFile, instead of an in memory buffer 121 func sizedTar(opts sizedOptions) io.Reader { 122 var ( 123 fh io.ReadWriter 124 err error 125 ) 126 if opts.realFile { 127 fh, err = ioutil.TempFile("", "tarsum") 128 if err != nil { 129 return nil 130 } 131 } else { 132 fh = bytes.NewBuffer([]byte{}) 133 } 134 tarW := tar.NewWriter(fh) 135 defer tarW.Close() 136 for i := int64(0); i < opts.num; i++ { 137 err := tarW.WriteHeader(&tar.Header{ 138 Name: fmt.Sprintf("/testdata%d", i), 139 Mode: 0755, 140 Uid: 0, 141 Gid: 0, 142 Size: opts.size, 143 }) 144 if err != nil { 145 return nil 146 } 147 var rBuf []byte 148 if opts.isRand { 149 rBuf = make([]byte, 8) 150 _, err = rand.Read(rBuf) 151 if err != nil { 152 return nil 153 } 154 } else { 155 rBuf = []byte{0, 0, 0, 0, 0, 0, 0, 0} 156 } 157 158 for i := int64(0); i < opts.size/int64(8); i++ { 159 tarW.Write(rBuf) 160 } 161 } 162 return fh 163 } 164 165 func emptyTarSum(gzip bool) (TarSum, error) { 166 reader, writer := io.Pipe() 167 tarWriter := tar.NewWriter(writer) 168 169 // Immediately close tarWriter and write-end of the 170 // Pipe in a separate goroutine so we don't block. 171 go func() { 172 tarWriter.Close() 173 writer.Close() 174 }() 175 176 return NewTarSum(reader, !gzip, Version0) 177 } 178 179 // Test errors on NewTarsumForLabel 180 func TestNewTarSumForLabelInvalid(t *testing.T) { 181 reader := strings.NewReader("") 182 183 if _, err := NewTarSumForLabel(reader, true, "invalidlabel"); err == nil { 184 t.Fatalf("Expected an error, got nothing.") 185 } 186 187 if _, err := NewTarSumForLabel(reader, true, "invalid+sha256"); err == nil { 188 t.Fatalf("Expected an error, got nothing.") 189 } 190 if _, err := NewTarSumForLabel(reader, true, "tarsum.v1+invalid"); err == nil { 191 t.Fatalf("Expected an error, got nothing.") 192 } 193 } 194 195 func TestNewTarSumForLabel(t *testing.T) { 196 197 layer := testLayers[0] 198 199 reader, err := os.Open(layer.filename) 200 if err != nil { 201 t.Fatal(err) 202 } 203 defer reader.Close() 204 205 label := strings.Split(layer.tarsum, ":")[0] 206 ts, err := NewTarSumForLabel(reader, false, label) 207 if err != nil { 208 t.Fatal(err) 209 } 210 211 // Make sure it actually worked by reading a little bit of it 212 nbByteToRead := 8 * 1024 213 dBuf := make([]byte, nbByteToRead) 214 _, err = ts.Read(dBuf) 215 if err != nil { 216 t.Errorf("failed to read %vKB from %s: %s", nbByteToRead, layer.filename, err) 217 } 218 } 219 220 // TestEmptyTar tests that tarsum does not fail to read an empty tar 221 // and correctly returns the hex digest of an empty hash. 222 func TestEmptyTar(t *testing.T) { 223 // Test without gzip. 224 ts, err := emptyTarSum(false) 225 if err != nil { 226 t.Fatal(err) 227 } 228 229 zeroBlock := make([]byte, 1024) 230 buf := new(bytes.Buffer) 231 232 n, err := io.Copy(buf, ts) 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 if n != int64(len(zeroBlock)) || !bytes.Equal(buf.Bytes(), zeroBlock) { 238 t.Fatalf("tarSum did not write the correct number of zeroed bytes: %d", n) 239 } 240 241 expectedSum := ts.Version().String() + "+sha256:" + hex.EncodeToString(sha256.New().Sum(nil)) 242 resultSum := ts.Sum(nil) 243 244 if resultSum != expectedSum { 245 t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum) 246 } 247 248 // Test with gzip. 249 ts, err = emptyTarSum(true) 250 if err != nil { 251 t.Fatal(err) 252 } 253 buf.Reset() 254 255 n, err = io.Copy(buf, ts) 256 if err != nil { 257 t.Fatal(err) 258 } 259 260 bufgz := new(bytes.Buffer) 261 gz := gzip.NewWriter(bufgz) 262 n, err = io.Copy(gz, bytes.NewBuffer(zeroBlock)) 263 gz.Close() 264 gzBytes := bufgz.Bytes() 265 266 if n != int64(len(zeroBlock)) || !bytes.Equal(buf.Bytes(), gzBytes) { 267 t.Fatalf("tarSum did not write the correct number of gzipped-zeroed bytes: %d", n) 268 } 269 270 resultSum = ts.Sum(nil) 271 272 if resultSum != expectedSum { 273 t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum) 274 } 275 276 // Test without ever actually writing anything. 277 if ts, err = NewTarSum(bytes.NewReader([]byte{}), true, Version0); err != nil { 278 t.Fatal(err) 279 } 280 281 resultSum = ts.Sum(nil) 282 283 if resultSum != expectedSum { 284 t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum) 285 } 286 } 287 288 var ( 289 md5THash = NewTHash("md5", md5.New) 290 sha1Hash = NewTHash("sha1", sha1.New) 291 sha224Hash = NewTHash("sha224", sha256.New224) 292 sha384Hash = NewTHash("sha384", sha512.New384) 293 sha512Hash = NewTHash("sha512", sha512.New) 294 ) 295 296 // Test all the build-in read size : buf8K, buf16K, buf32K and more 297 func TestTarSumsReadSize(t *testing.T) { 298 // Test always on the same layer (that is big enough) 299 layer := testLayers[0] 300 301 for i := 0; i < 5; i++ { 302 303 reader, err := os.Open(layer.filename) 304 if err != nil { 305 t.Fatal(err) 306 } 307 defer reader.Close() 308 309 ts, err := NewTarSum(reader, false, layer.version) 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 // Read and discard bytes so that it populates sums 315 nbByteToRead := (i + 1) * 8 * 1024 316 dBuf := make([]byte, nbByteToRead) 317 _, err = ts.Read(dBuf) 318 if err != nil { 319 t.Errorf("failed to read %vKB from %s: %s", nbByteToRead, layer.filename, err) 320 continue 321 } 322 } 323 } 324 325 func TestTarSums(t *testing.T) { 326 for _, layer := range testLayers { 327 var ( 328 fh io.Reader 329 err error 330 ) 331 if len(layer.filename) > 0 { 332 fh, err = os.Open(layer.filename) 333 if err != nil { 334 t.Errorf("failed to open %s: %s", layer.filename, err) 335 continue 336 } 337 } else if layer.options != nil { 338 fh = sizedTar(*layer.options) 339 } else { 340 // What else is there to test? 341 t.Errorf("what to do with %#v", layer) 342 continue 343 } 344 if file, ok := fh.(*os.File); ok { 345 defer file.Close() 346 } 347 348 var ts TarSum 349 if layer.hash == nil { 350 // double negatives! 351 ts, err = NewTarSum(fh, !layer.gzip, layer.version) 352 } else { 353 ts, err = NewTarSumHash(fh, !layer.gzip, layer.version, layer.hash) 354 } 355 if err != nil { 356 t.Errorf("%q :: %q", err, layer.filename) 357 continue 358 } 359 360 // Read variable number of bytes to test dynamic buffer 361 dBuf := make([]byte, 1) 362 _, err = ts.Read(dBuf) 363 if err != nil { 364 t.Errorf("failed to read 1B from %s: %s", layer.filename, err) 365 continue 366 } 367 dBuf = make([]byte, 16*1024) 368 _, err = ts.Read(dBuf) 369 if err != nil { 370 t.Errorf("failed to read 16KB from %s: %s", layer.filename, err) 371 continue 372 } 373 374 // Read and discard remaining bytes 375 _, err = io.Copy(ioutil.Discard, ts) 376 if err != nil { 377 t.Errorf("failed to copy from %s: %s", layer.filename, err) 378 continue 379 } 380 var gotSum string 381 if len(layer.jsonfile) > 0 { 382 jfh, err := os.Open(layer.jsonfile) 383 if err != nil { 384 t.Errorf("failed to open %s: %s", layer.jsonfile, err) 385 continue 386 } 387 defer jfh.Close() 388 389 buf, err := ioutil.ReadAll(jfh) 390 if err != nil { 391 t.Errorf("failed to readAll %s: %s", layer.jsonfile, err) 392 continue 393 } 394 gotSum = ts.Sum(buf) 395 } else { 396 gotSum = ts.Sum(nil) 397 } 398 399 if layer.tarsum != gotSum { 400 t.Errorf("expecting [%s], but got [%s]", layer.tarsum, gotSum) 401 } 402 var expectedHashName string 403 if layer.hash != nil { 404 expectedHashName = layer.hash.Name() 405 } else { 406 expectedHashName = DefaultTHash.Name() 407 } 408 if expectedHashName != ts.Hash().Name() { 409 t.Errorf("expecting hash [%v], but got [%s]", expectedHashName, ts.Hash().Name()) 410 } 411 } 412 } 413 414 func TestIteration(t *testing.T) { 415 headerTests := []struct { 416 expectedSum string // TODO(vbatts) it would be nice to get individual sums of each 417 version Version 418 hdr *tar.Header 419 data []byte 420 }{ 421 { 422 "tarsum+sha256:626c4a2e9a467d65c33ae81f7f3dedd4de8ccaee72af73223c4bc4718cbc7bbd", 423 Version0, 424 &tar.Header{ 425 Name: "file.txt", 426 Size: 0, 427 Typeflag: tar.TypeReg, 428 Devminor: 0, 429 Devmajor: 0, 430 }, 431 []byte(""), 432 }, 433 { 434 "tarsum.dev+sha256:6ffd43a1573a9913325b4918e124ee982a99c0f3cba90fc032a65f5e20bdd465", 435 VersionDev, 436 &tar.Header{ 437 Name: "file.txt", 438 Size: 0, 439 Typeflag: tar.TypeReg, 440 Devminor: 0, 441 Devmajor: 0, 442 }, 443 []byte(""), 444 }, 445 { 446 "tarsum.dev+sha256:b38166c059e11fb77bef30bf16fba7584446e80fcc156ff46d47e36c5305d8ef", 447 VersionDev, 448 &tar.Header{ 449 Name: "another.txt", 450 Uid: 1000, 451 Gid: 1000, 452 Uname: "slartibartfast", 453 Gname: "users", 454 Size: 4, 455 Typeflag: tar.TypeReg, 456 Devminor: 0, 457 Devmajor: 0, 458 }, 459 []byte("test"), 460 }, 461 { 462 "tarsum.dev+sha256:4cc2e71ac5d31833ab2be9b4f7842a14ce595ec96a37af4ed08f87bc374228cd", 463 VersionDev, 464 &tar.Header{ 465 Name: "xattrs.txt", 466 Uid: 1000, 467 Gid: 1000, 468 Uname: "slartibartfast", 469 Gname: "users", 470 Size: 4, 471 Typeflag: tar.TypeReg, 472 Xattrs: map[string]string{ 473 "user.key1": "value1", 474 "user.key2": "value2", 475 }, 476 }, 477 []byte("test"), 478 }, 479 { 480 "tarsum.dev+sha256:65f4284fa32c0d4112dd93c3637697805866415b570587e4fd266af241503760", 481 VersionDev, 482 &tar.Header{ 483 Name: "xattrs.txt", 484 Uid: 1000, 485 Gid: 1000, 486 Uname: "slartibartfast", 487 Gname: "users", 488 Size: 4, 489 Typeflag: tar.TypeReg, 490 Xattrs: map[string]string{ 491 "user.KEY1": "value1", // adding different case to ensure different sum 492 "user.key2": "value2", 493 }, 494 }, 495 []byte("test"), 496 }, 497 { 498 "tarsum+sha256:c12bb6f1303a9ddbf4576c52da74973c00d14c109bcfa76b708d5da1154a07fa", 499 Version0, 500 &tar.Header{ 501 Name: "xattrs.txt", 502 Uid: 1000, 503 Gid: 1000, 504 Uname: "slartibartfast", 505 Gname: "users", 506 Size: 4, 507 Typeflag: tar.TypeReg, 508 Xattrs: map[string]string{ 509 "user.NOT": "CALCULATED", 510 }, 511 }, 512 []byte("test"), 513 }, 514 } 515 for _, htest := range headerTests { 516 s, err := renderSumForHeader(htest.version, htest.hdr, htest.data) 517 if err != nil { 518 t.Fatal(err) 519 } 520 521 if s != htest.expectedSum { 522 t.Errorf("expected sum: %q, got: %q", htest.expectedSum, s) 523 } 524 } 525 526 } 527 528 func renderSumForHeader(v Version, h *tar.Header, data []byte) (string, error) { 529 buf := bytes.NewBuffer(nil) 530 // first build our test tar 531 tw := tar.NewWriter(buf) 532 if err := tw.WriteHeader(h); err != nil { 533 return "", err 534 } 535 if _, err := tw.Write(data); err != nil { 536 return "", err 537 } 538 tw.Close() 539 540 ts, err := NewTarSum(buf, true, v) 541 if err != nil { 542 return "", err 543 } 544 tr := tar.NewReader(ts) 545 for { 546 hdr, err := tr.Next() 547 if hdr == nil || err == io.EOF { 548 // Signals the end of the archive. 549 break 550 } 551 if err != nil { 552 return "", err 553 } 554 if _, err = io.Copy(ioutil.Discard, tr); err != nil { 555 return "", err 556 } 557 } 558 return ts.Sum(nil), nil 559 } 560 561 func Benchmark9kTar(b *testing.B) { 562 buf := bytes.NewBuffer([]byte{}) 563 fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar") 564 if err != nil { 565 b.Error(err) 566 return 567 } 568 defer fh.Close() 569 570 n, err := io.Copy(buf, fh) 571 if err != nil { 572 b.Error(err) 573 return 574 } 575 576 reader := bytes.NewReader(buf.Bytes()) 577 578 b.SetBytes(n) 579 b.ResetTimer() 580 for i := 0; i < b.N; i++ { 581 reader.Seek(0, 0) 582 ts, err := NewTarSum(reader, true, Version0) 583 if err != nil { 584 b.Error(err) 585 return 586 } 587 io.Copy(ioutil.Discard, ts) 588 ts.Sum(nil) 589 } 590 } 591 592 func Benchmark9kTarGzip(b *testing.B) { 593 buf := bytes.NewBuffer([]byte{}) 594 fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar") 595 if err != nil { 596 b.Error(err) 597 return 598 } 599 defer fh.Close() 600 601 n, err := io.Copy(buf, fh) 602 if err != nil { 603 b.Error(err) 604 return 605 } 606 607 reader := bytes.NewReader(buf.Bytes()) 608 609 b.SetBytes(n) 610 b.ResetTimer() 611 for i := 0; i < b.N; i++ { 612 reader.Seek(0, 0) 613 ts, err := NewTarSum(reader, false, Version0) 614 if err != nil { 615 b.Error(err) 616 return 617 } 618 io.Copy(ioutil.Discard, ts) 619 ts.Sum(nil) 620 } 621 } 622 623 // this is a single big file in the tar archive 624 func Benchmark1mbSingleFileTar(b *testing.B) { 625 benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, false) 626 } 627 628 // this is a single big file in the tar archive 629 func Benchmark1mbSingleFileTarGzip(b *testing.B) { 630 benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, true) 631 } 632 633 // this is 1024 1k files in the tar archive 634 func Benchmark1kFilesTar(b *testing.B) { 635 benchmarkTar(b, sizedOptions{1024, 1024, true, true}, false) 636 } 637 638 // this is 1024 1k files in the tar archive 639 func Benchmark1kFilesTarGzip(b *testing.B) { 640 benchmarkTar(b, sizedOptions{1024, 1024, true, true}, true) 641 } 642 643 func benchmarkTar(b *testing.B, opts sizedOptions, isGzip bool) { 644 var fh *os.File 645 tarReader := sizedTar(opts) 646 if br, ok := tarReader.(*os.File); ok { 647 fh = br 648 } 649 defer os.Remove(fh.Name()) 650 defer fh.Close() 651 652 b.SetBytes(opts.size * opts.num) 653 b.ResetTimer() 654 for i := 0; i < b.N; i++ { 655 ts, err := NewTarSum(fh, !isGzip, Version0) 656 if err != nil { 657 b.Error(err) 658 return 659 } 660 io.Copy(ioutil.Discard, ts) 661 ts.Sum(nil) 662 fh.Seek(0, 0) 663 } 664 }