github.com/christopherobin/docker@v1.6.2/pkg/tarsum/tarsum_test.go (about) 1 package tarsum 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "crypto/md5" 7 "crypto/rand" 8 "crypto/sha1" 9 "crypto/sha256" 10 "crypto/sha512" 11 "encoding/hex" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "os" 16 "testing" 17 18 "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" 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:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, 37 { 38 filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", 39 jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", 40 version: VersionDev, 41 tarsum: "tarsum.dev+sha256:486b86e25c4db4551228154848bc4663b15dd95784b1588980f4ba1cb42e83e9"}, 42 { 43 filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", 44 jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", 45 gzip: true, 46 tarsum: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, 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:e86f81a4d552f13039b1396ed03ca968ea9717581f9577ef1876ea6ff9b38c98"}, 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:6235cd3a2afb7501bac541772a3d61a3634e95bc90bb39a4676e2cb98d08390d"}, 59 { 60 filename: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar", 61 jsonfile: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json", 62 tarsum: "tarsum+sha256:ac672ee85da9ab7f9667ae3c32841d3e42f33cc52c273c23341dabba1c8b0c8b"}, 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 // TestEmptyTar tests that tarsum does not fail to read an empty tar 180 // and correctly returns the hex digest of an empty hash. 181 func TestEmptyTar(t *testing.T) { 182 // Test without gzip. 183 ts, err := emptyTarSum(false) 184 if err != nil { 185 t.Fatal(err) 186 } 187 188 zeroBlock := make([]byte, 1024) 189 buf := new(bytes.Buffer) 190 191 n, err := io.Copy(buf, ts) 192 if err != nil { 193 t.Fatal(err) 194 } 195 196 if n != int64(len(zeroBlock)) || !bytes.Equal(buf.Bytes(), zeroBlock) { 197 t.Fatalf("tarSum did not write the correct number of zeroed bytes: %d", n) 198 } 199 200 expectedSum := ts.Version().String() + "+sha256:" + hex.EncodeToString(sha256.New().Sum(nil)) 201 resultSum := ts.Sum(nil) 202 203 if resultSum != expectedSum { 204 t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum) 205 } 206 207 // Test with gzip. 208 ts, err = emptyTarSum(true) 209 if err != nil { 210 t.Fatal(err) 211 } 212 buf.Reset() 213 214 n, err = io.Copy(buf, ts) 215 if err != nil { 216 t.Fatal(err) 217 } 218 219 bufgz := new(bytes.Buffer) 220 gz := gzip.NewWriter(bufgz) 221 n, err = io.Copy(gz, bytes.NewBuffer(zeroBlock)) 222 gz.Close() 223 gzBytes := bufgz.Bytes() 224 225 if n != int64(len(zeroBlock)) || !bytes.Equal(buf.Bytes(), gzBytes) { 226 t.Fatalf("tarSum did not write the correct number of gzipped-zeroed bytes: %d", n) 227 } 228 229 resultSum = ts.Sum(nil) 230 231 if resultSum != expectedSum { 232 t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum) 233 } 234 235 // Test without ever actually writing anything. 236 if ts, err = NewTarSum(bytes.NewReader([]byte{}), true, Version0); err != nil { 237 t.Fatal(err) 238 } 239 240 resultSum = ts.Sum(nil) 241 242 if resultSum != expectedSum { 243 t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum) 244 } 245 } 246 247 var ( 248 md5THash = NewTHash("md5", md5.New) 249 sha1Hash = NewTHash("sha1", sha1.New) 250 sha224Hash = NewTHash("sha224", sha256.New224) 251 sha384Hash = NewTHash("sha384", sha512.New384) 252 sha512Hash = NewTHash("sha512", sha512.New) 253 ) 254 255 func TestTarSums(t *testing.T) { 256 for _, layer := range testLayers { 257 var ( 258 fh io.Reader 259 err error 260 ) 261 if len(layer.filename) > 0 { 262 fh, err = os.Open(layer.filename) 263 if err != nil { 264 t.Errorf("failed to open %s: %s", layer.filename, err) 265 continue 266 } 267 } else if layer.options != nil { 268 fh = sizedTar(*layer.options) 269 } else { 270 // What else is there to test? 271 t.Errorf("what to do with %#v", layer) 272 continue 273 } 274 if file, ok := fh.(*os.File); ok { 275 defer file.Close() 276 } 277 278 var ts TarSum 279 if layer.hash == nil { 280 // double negatives! 281 ts, err = NewTarSum(fh, !layer.gzip, layer.version) 282 } else { 283 ts, err = NewTarSumHash(fh, !layer.gzip, layer.version, layer.hash) 284 } 285 if err != nil { 286 t.Errorf("%q :: %q", err, layer.filename) 287 continue 288 } 289 290 // Read variable number of bytes to test dynamic buffer 291 dBuf := make([]byte, 1) 292 _, err = ts.Read(dBuf) 293 if err != nil { 294 t.Errorf("failed to read 1B from %s: %s", layer.filename, err) 295 continue 296 } 297 dBuf = make([]byte, 16*1024) 298 _, err = ts.Read(dBuf) 299 if err != nil { 300 t.Errorf("failed to read 16KB from %s: %s", layer.filename, err) 301 continue 302 } 303 304 // Read and discard remaining bytes 305 _, err = io.Copy(ioutil.Discard, ts) 306 if err != nil { 307 t.Errorf("failed to copy from %s: %s", layer.filename, err) 308 continue 309 } 310 var gotSum string 311 if len(layer.jsonfile) > 0 { 312 jfh, err := os.Open(layer.jsonfile) 313 if err != nil { 314 t.Errorf("failed to open %s: %s", layer.jsonfile, err) 315 continue 316 } 317 buf, err := ioutil.ReadAll(jfh) 318 if err != nil { 319 t.Errorf("failed to readAll %s: %s", layer.jsonfile, err) 320 continue 321 } 322 gotSum = ts.Sum(buf) 323 } else { 324 gotSum = ts.Sum(nil) 325 } 326 327 if layer.tarsum != gotSum { 328 t.Errorf("expecting [%s], but got [%s]", layer.tarsum, gotSum) 329 } 330 } 331 } 332 333 func TestIteration(t *testing.T) { 334 headerTests := []struct { 335 expectedSum string // TODO(vbatts) it would be nice to get individual sums of each 336 version Version 337 hdr *tar.Header 338 data []byte 339 }{ 340 { 341 "tarsum+sha256:626c4a2e9a467d65c33ae81f7f3dedd4de8ccaee72af73223c4bc4718cbc7bbd", 342 Version0, 343 &tar.Header{ 344 Name: "file.txt", 345 Size: 0, 346 Typeflag: tar.TypeReg, 347 Devminor: 0, 348 Devmajor: 0, 349 }, 350 []byte(""), 351 }, 352 { 353 "tarsum.dev+sha256:6ffd43a1573a9913325b4918e124ee982a99c0f3cba90fc032a65f5e20bdd465", 354 VersionDev, 355 &tar.Header{ 356 Name: "file.txt", 357 Size: 0, 358 Typeflag: tar.TypeReg, 359 Devminor: 0, 360 Devmajor: 0, 361 }, 362 []byte(""), 363 }, 364 { 365 "tarsum.dev+sha256:b38166c059e11fb77bef30bf16fba7584446e80fcc156ff46d47e36c5305d8ef", 366 VersionDev, 367 &tar.Header{ 368 Name: "another.txt", 369 Uid: 1000, 370 Gid: 1000, 371 Uname: "slartibartfast", 372 Gname: "users", 373 Size: 4, 374 Typeflag: tar.TypeReg, 375 Devminor: 0, 376 Devmajor: 0, 377 }, 378 []byte("test"), 379 }, 380 { 381 "tarsum.dev+sha256:4cc2e71ac5d31833ab2be9b4f7842a14ce595ec96a37af4ed08f87bc374228cd", 382 VersionDev, 383 &tar.Header{ 384 Name: "xattrs.txt", 385 Uid: 1000, 386 Gid: 1000, 387 Uname: "slartibartfast", 388 Gname: "users", 389 Size: 4, 390 Typeflag: tar.TypeReg, 391 Xattrs: map[string]string{ 392 "user.key1": "value1", 393 "user.key2": "value2", 394 }, 395 }, 396 []byte("test"), 397 }, 398 { 399 "tarsum.dev+sha256:65f4284fa32c0d4112dd93c3637697805866415b570587e4fd266af241503760", 400 VersionDev, 401 &tar.Header{ 402 Name: "xattrs.txt", 403 Uid: 1000, 404 Gid: 1000, 405 Uname: "slartibartfast", 406 Gname: "users", 407 Size: 4, 408 Typeflag: tar.TypeReg, 409 Xattrs: map[string]string{ 410 "user.KEY1": "value1", // adding different case to ensure different sum 411 "user.key2": "value2", 412 }, 413 }, 414 []byte("test"), 415 }, 416 { 417 "tarsum+sha256:c12bb6f1303a9ddbf4576c52da74973c00d14c109bcfa76b708d5da1154a07fa", 418 Version0, 419 &tar.Header{ 420 Name: "xattrs.txt", 421 Uid: 1000, 422 Gid: 1000, 423 Uname: "slartibartfast", 424 Gname: "users", 425 Size: 4, 426 Typeflag: tar.TypeReg, 427 Xattrs: map[string]string{ 428 "user.NOT": "CALCULATED", 429 }, 430 }, 431 []byte("test"), 432 }, 433 } 434 for _, htest := range headerTests { 435 s, err := renderSumForHeader(htest.version, htest.hdr, htest.data) 436 if err != nil { 437 t.Fatal(err) 438 } 439 440 if s != htest.expectedSum { 441 t.Errorf("expected sum: %q, got: %q", htest.expectedSum, s) 442 } 443 } 444 445 } 446 447 func renderSumForHeader(v Version, h *tar.Header, data []byte) (string, error) { 448 buf := bytes.NewBuffer(nil) 449 // first build our test tar 450 tw := tar.NewWriter(buf) 451 if err := tw.WriteHeader(h); err != nil { 452 return "", err 453 } 454 if _, err := tw.Write(data); err != nil { 455 return "", err 456 } 457 tw.Close() 458 459 ts, err := NewTarSum(buf, true, v) 460 if err != nil { 461 return "", err 462 } 463 tr := tar.NewReader(ts) 464 for { 465 hdr, err := tr.Next() 466 if hdr == nil || err == io.EOF { 467 // Signals the end of the archive. 468 break 469 } 470 if err != nil { 471 return "", err 472 } 473 if _, err = io.Copy(ioutil.Discard, tr); err != nil { 474 return "", err 475 } 476 } 477 return ts.Sum(nil), nil 478 } 479 480 func Benchmark9kTar(b *testing.B) { 481 buf := bytes.NewBuffer([]byte{}) 482 fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar") 483 if err != nil { 484 b.Error(err) 485 return 486 } 487 n, err := io.Copy(buf, fh) 488 fh.Close() 489 490 reader := bytes.NewReader(buf.Bytes()) 491 492 b.SetBytes(n) 493 b.ResetTimer() 494 for i := 0; i < b.N; i++ { 495 reader.Seek(0, 0) 496 ts, err := NewTarSum(reader, true, Version0) 497 if err != nil { 498 b.Error(err) 499 return 500 } 501 io.Copy(ioutil.Discard, ts) 502 ts.Sum(nil) 503 } 504 } 505 506 func Benchmark9kTarGzip(b *testing.B) { 507 buf := bytes.NewBuffer([]byte{}) 508 fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar") 509 if err != nil { 510 b.Error(err) 511 return 512 } 513 n, err := io.Copy(buf, fh) 514 fh.Close() 515 516 reader := bytes.NewReader(buf.Bytes()) 517 518 b.SetBytes(n) 519 b.ResetTimer() 520 for i := 0; i < b.N; i++ { 521 reader.Seek(0, 0) 522 ts, err := NewTarSum(reader, false, Version0) 523 if err != nil { 524 b.Error(err) 525 return 526 } 527 io.Copy(ioutil.Discard, ts) 528 ts.Sum(nil) 529 } 530 } 531 532 // this is a single big file in the tar archive 533 func Benchmark1mbSingleFileTar(b *testing.B) { 534 benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, false) 535 } 536 537 // this is a single big file in the tar archive 538 func Benchmark1mbSingleFileTarGzip(b *testing.B) { 539 benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, true) 540 } 541 542 // this is 1024 1k files in the tar archive 543 func Benchmark1kFilesTar(b *testing.B) { 544 benchmarkTar(b, sizedOptions{1024, 1024, true, true}, false) 545 } 546 547 // this is 1024 1k files in the tar archive 548 func Benchmark1kFilesTarGzip(b *testing.B) { 549 benchmarkTar(b, sizedOptions{1024, 1024, true, true}, true) 550 } 551 552 func benchmarkTar(b *testing.B, opts sizedOptions, isGzip bool) { 553 var fh *os.File 554 tarReader := sizedTar(opts) 555 if br, ok := tarReader.(*os.File); ok { 556 fh = br 557 } 558 defer os.Remove(fh.Name()) 559 defer fh.Close() 560 561 b.SetBytes(opts.size * opts.num) 562 b.ResetTimer() 563 for i := 0; i < b.N; i++ { 564 ts, err := NewTarSum(fh, !isGzip, Version0) 565 if err != nil { 566 b.Error(err) 567 return 568 } 569 io.Copy(ioutil.Discard, ts) 570 ts.Sum(nil) 571 fh.Seek(0, 0) 572 } 573 }