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