github.com/jingleWang/moby@v1.13.1/layer/layer_test.go (about) 1 package layer 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "testing" 12 13 "github.com/docker/distribution/digest" 14 "github.com/docker/docker/daemon/graphdriver" 15 "github.com/docker/docker/daemon/graphdriver/vfs" 16 "github.com/docker/docker/pkg/archive" 17 "github.com/docker/docker/pkg/idtools" 18 "github.com/docker/docker/pkg/stringid" 19 ) 20 21 func init() { 22 graphdriver.ApplyUncompressedLayer = archive.UnpackLayer 23 vfs.CopyWithTar = archive.CopyWithTar 24 } 25 26 func newVFSGraphDriver(td string) (graphdriver.Driver, error) { 27 uidMap := []idtools.IDMap{ 28 { 29 ContainerID: 0, 30 HostID: os.Getuid(), 31 Size: 1, 32 }, 33 } 34 gidMap := []idtools.IDMap{ 35 { 36 ContainerID: 0, 37 HostID: os.Getgid(), 38 Size: 1, 39 }, 40 } 41 42 options := graphdriver.Options{Root: td, UIDMaps: uidMap, GIDMaps: gidMap} 43 return graphdriver.GetDriver("vfs", nil, options) 44 } 45 46 func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) { 47 td, err := ioutil.TempDir("", "graph-") 48 if err != nil { 49 t.Fatal(err) 50 } 51 52 driver, err := newVFSGraphDriver(td) 53 if err != nil { 54 t.Fatal(err) 55 } 56 57 return driver, func() { 58 os.RemoveAll(td) 59 } 60 } 61 62 func newTestStore(t *testing.T) (Store, string, func()) { 63 td, err := ioutil.TempDir("", "layerstore-") 64 if err != nil { 65 t.Fatal(err) 66 } 67 68 graph, graphcleanup := newTestGraphDriver(t) 69 fms, err := NewFSMetadataStore(td) 70 if err != nil { 71 t.Fatal(err) 72 } 73 ls, err := NewStoreFromGraphDriver(fms, graph) 74 if err != nil { 75 t.Fatal(err) 76 } 77 78 return ls, td, func() { 79 graphcleanup() 80 os.RemoveAll(td) 81 } 82 } 83 84 type layerInit func(root string) error 85 86 func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) { 87 containerID := stringid.GenerateRandomID() 88 mount, err := ls.CreateRWLayer(containerID, parent, "", nil, nil) 89 if err != nil { 90 return nil, err 91 } 92 93 path, err := mount.Mount("") 94 if err != nil { 95 return nil, err 96 } 97 98 if err := layerFunc(path); err != nil { 99 return nil, err 100 } 101 102 ts, err := mount.TarStream() 103 if err != nil { 104 return nil, err 105 } 106 defer ts.Close() 107 108 layer, err := ls.Register(ts, parent) 109 if err != nil { 110 return nil, err 111 } 112 113 if err := mount.Unmount(); err != nil { 114 return nil, err 115 } 116 117 if _, err := ls.ReleaseRWLayer(mount); err != nil { 118 return nil, err 119 } 120 121 return layer, nil 122 } 123 124 type FileApplier interface { 125 ApplyFile(root string) error 126 } 127 128 type testFile struct { 129 name string 130 content []byte 131 permission os.FileMode 132 } 133 134 func newTestFile(name string, content []byte, perm os.FileMode) FileApplier { 135 return &testFile{ 136 name: name, 137 content: content, 138 permission: perm, 139 } 140 } 141 142 func (tf *testFile) ApplyFile(root string) error { 143 fullPath := filepath.Join(root, tf.name) 144 if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { 145 return err 146 } 147 // Check if already exists 148 if stat, err := os.Stat(fullPath); err == nil && stat.Mode().Perm() != tf.permission { 149 if err := os.Chmod(fullPath, tf.permission); err != nil { 150 return err 151 } 152 } 153 if err := ioutil.WriteFile(fullPath, tf.content, tf.permission); err != nil { 154 return err 155 } 156 return nil 157 } 158 159 func initWithFiles(files ...FileApplier) layerInit { 160 return func(root string) error { 161 for _, f := range files { 162 if err := f.ApplyFile(root); err != nil { 163 return err 164 } 165 } 166 return nil 167 } 168 } 169 170 func getCachedLayer(l Layer) *roLayer { 171 if rl, ok := l.(*referencedCacheLayer); ok { 172 return rl.roLayer 173 } 174 return l.(*roLayer) 175 } 176 177 func getMountLayer(l RWLayer) *mountedLayer { 178 return l.(*referencedRWLayer).mountedLayer 179 } 180 181 func createMetadata(layers ...Layer) []Metadata { 182 metadata := make([]Metadata, len(layers)) 183 for i := range layers { 184 size, err := layers[i].Size() 185 if err != nil { 186 panic(err) 187 } 188 189 metadata[i].ChainID = layers[i].ChainID() 190 metadata[i].DiffID = layers[i].DiffID() 191 metadata[i].Size = size 192 metadata[i].DiffSize = getCachedLayer(layers[i]).size 193 } 194 195 return metadata 196 } 197 198 func assertMetadata(t *testing.T, metadata, expectedMetadata []Metadata) { 199 if len(metadata) != len(expectedMetadata) { 200 t.Fatalf("Unexpected number of deletes %d, expected %d", len(metadata), len(expectedMetadata)) 201 } 202 203 for i := range metadata { 204 if metadata[i] != expectedMetadata[i] { 205 t.Errorf("Unexpected metadata\n\tExpected: %#v\n\tActual: %#v", expectedMetadata[i], metadata[i]) 206 } 207 } 208 if t.Failed() { 209 t.FailNow() 210 } 211 } 212 213 func releaseAndCheckDeleted(t *testing.T, ls Store, layer Layer, removed ...Layer) { 214 layerCount := len(ls.(*layerStore).layerMap) 215 expectedMetadata := createMetadata(removed...) 216 metadata, err := ls.Release(layer) 217 if err != nil { 218 t.Fatal(err) 219 } 220 221 assertMetadata(t, metadata, expectedMetadata) 222 223 if expected := layerCount - len(removed); len(ls.(*layerStore).layerMap) != expected { 224 t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected) 225 } 226 } 227 228 func cacheID(l Layer) string { 229 return getCachedLayer(l).cacheID 230 } 231 232 func assertLayerEqual(t *testing.T, l1, l2 Layer) { 233 if l1.ChainID() != l2.ChainID() { 234 t.Fatalf("Mismatched ID: %s vs %s", l1.ChainID(), l2.ChainID()) 235 } 236 if l1.DiffID() != l2.DiffID() { 237 t.Fatalf("Mismatched DiffID: %s vs %s", l1.DiffID(), l2.DiffID()) 238 } 239 240 size1, err := l1.Size() 241 if err != nil { 242 t.Fatal(err) 243 } 244 245 size2, err := l2.Size() 246 if err != nil { 247 t.Fatal(err) 248 } 249 250 if size1 != size2 { 251 t.Fatalf("Mismatched size: %d vs %d", size1, size2) 252 } 253 254 if cacheID(l1) != cacheID(l2) { 255 t.Fatalf("Mismatched cache id: %s vs %s", cacheID(l1), cacheID(l2)) 256 } 257 258 p1 := l1.Parent() 259 p2 := l2.Parent() 260 if p1 != nil && p2 != nil { 261 assertLayerEqual(t, p1, p2) 262 } else if p1 != nil || p2 != nil { 263 t.Fatalf("Mismatched parents: %v vs %v", p1, p2) 264 } 265 } 266 267 func TestMountAndRegister(t *testing.T) { 268 ls, _, cleanup := newTestStore(t) 269 defer cleanup() 270 271 li := initWithFiles(newTestFile("testfile.txt", []byte("some test data"), 0644)) 272 layer, err := createLayer(ls, "", li) 273 if err != nil { 274 t.Fatal(err) 275 } 276 277 size, _ := layer.Size() 278 t.Logf("Layer size: %d", size) 279 280 mount2, err := ls.CreateRWLayer("new-test-mount", layer.ChainID(), "", nil, nil) 281 if err != nil { 282 t.Fatal(err) 283 } 284 285 path2, err := mount2.Mount("") 286 if err != nil { 287 t.Fatal(err) 288 } 289 290 b, err := ioutil.ReadFile(filepath.Join(path2, "testfile.txt")) 291 if err != nil { 292 t.Fatal(err) 293 } 294 295 if expected := "some test data"; string(b) != expected { 296 t.Fatalf("Wrong file data, expected %q, got %q", expected, string(b)) 297 } 298 299 if err := mount2.Unmount(); err != nil { 300 t.Fatal(err) 301 } 302 303 if _, err := ls.ReleaseRWLayer(mount2); err != nil { 304 t.Fatal(err) 305 } 306 } 307 308 func TestLayerRelease(t *testing.T) { 309 // TODO Windows: Figure out why this is failing 310 if runtime.GOOS == "windows" { 311 t.Skip("Failing on Windows") 312 } 313 ls, _, cleanup := newTestStore(t) 314 defer cleanup() 315 316 layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644))) 317 if err != nil { 318 t.Fatal(err) 319 } 320 321 layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644))) 322 if err != nil { 323 t.Fatal(err) 324 } 325 326 if _, err := ls.Release(layer1); err != nil { 327 t.Fatal(err) 328 } 329 330 layer3a, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3a file"), 0644))) 331 if err != nil { 332 t.Fatal(err) 333 } 334 335 layer3b, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3b file"), 0644))) 336 if err != nil { 337 t.Fatal(err) 338 } 339 340 if _, err := ls.Release(layer2); err != nil { 341 t.Fatal(err) 342 } 343 344 t.Logf("Layer1: %s", layer1.ChainID()) 345 t.Logf("Layer2: %s", layer2.ChainID()) 346 t.Logf("Layer3a: %s", layer3a.ChainID()) 347 t.Logf("Layer3b: %s", layer3b.ChainID()) 348 349 if expected := 4; len(ls.(*layerStore).layerMap) != expected { 350 t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected) 351 } 352 353 releaseAndCheckDeleted(t, ls, layer3b, layer3b) 354 releaseAndCheckDeleted(t, ls, layer3a, layer3a, layer2, layer1) 355 } 356 357 func TestStoreRestore(t *testing.T) { 358 // TODO Windows: Figure out why this is failing 359 if runtime.GOOS == "windows" { 360 t.Skip("Failing on Windows") 361 } 362 ls, _, cleanup := newTestStore(t) 363 defer cleanup() 364 365 layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644))) 366 if err != nil { 367 t.Fatal(err) 368 } 369 370 layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644))) 371 if err != nil { 372 t.Fatal(err) 373 } 374 375 if _, err := ls.Release(layer1); err != nil { 376 t.Fatal(err) 377 } 378 379 layer3, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3 file"), 0644))) 380 if err != nil { 381 t.Fatal(err) 382 } 383 384 if _, err := ls.Release(layer2); err != nil { 385 t.Fatal(err) 386 } 387 388 m, err := ls.CreateRWLayer("some-mount_name", layer3.ChainID(), "", nil, nil) 389 if err != nil { 390 t.Fatal(err) 391 } 392 393 path, err := m.Mount("") 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 if err := ioutil.WriteFile(filepath.Join(path, "testfile.txt"), []byte("nothing here"), 0644); err != nil { 399 t.Fatal(err) 400 } 401 402 if err := m.Unmount(); err != nil { 403 t.Fatal(err) 404 } 405 406 ls2, err := NewStoreFromGraphDriver(ls.(*layerStore).store, ls.(*layerStore).driver) 407 if err != nil { 408 t.Fatal(err) 409 } 410 411 layer3b, err := ls2.Get(layer3.ChainID()) 412 if err != nil { 413 t.Fatal(err) 414 } 415 416 assertLayerEqual(t, layer3b, layer3) 417 418 // Create again with same name, should return error 419 if _, err := ls2.CreateRWLayer("some-mount_name", layer3b.ChainID(), "", nil, nil); err == nil { 420 t.Fatal("Expected error creating mount with same name") 421 } else if err != ErrMountNameConflict { 422 t.Fatal(err) 423 } 424 425 m2, err := ls2.GetRWLayer("some-mount_name") 426 if err != nil { 427 t.Fatal(err) 428 } 429 430 if mountPath, err := m2.Mount(""); err != nil { 431 t.Fatal(err) 432 } else if path != mountPath { 433 t.Fatalf("Unexpected path %s, expected %s", mountPath, path) 434 } 435 436 if mountPath, err := m2.Mount(""); err != nil { 437 t.Fatal(err) 438 } else if path != mountPath { 439 t.Fatalf("Unexpected path %s, expected %s", mountPath, path) 440 } 441 if err := m2.Unmount(); err != nil { 442 t.Fatal(err) 443 } 444 445 b, err := ioutil.ReadFile(filepath.Join(path, "testfile.txt")) 446 if err != nil { 447 t.Fatal(err) 448 } 449 if expected := "nothing here"; string(b) != expected { 450 t.Fatalf("Unexpected content %q, expected %q", string(b), expected) 451 } 452 453 if err := m2.Unmount(); err != nil { 454 t.Fatal(err) 455 } 456 457 if metadata, err := ls2.ReleaseRWLayer(m2); err != nil { 458 t.Fatal(err) 459 } else if len(metadata) != 0 { 460 t.Fatalf("Unexpectedly deleted layers: %#v", metadata) 461 } 462 463 if metadata, err := ls2.ReleaseRWLayer(m2); err != nil { 464 t.Fatal(err) 465 } else if len(metadata) != 0 { 466 t.Fatalf("Unexpectedly deleted layers: %#v", metadata) 467 } 468 469 releaseAndCheckDeleted(t, ls2, layer3b, layer3, layer2, layer1) 470 } 471 472 func TestTarStreamStability(t *testing.T) { 473 // TODO Windows: Figure out why this is failing 474 if runtime.GOOS == "windows" { 475 t.Skip("Failing on Windows") 476 } 477 ls, _, cleanup := newTestStore(t) 478 defer cleanup() 479 480 files1 := []FileApplier{ 481 newTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644), 482 newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644), 483 } 484 addedFile := newTestFile("/etc/shadow", []byte("root:::::::"), 0644) 485 files2 := []FileApplier{ 486 newTestFile("/etc/hosts", []byte("mydomain 10.0.0.2"), 0644), 487 newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0664), 488 newTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644), 489 } 490 491 tar1, err := tarFromFiles(files1...) 492 if err != nil { 493 t.Fatal(err) 494 } 495 496 tar2, err := tarFromFiles(files2...) 497 if err != nil { 498 t.Fatal(err) 499 } 500 501 layer1, err := ls.Register(bytes.NewReader(tar1), "") 502 if err != nil { 503 t.Fatal(err) 504 } 505 506 // hack layer to add file 507 p, err := ls.(*layerStore).driver.Get(layer1.(*referencedCacheLayer).cacheID, "") 508 if err != nil { 509 t.Fatal(err) 510 } 511 512 if err := addedFile.ApplyFile(p); err != nil { 513 t.Fatal(err) 514 } 515 516 if err := ls.(*layerStore).driver.Put(layer1.(*referencedCacheLayer).cacheID); err != nil { 517 t.Fatal(err) 518 } 519 520 layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID()) 521 if err != nil { 522 t.Fatal(err) 523 } 524 525 id1 := layer1.ChainID() 526 t.Logf("Layer 1: %s", layer1.ChainID()) 527 t.Logf("Layer 2: %s", layer2.ChainID()) 528 529 if _, err := ls.Release(layer1); err != nil { 530 t.Fatal(err) 531 } 532 533 assertLayerDiff(t, tar2, layer2) 534 535 layer1b, err := ls.Get(id1) 536 if err != nil { 537 t.Logf("Content of layer map: %#v", ls.(*layerStore).layerMap) 538 t.Fatal(err) 539 } 540 541 if _, err := ls.Release(layer2); err != nil { 542 t.Fatal(err) 543 } 544 545 assertLayerDiff(t, tar1, layer1b) 546 547 if _, err := ls.Release(layer1b); err != nil { 548 t.Fatal(err) 549 } 550 } 551 552 func assertLayerDiff(t *testing.T, expected []byte, layer Layer) { 553 expectedDigest := digest.FromBytes(expected) 554 555 if digest.Digest(layer.DiffID()) != expectedDigest { 556 t.Fatalf("Mismatched diff id for %s, got %s, expected %s", layer.ChainID(), layer.DiffID(), expected) 557 } 558 559 ts, err := layer.TarStream() 560 if err != nil { 561 t.Fatal(err) 562 } 563 defer ts.Close() 564 565 actual, err := ioutil.ReadAll(ts) 566 if err != nil { 567 t.Fatal(err) 568 } 569 570 if len(actual) != len(expected) { 571 logByteDiff(t, actual, expected) 572 t.Fatalf("Mismatched tar stream size for %s, got %d, expected %d", layer.ChainID(), len(actual), len(expected)) 573 } 574 575 actualDigest := digest.FromBytes(actual) 576 577 if actualDigest != expectedDigest { 578 logByteDiff(t, actual, expected) 579 t.Fatalf("Wrong digest of tar stream, got %s, expected %s", actualDigest, expectedDigest) 580 } 581 } 582 583 const maxByteLog = 4 * 1024 584 585 func logByteDiff(t *testing.T, actual, expected []byte) { 586 d1, d2 := byteDiff(actual, expected) 587 if len(d1) == 0 && len(d2) == 0 { 588 return 589 } 590 591 prefix := len(actual) - len(d1) 592 if len(d1) > maxByteLog || len(d2) > maxByteLog { 593 t.Logf("Byte diff after %d matching bytes", prefix) 594 } else { 595 t.Logf("Byte diff after %d matching bytes\nActual bytes after prefix:\n%x\nExpected bytes after prefix:\n%x", prefix, d1, d2) 596 } 597 } 598 599 // byteDiff returns the differing bytes after the matching prefix 600 func byteDiff(b1, b2 []byte) ([]byte, []byte) { 601 i := 0 602 for i < len(b1) && i < len(b2) { 603 if b1[i] != b2[i] { 604 break 605 } 606 i++ 607 } 608 609 return b1[i:], b2[i:] 610 } 611 612 func tarFromFiles(files ...FileApplier) ([]byte, error) { 613 td, err := ioutil.TempDir("", "tar-") 614 if err != nil { 615 return nil, err 616 } 617 defer os.RemoveAll(td) 618 619 for _, f := range files { 620 if err := f.ApplyFile(td); err != nil { 621 return nil, err 622 } 623 } 624 625 r, err := archive.Tar(td, archive.Uncompressed) 626 if err != nil { 627 return nil, err 628 } 629 630 buf := bytes.NewBuffer(nil) 631 if _, err := io.Copy(buf, r); err != nil { 632 return nil, err 633 } 634 635 return buf.Bytes(), nil 636 } 637 638 // assertReferences asserts that all the references are to the same 639 // image and represent the full set of references to that image. 640 func assertReferences(t *testing.T, references ...Layer) { 641 if len(references) == 0 { 642 return 643 } 644 base := references[0].(*referencedCacheLayer).roLayer 645 seenReferences := map[Layer]struct{}{ 646 references[0]: {}, 647 } 648 for i := 1; i < len(references); i++ { 649 other := references[i].(*referencedCacheLayer).roLayer 650 if base != other { 651 t.Fatalf("Unexpected referenced cache layer %s, expecting %s", other.ChainID(), base.ChainID()) 652 } 653 if _, ok := base.references[references[i]]; !ok { 654 t.Fatalf("Reference not part of reference list: %v", references[i]) 655 } 656 if _, ok := seenReferences[references[i]]; ok { 657 t.Fatalf("Duplicated reference %v", references[i]) 658 } 659 } 660 if rc := len(base.references); rc != len(references) { 661 t.Fatalf("Unexpected number of references %d, expecting %d", rc, len(references)) 662 } 663 } 664 665 func TestRegisterExistingLayer(t *testing.T) { 666 ls, _, cleanup := newTestStore(t) 667 defer cleanup() 668 669 baseFiles := []FileApplier{ 670 newTestFile("/etc/profile", []byte("# Base configuration"), 0644), 671 } 672 673 layerFiles := []FileApplier{ 674 newTestFile("/root/.bashrc", []byte("# Root configuration"), 0644), 675 } 676 677 li := initWithFiles(baseFiles...) 678 layer1, err := createLayer(ls, "", li) 679 if err != nil { 680 t.Fatal(err) 681 } 682 683 tar1, err := tarFromFiles(layerFiles...) 684 if err != nil { 685 t.Fatal(err) 686 } 687 688 layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID()) 689 if err != nil { 690 t.Fatal(err) 691 } 692 693 layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID()) 694 if err != nil { 695 t.Fatal(err) 696 } 697 698 assertReferences(t, layer2a, layer2b) 699 } 700 701 func TestTarStreamVerification(t *testing.T) { 702 // TODO Windows: Figure out why this is failing 703 if runtime.GOOS == "windows" { 704 t.Skip("Failing on Windows") 705 } 706 ls, tmpdir, cleanup := newTestStore(t) 707 defer cleanup() 708 709 files1 := []FileApplier{ 710 newTestFile("/foo", []byte("abc"), 0644), 711 newTestFile("/bar", []byte("def"), 0644), 712 } 713 files2 := []FileApplier{ 714 newTestFile("/foo", []byte("abc"), 0644), 715 newTestFile("/bar", []byte("def"), 0600), // different perm 716 } 717 718 tar1, err := tarFromFiles(files1...) 719 if err != nil { 720 t.Fatal(err) 721 } 722 723 tar2, err := tarFromFiles(files2...) 724 if err != nil { 725 t.Fatal(err) 726 } 727 728 layer1, err := ls.Register(bytes.NewReader(tar1), "") 729 if err != nil { 730 t.Fatal(err) 731 } 732 733 layer2, err := ls.Register(bytes.NewReader(tar2), "") 734 if err != nil { 735 t.Fatal(err) 736 } 737 id1 := digest.Digest(layer1.ChainID()) 738 id2 := digest.Digest(layer2.ChainID()) 739 740 // Replace tar data files 741 src, err := os.Open(filepath.Join(tmpdir, id1.Algorithm().String(), id1.Hex(), "tar-split.json.gz")) 742 if err != nil { 743 t.Fatal(err) 744 } 745 defer src.Close() 746 747 dst, err := os.Create(filepath.Join(tmpdir, id2.Algorithm().String(), id2.Hex(), "tar-split.json.gz")) 748 if err != nil { 749 t.Fatal(err) 750 } 751 defer dst.Close() 752 753 if _, err := io.Copy(dst, src); err != nil { 754 t.Fatal(err) 755 } 756 757 src.Sync() 758 dst.Sync() 759 760 ts, err := layer2.TarStream() 761 if err != nil { 762 t.Fatal(err) 763 } 764 _, err = io.Copy(ioutil.Discard, ts) 765 if err == nil { 766 t.Fatal("expected data verification to fail") 767 } 768 if !strings.Contains(err.Error(), "could not verify layer data") { 769 t.Fatalf("wrong error returned from tarstream: %q", err) 770 } 771 }