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