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