github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/copy_test.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package oras_test
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	_ "crypto/sha256"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"reflect"
    27  	"sync/atomic"
    28  	"testing"
    29  
    30  	"github.com/opcr-io/oras-go/v2"
    31  	"github.com/opcr-io/oras-go/v2/content"
    32  	"github.com/opcr-io/oras-go/v2/content/file"
    33  	"github.com/opcr-io/oras-go/v2/content/memory"
    34  	"github.com/opcr-io/oras-go/v2/errdef"
    35  	"github.com/opcr-io/oras-go/v2/internal/cas"
    36  	"github.com/opcr-io/oras-go/v2/internal/docker"
    37  	"github.com/opencontainers/go-digest"
    38  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    39  )
    40  
    41  // storageTracker tracks storage API counts.
    42  type storageTracker struct {
    43  	content.Storage
    44  	fetch  int64
    45  	push   int64
    46  	exists int64
    47  }
    48  
    49  func (t *storageTracker) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
    50  	atomic.AddInt64(&t.fetch, 1)
    51  	return t.Storage.Fetch(ctx, target)
    52  }
    53  
    54  func (t *storageTracker) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error {
    55  	atomic.AddInt64(&t.push, 1)
    56  	return t.Storage.Push(ctx, expected, content)
    57  }
    58  
    59  func (t *storageTracker) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
    60  	atomic.AddInt64(&t.exists, 1)
    61  	return t.Storage.Exists(ctx, target)
    62  }
    63  
    64  func TestCopy_FullCopy(t *testing.T) {
    65  	src := memory.New()
    66  	dst := memory.New()
    67  
    68  	// generate test content
    69  	var blobs [][]byte
    70  	var descs []ocispec.Descriptor
    71  	appendBlob := func(mediaType string, blob []byte) {
    72  		blobs = append(blobs, blob)
    73  		descs = append(descs, ocispec.Descriptor{
    74  			MediaType: mediaType,
    75  			Digest:    digest.FromBytes(blob),
    76  			Size:      int64(len(blob)),
    77  		})
    78  	}
    79  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
    80  		manifest := ocispec.Manifest{
    81  			Config: config,
    82  			Layers: layers,
    83  		}
    84  		manifestJSON, err := json.Marshal(manifest)
    85  		if err != nil {
    86  			t.Fatal(err)
    87  		}
    88  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
    89  	}
    90  
    91  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
    92  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))     // Blob 1
    93  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))     // Blob 2
    94  	generateManifest(descs[0], descs[1:3]...)                  // Blob 3
    95  
    96  	ctx := context.Background()
    97  	for i := range blobs {
    98  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
    99  		if err != nil {
   100  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   101  		}
   102  	}
   103  
   104  	root := descs[3]
   105  	ref := "foobar"
   106  	err := src.Tag(ctx, root, ref)
   107  	if err != nil {
   108  		t.Fatal("fail to tag root node", err)
   109  	}
   110  
   111  	// test copy
   112  	gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{})
   113  	if err != nil {
   114  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   115  	}
   116  	if !reflect.DeepEqual(gotDesc, root) {
   117  		t.Errorf("Copy() = %v, want %v", gotDesc, root)
   118  	}
   119  
   120  	// verify contents
   121  	for i, desc := range descs {
   122  		exists, err := dst.Exists(ctx, desc)
   123  		if err != nil {
   124  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   125  		}
   126  		if !exists {
   127  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   128  		}
   129  	}
   130  
   131  	// verify tag
   132  	gotDesc, err = dst.Resolve(ctx, ref)
   133  	if err != nil {
   134  		t.Fatal("dst.Resolve() error =", err)
   135  	}
   136  	if !reflect.DeepEqual(gotDesc, root) {
   137  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root)
   138  	}
   139  }
   140  
   141  func TestCopy_ExistedRoot(t *testing.T) {
   142  	src := memory.New()
   143  	dst := memory.New()
   144  
   145  	// generate test content
   146  	var blobs [][]byte
   147  	var descs []ocispec.Descriptor
   148  	appendBlob := func(mediaType string, blob []byte) {
   149  		blobs = append(blobs, blob)
   150  		descs = append(descs, ocispec.Descriptor{
   151  			MediaType: mediaType,
   152  			Digest:    digest.FromBytes(blob),
   153  			Size:      int64(len(blob)),
   154  		})
   155  	}
   156  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   157  		manifest := ocispec.Manifest{
   158  			Config: config,
   159  			Layers: layers,
   160  		}
   161  		manifestJSON, err := json.Marshal(manifest)
   162  		if err != nil {
   163  			t.Fatal(err)
   164  		}
   165  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
   166  	}
   167  
   168  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
   169  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))     // Blob 1
   170  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))     // Blob 2
   171  	generateManifest(descs[0], descs[1:3]...)                  // Blob 3
   172  
   173  	ctx := context.Background()
   174  	for i := range blobs {
   175  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   176  		if err != nil {
   177  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   178  		}
   179  	}
   180  
   181  	root := descs[3]
   182  	ref := "foobar"
   183  	newTag := "newtag"
   184  	err := src.Tag(ctx, root, ref)
   185  	if err != nil {
   186  		t.Fatal("fail to tag root node", err)
   187  	}
   188  
   189  	var skippedCount int64
   190  	copyOpts := oras.CopyOptions{
   191  		CopyGraphOptions: oras.CopyGraphOptions{
   192  			OnCopySkipped: func(ctx context.Context, desc ocispec.Descriptor) error {
   193  				atomic.AddInt64(&skippedCount, 1)
   194  				return nil
   195  			},
   196  		},
   197  	}
   198  
   199  	// copy with src tag
   200  	gotDesc, err := oras.Copy(ctx, src, ref, dst, "", copyOpts)
   201  	if err != nil {
   202  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   203  	}
   204  	if !reflect.DeepEqual(gotDesc, root) {
   205  		t.Errorf("Copy() = %v, want %v", gotDesc, root)
   206  	}
   207  	// copy with new tag
   208  	gotDesc, err = oras.Copy(ctx, src, ref, dst, newTag, copyOpts)
   209  	if err != nil {
   210  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   211  	}
   212  	if !reflect.DeepEqual(gotDesc, root) {
   213  		t.Errorf("Copy() = %v, want %v", gotDesc, root)
   214  	}
   215  
   216  	// verify contents
   217  	for i, desc := range descs {
   218  		exists, err := dst.Exists(ctx, desc)
   219  		if err != nil {
   220  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   221  		}
   222  		if !exists {
   223  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   224  		}
   225  	}
   226  
   227  	// verify src tag
   228  	gotDesc, err = dst.Resolve(ctx, ref)
   229  	if err != nil {
   230  		t.Fatal("dst.Resolve() error =", err)
   231  	}
   232  	if !reflect.DeepEqual(gotDesc, root) {
   233  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root)
   234  	}
   235  	// verify new tag
   236  	gotDesc, err = dst.Resolve(ctx, newTag)
   237  	if err != nil {
   238  		t.Fatal("dst.Resolve() error =", err)
   239  	}
   240  	if !reflect.DeepEqual(gotDesc, root) {
   241  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root)
   242  	}
   243  	// verify invocation of onCopySkipped()
   244  	if got, want := skippedCount, int64(1); got != want {
   245  		t.Errorf("count(OnCopySkipped()) = %v, want %v", got, want)
   246  	}
   247  }
   248  
   249  func TestCopyGraph_FullCopy(t *testing.T) {
   250  	src := cas.NewMemory()
   251  	dst := cas.NewMemory()
   252  
   253  	// generate test content
   254  	var blobs [][]byte
   255  	var descs []ocispec.Descriptor
   256  	appendBlob := func(mediaType string, blob []byte) {
   257  		blobs = append(blobs, blob)
   258  		descs = append(descs, ocispec.Descriptor{
   259  			MediaType: mediaType,
   260  			Digest:    digest.FromBytes(blob),
   261  			Size:      int64(len(blob)),
   262  		})
   263  	}
   264  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   265  		manifest := ocispec.Manifest{
   266  			Config: config,
   267  			Layers: layers,
   268  		}
   269  		manifestJSON, err := json.Marshal(manifest)
   270  		if err != nil {
   271  			t.Fatal(err)
   272  		}
   273  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
   274  	}
   275  	generateIndex := func(manifests ...ocispec.Descriptor) {
   276  		index := ocispec.Index{
   277  			Manifests: manifests,
   278  		}
   279  		indexJSON, err := json.Marshal(index)
   280  		if err != nil {
   281  			t.Fatal(err)
   282  		}
   283  		appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
   284  	}
   285  
   286  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
   287  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))     // Blob 1
   288  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))     // Blob 2
   289  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"))   // Blob 3
   290  	generateManifest(descs[0], descs[1:3]...)                  // Blob 4
   291  	generateManifest(descs[0], descs[3])                       // Blob 5
   292  	generateManifest(descs[0], descs[1:4]...)                  // Blob 6
   293  	generateIndex(descs[4:6]...)                               // Blob 7
   294  	generateIndex(descs[6])                                    // Blob 8
   295  	generateIndex()                                            // Blob 9
   296  	generateIndex(descs[7:10]...)                              // Blob 10
   297  
   298  	ctx := context.Background()
   299  	for i := range blobs {
   300  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   301  		if err != nil {
   302  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   303  		}
   304  	}
   305  
   306  	// test copy graph
   307  	srcTracker := &storageTracker{Storage: src}
   308  	dstTracker := &storageTracker{Storage: dst}
   309  	root := descs[len(descs)-1]
   310  	if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil {
   311  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
   312  	}
   313  
   314  	// verify contents
   315  	contents := dst.Map()
   316  	if got, want := len(contents), len(blobs); got != want {
   317  		t.Errorf("len(dst) = %v, wantErr %v", got, want)
   318  	}
   319  	for i := range blobs {
   320  		got, err := content.FetchAll(ctx, dst, descs[i])
   321  		if err != nil {
   322  			t.Errorf("content[%d] error = %v, wantErr %v", i, err, false)
   323  			continue
   324  		}
   325  		if want := blobs[i]; !bytes.Equal(got, want) {
   326  			t.Errorf("content[%d] = %v, want %v", i, got, want)
   327  		}
   328  	}
   329  
   330  	// verify API counts
   331  	if got, want := srcTracker.fetch, int64(len(blobs)); got != want {
   332  		t.Errorf("count(src.Fetch()) = %v, want %v", got, want)
   333  	}
   334  	if got, want := srcTracker.push, int64(0); got != want {
   335  		t.Errorf("count(src.Push()) = %v, want %v", got, want)
   336  	}
   337  	if got, want := srcTracker.exists, int64(0); got != want {
   338  		t.Errorf("count(src.Exists()) = %v, want %v", got, want)
   339  	}
   340  	if got, want := dstTracker.fetch, int64(0); got != want {
   341  		t.Errorf("count(dst.Fetch()) = %v, want %v", got, want)
   342  	}
   343  	if got, want := dstTracker.push, int64(len(blobs)); got != want {
   344  		t.Errorf("count(dst.Push()) = %v, want %v", got, want)
   345  	}
   346  	if got, want := dstTracker.exists, int64(len(blobs)); got != want {
   347  		t.Errorf("count(dst.Exists()) = %v, want %v", got, want)
   348  	}
   349  }
   350  
   351  func TestCopyGraph_PartialCopy(t *testing.T) {
   352  	src := cas.NewMemory()
   353  	dst := cas.NewMemory()
   354  
   355  	// generate test content
   356  	var blobs [][]byte
   357  	var descs []ocispec.Descriptor
   358  	appendBlob := func(mediaType string, blob []byte) {
   359  		blobs = append(blobs, blob)
   360  		descs = append(descs, ocispec.Descriptor{
   361  			MediaType: mediaType,
   362  			Digest:    digest.FromBytes(blob),
   363  			Size:      int64(len(blob)),
   364  		})
   365  	}
   366  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   367  		manifest := ocispec.Manifest{
   368  			Config: config,
   369  			Layers: layers,
   370  		}
   371  		manifestJSON, err := json.Marshal(manifest)
   372  		if err != nil {
   373  			t.Fatal(err)
   374  		}
   375  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
   376  	}
   377  	generateIndex := func(manifests ...ocispec.Descriptor) {
   378  		index := ocispec.Index{
   379  			Manifests: manifests,
   380  		}
   381  		indexJSON, err := json.Marshal(index)
   382  		if err != nil {
   383  			t.Fatal(err)
   384  		}
   385  		appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
   386  	}
   387  
   388  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
   389  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))     // Blob 1
   390  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))     // Blob 2
   391  	generateManifest(descs[0], descs[1:3]...)                  // Blob 3
   392  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"))   // Blob 4
   393  	generateManifest(descs[0], descs[4])                       // Blob 5
   394  	generateIndex(descs[3], descs[5])                          // Blob 6
   395  
   396  	ctx := context.Background()
   397  	for i := range blobs {
   398  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   399  		if err != nil {
   400  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   401  		}
   402  	}
   403  
   404  	// initial copy
   405  	root := descs[3]
   406  	if err := oras.CopyGraph(ctx, src, dst, root, oras.CopyGraphOptions{}); err != nil {
   407  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
   408  	}
   409  	// verify contents
   410  	contents := dst.Map()
   411  	if got, want := len(contents), len(blobs[:4]); got != want {
   412  		t.Fatalf("len(dst) = %v, wantErr %v", got, want)
   413  	}
   414  	for i := range blobs[:4] {
   415  		got, err := content.FetchAll(ctx, dst, descs[i])
   416  		if err != nil {
   417  			t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false)
   418  		}
   419  		if want := blobs[i]; !bytes.Equal(got, want) {
   420  			t.Fatalf("content[%d] = %v, want %v", i, got, want)
   421  		}
   422  	}
   423  
   424  	// test copy
   425  	srcTracker := &storageTracker{Storage: src}
   426  	dstTracker := &storageTracker{Storage: dst}
   427  	root = descs[len(descs)-1]
   428  	if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil {
   429  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
   430  	}
   431  
   432  	// verify contents
   433  	contents = dst.Map()
   434  	if got, want := len(contents), len(blobs); got != want {
   435  		t.Errorf("len(dst) = %v, wantErr %v", got, want)
   436  	}
   437  	for i := range blobs {
   438  		got, err := content.FetchAll(ctx, dst, descs[i])
   439  		if err != nil {
   440  			t.Errorf("content[%d] error = %v, wantErr %v", i, err, false)
   441  			continue
   442  		}
   443  		if want := blobs[i]; !bytes.Equal(got, want) {
   444  			t.Errorf("content[%d] = %v, want %v", i, got, want)
   445  		}
   446  	}
   447  
   448  	// verify API counts
   449  	if got, want := srcTracker.fetch, int64(3); got != want {
   450  		t.Errorf("count(src.Fetch()) = %v, want %v", got, want)
   451  	}
   452  	if got, want := srcTracker.push, int64(0); got != want {
   453  		t.Errorf("count(src.Push()) = %v, want %v", got, want)
   454  	}
   455  	if got, want := srcTracker.exists, int64(0); got != want {
   456  		t.Errorf("count(src.Exists()) = %v, want %v", got, want)
   457  	}
   458  	if got, want := dstTracker.fetch, int64(0); got != want {
   459  		t.Errorf("count(dst.Fetch()) = %v, want %v", got, want)
   460  	}
   461  	if got, want := dstTracker.push, int64(3); got != want {
   462  		t.Errorf("count(dst.Push()) = %v, want %v", got, want)
   463  	}
   464  	if got, want := dstTracker.exists, int64(5); got != want {
   465  		t.Errorf("count(dst.Exists()) = %v, want %v", got, want)
   466  	}
   467  }
   468  
   469  func TestCopy_WithOptions(t *testing.T) {
   470  	src := memory.New()
   471  
   472  	// generate test content
   473  	var blobs [][]byte
   474  	var descs []ocispec.Descriptor
   475  	appendBlob := func(mediaType string, blob []byte) {
   476  		blobs = append(blobs, blob)
   477  		descs = append(descs, ocispec.Descriptor{
   478  			MediaType: mediaType,
   479  			Digest:    digest.FromBytes(blob),
   480  			Size:      int64(len(blob)),
   481  		})
   482  	}
   483  	appendManifest := func(arc, os string, mediaType string, blob []byte) {
   484  		blobs = append(blobs, blob)
   485  		descs = append(descs, ocispec.Descriptor{
   486  			MediaType: mediaType,
   487  			Digest:    digest.FromBytes(blob),
   488  			Size:      int64(len(blob)),
   489  			Platform: &ocispec.Platform{
   490  				Architecture: arc,
   491  				OS:           os,
   492  			},
   493  		})
   494  	}
   495  	generateManifest := func(arc, os string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   496  		manifest := ocispec.Manifest{
   497  			Config: config,
   498  			Layers: layers,
   499  		}
   500  		manifestJSON, err := json.Marshal(manifest)
   501  		if err != nil {
   502  			t.Fatal(err)
   503  		}
   504  		appendManifest(arc, os, ocispec.MediaTypeImageManifest, manifestJSON)
   505  	}
   506  	generateIndex := func(manifests ...ocispec.Descriptor) {
   507  		index := ocispec.Index{
   508  			Manifests: manifests,
   509  		}
   510  		indexJSON, err := json.Marshal(index)
   511  		if err != nil {
   512  			t.Fatal(err)
   513  		}
   514  		appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
   515  	}
   516  
   517  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config"))           // Blob 0
   518  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))               // Blob 1
   519  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))               // Blob 2
   520  	generateManifest("test-arc-1", "test-os-1", descs[0], descs[1:3]...) // Blob 3
   521  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"))             // Blob 4
   522  	generateManifest("test-arc-2", "test-os-2", descs[0], descs[4])      // Blob 5
   523  	generateIndex(descs[3], descs[5])                                    // Blob 6
   524  
   525  	ctx := context.Background()
   526  	for i := range blobs {
   527  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   528  		if err != nil {
   529  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   530  		}
   531  	}
   532  
   533  	root := descs[6]
   534  	ref := "foobar"
   535  	err := src.Tag(ctx, root, ref)
   536  	if err != nil {
   537  		t.Fatal("fail to tag root node", err)
   538  	}
   539  
   540  	// test copy with media type filter
   541  	dst := memory.New()
   542  	opts := oras.DefaultCopyOptions
   543  	opts.MapRoot = func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
   544  		if root.MediaType == ocispec.MediaTypeImageIndex {
   545  			return root, nil
   546  		} else {
   547  			return ocispec.Descriptor{}, errdef.ErrNotFound
   548  		}
   549  	}
   550  	gotDesc, err := oras.Copy(ctx, src, ref, dst, "", opts)
   551  	if err != nil {
   552  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   553  	}
   554  	if !reflect.DeepEqual(gotDesc, root) {
   555  		t.Errorf("Copy() = %v, want %v", gotDesc, root)
   556  	}
   557  
   558  	// verify contents
   559  	for i, desc := range descs {
   560  		exists, err := dst.Exists(ctx, desc)
   561  		if err != nil {
   562  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   563  		}
   564  		if !exists {
   565  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   566  		}
   567  	}
   568  
   569  	// verify tag
   570  	gotDesc, err = dst.Resolve(ctx, ref)
   571  	if err != nil {
   572  		t.Fatal("dst.Resolve() error =", err)
   573  	}
   574  	if !reflect.DeepEqual(gotDesc, root) {
   575  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root)
   576  	}
   577  
   578  	// test copy with platform filter and hooks
   579  	dst = memory.New()
   580  	preCopyCount := int64(0)
   581  	postCopyCount := int64(0)
   582  	opts = oras.CopyOptions{
   583  		MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
   584  			manifests, err := content.Successors(ctx, src, root)
   585  			if err != nil {
   586  				return ocispec.Descriptor{}, errdef.ErrNotFound
   587  			}
   588  
   589  			// platform filter
   590  			for _, m := range manifests {
   591  				if m.Platform.Architecture == "test-arc-2" && m.Platform.OS == "test-os-2" {
   592  					return m, nil
   593  				}
   594  			}
   595  			return ocispec.Descriptor{}, errdef.ErrNotFound
   596  		},
   597  		CopyGraphOptions: oras.CopyGraphOptions{
   598  			PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error {
   599  				atomic.AddInt64(&preCopyCount, 1)
   600  				return nil
   601  			},
   602  			PostCopy: func(ctx context.Context, desc ocispec.Descriptor) error {
   603  				atomic.AddInt64(&postCopyCount, 1)
   604  				return nil
   605  			},
   606  		},
   607  	}
   608  	wantDesc := descs[5]
   609  	gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts)
   610  	if err != nil {
   611  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   612  	}
   613  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   614  		t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc)
   615  	}
   616  
   617  	// verify contents
   618  	for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[4:6]...) {
   619  		exists, err := dst.Exists(ctx, desc)
   620  		if err != nil {
   621  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   622  		}
   623  		if !exists {
   624  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   625  		}
   626  	}
   627  
   628  	// verify tag
   629  	gotDesc, err = dst.Resolve(ctx, ref)
   630  	if err != nil {
   631  		t.Fatal("dst.Resolve() error =", err)
   632  	}
   633  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   634  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc)
   635  	}
   636  
   637  	// verify API counts
   638  	if got, want := preCopyCount, int64(3); got != want {
   639  		t.Errorf("count(PreCopy()) = %v, want %v", got, want)
   640  	}
   641  	if got, want := postCopyCount, int64(3); got != want {
   642  		t.Errorf("count(PostCopy()) = %v, want %v", got, want)
   643  	}
   644  
   645  	// test copy with root filter, but no matching node can be found
   646  	dst = memory.New()
   647  	opts = oras.CopyOptions{
   648  		MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
   649  			if root.MediaType == "test" {
   650  				return root, nil
   651  			} else {
   652  				return ocispec.Descriptor{}, errdef.ErrNotFound
   653  			}
   654  		},
   655  		CopyGraphOptions: oras.DefaultCopyGraphOptions,
   656  	}
   657  
   658  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
   659  	if !errors.Is(err, errdef.ErrNotFound) {
   660  		t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrNotFound)
   661  	}
   662  
   663  	// test copy with MaxMetadataBytes = 1
   664  	dst = memory.New()
   665  	opts = oras.CopyOptions{
   666  		CopyGraphOptions: oras.CopyGraphOptions{
   667  			MaxMetadataBytes: 1,
   668  		},
   669  	}
   670  	if _, err := oras.Copy(ctx, src, ref, dst, "", opts); !errors.Is(err, errdef.ErrSizeExceedsLimit) {
   671  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, errdef.ErrSizeExceedsLimit)
   672  	}
   673  }
   674  
   675  func TestCopy_WithTargetPlatformOptions(t *testing.T) {
   676  	src := memory.New()
   677  	arc_1 := "test-arc-1"
   678  	os_1 := "test-os-1"
   679  	variant_1 := "v1"
   680  	arc_2 := "test-arc-2"
   681  	os_2 := "test-os-2"
   682  	variant_2 := "v2"
   683  
   684  	// generate test content
   685  	var blobs [][]byte
   686  	var descs []ocispec.Descriptor
   687  	appendBlob := func(mediaType string, blob []byte) {
   688  		blobs = append(blobs, blob)
   689  		descs = append(descs, ocispec.Descriptor{
   690  			MediaType: mediaType,
   691  			Digest:    digest.FromBytes(blob),
   692  			Size:      int64(len(blob)),
   693  		})
   694  	}
   695  	appendManifest := func(arc, os, variant string, mediaType string, blob []byte) {
   696  		blobs = append(blobs, blob)
   697  		descs = append(descs, ocispec.Descriptor{
   698  			MediaType: mediaType,
   699  			Digest:    digest.FromBytes(blob),
   700  			Size:      int64(len(blob)),
   701  			Platform: &ocispec.Platform{
   702  				Architecture: arc,
   703  				OS:           os,
   704  				Variant:      variant,
   705  			},
   706  		})
   707  	}
   708  	generateManifest := func(arc, os, variant string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   709  		manifest := ocispec.Manifest{
   710  			Config: config,
   711  			Layers: layers,
   712  		}
   713  		manifestJSON, err := json.Marshal(manifest)
   714  		if err != nil {
   715  			t.Fatal(err)
   716  		}
   717  		appendManifest(arc, os, variant, ocispec.MediaTypeImageManifest, manifestJSON)
   718  	}
   719  	generateIndex := func(manifests ...ocispec.Descriptor) {
   720  		index := ocispec.Index{
   721  			Manifests: manifests,
   722  		}
   723  		indexJSON, err := json.Marshal(index)
   724  		if err != nil {
   725  			t.Fatal(err)
   726  		}
   727  		appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
   728  	}
   729  
   730  	appendBlob(ocispec.MediaTypeImageConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json",
   731  "created":"2022-07-29T08:13:55Z",
   732  "author":"test author",
   733  "architecture":"test-arc-1",
   734  "os":"test-os-1",
   735  "variant":"v1"}`)) // Blob 0
   736  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))            // Blob 1
   737  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))            // Blob 2
   738  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1:3]...) // Blob 3
   739  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello1"))         // Blob 4
   740  	generateManifest(arc_2, os_2, variant_1, descs[0], descs[4])      // Blob 5
   741  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello2"))         // Blob 6
   742  	generateManifest(arc_1, os_1, variant_2, descs[0], descs[6])      // Blob 7
   743  	generateIndex(descs[3], descs[5], descs[7])                       // Blob 8
   744  
   745  	ctx := context.Background()
   746  	for i := range blobs {
   747  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   748  		if err != nil {
   749  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   750  		}
   751  	}
   752  
   753  	root := descs[8]
   754  	ref := "foobar"
   755  	err := src.Tag(ctx, root, ref)
   756  	if err != nil {
   757  		t.Fatal("fail to tag root node", err)
   758  	}
   759  
   760  	// test copy with platform filter for the image index
   761  	dst := memory.New()
   762  	opts := oras.CopyOptions{}
   763  	targetPlatform := ocispec.Platform{
   764  		Architecture: arc_2,
   765  		OS:           os_2,
   766  	}
   767  	opts.WithTargetPlatform(&targetPlatform)
   768  	wantDesc := descs[5]
   769  	gotDesc, err := oras.Copy(ctx, src, ref, dst, "", opts)
   770  	if err != nil {
   771  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   772  	}
   773  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   774  		t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc)
   775  	}
   776  
   777  	// verify contents
   778  	for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[4:6]...) {
   779  		exists, err := dst.Exists(ctx, desc)
   780  		if err != nil {
   781  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   782  		}
   783  		if !exists {
   784  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   785  		}
   786  	}
   787  
   788  	// verify tag
   789  	gotDesc, err = dst.Resolve(ctx, ref)
   790  	if err != nil {
   791  		t.Fatal("dst.Resolve() error =", err)
   792  	}
   793  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   794  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc)
   795  	}
   796  
   797  	// test copy with platform filter for the image index, and multiple
   798  	// manifests match the required platform. Should return the first matching
   799  	// entry.
   800  	dst = memory.New()
   801  	targetPlatform = ocispec.Platform{
   802  		Architecture: arc_1,
   803  		OS:           os_1,
   804  	}
   805  	opts = oras.CopyOptions{}
   806  	opts.WithTargetPlatform(&targetPlatform)
   807  	wantDesc = descs[3]
   808  	gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts)
   809  	if err != nil {
   810  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   811  	}
   812  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   813  		t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc)
   814  	}
   815  
   816  	// verify contents
   817  	for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[1:3]...) {
   818  		exists, err := dst.Exists(ctx, desc)
   819  		if err != nil {
   820  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   821  		}
   822  		if !exists {
   823  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   824  		}
   825  	}
   826  
   827  	// verify tag
   828  	gotDesc, err = dst.Resolve(ctx, ref)
   829  	if err != nil {
   830  		t.Fatal("dst.Resolve() error =", err)
   831  	}
   832  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   833  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc)
   834  	}
   835  
   836  	// test copy with platform filter and existing MapRoot func for the image
   837  	// index, but there is no matching node. Should return not found error.
   838  	dst = memory.New()
   839  	opts = oras.CopyOptions{
   840  		MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
   841  			if root.MediaType == ocispec.MediaTypeImageIndex {
   842  				return root, nil
   843  			} else {
   844  				return ocispec.Descriptor{}, errdef.ErrNotFound
   845  			}
   846  		},
   847  	}
   848  	targetPlatform = ocispec.Platform{
   849  		Architecture: arc_1,
   850  		OS:           os_2,
   851  	}
   852  	opts.WithTargetPlatform(&targetPlatform)
   853  
   854  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
   855  	expected := fmt.Sprintf("%s: %v: no matching manifest was found in the manifest list", root.Digest, errdef.ErrNotFound)
   856  	if err.Error() != expected {
   857  		t.Fatalf("Copy() error = %v, wantErr %v", err, expected)
   858  	}
   859  
   860  	// test copy with platform filter for the manifest
   861  	dst = memory.New()
   862  	opts = oras.CopyOptions{}
   863  	targetPlatform = ocispec.Platform{
   864  		Architecture: arc_1,
   865  		OS:           os_1,
   866  	}
   867  	opts.WithTargetPlatform(&targetPlatform)
   868  
   869  	root = descs[7]
   870  	err = src.Tag(ctx, root, ref)
   871  	if err != nil {
   872  		t.Fatal("fail to tag root node", err)
   873  	}
   874  
   875  	wantDesc = descs[7]
   876  	gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts)
   877  	if err != nil {
   878  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
   879  	}
   880  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   881  		t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc)
   882  	}
   883  
   884  	// verify contents
   885  	for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[6]) {
   886  		exists, err := dst.Exists(ctx, desc)
   887  		if err != nil {
   888  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
   889  		}
   890  		if !exists {
   891  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
   892  		}
   893  	}
   894  
   895  	// verify tag
   896  	gotDesc, err = dst.Resolve(ctx, ref)
   897  	if err != nil {
   898  		t.Fatal("dst.Resolve() error =", err)
   899  	}
   900  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   901  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc)
   902  	}
   903  
   904  	// test copy with platform filter for the manifest, but there is no matching
   905  	// node. Should return not found error.
   906  	dst = memory.New()
   907  	opts = oras.CopyOptions{}
   908  	targetPlatform = ocispec.Platform{
   909  		Architecture: arc_1,
   910  		OS:           os_1,
   911  		Variant:      variant_2,
   912  	}
   913  	opts.WithTargetPlatform(&targetPlatform)
   914  
   915  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
   916  	expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound)
   917  	if err.Error() != expected {
   918  		t.Fatalf("Copy() error = %v, wantErr %v", err, expected)
   919  	}
   920  
   921  	// test copy with platform filter, but the node's media type is not
   922  	// supported. Should return unsupported error
   923  	dst = memory.New()
   924  	opts = oras.CopyOptions{}
   925  	targetPlatform = ocispec.Platform{
   926  		Architecture: arc_1,
   927  		OS:           os_1,
   928  	}
   929  	opts.WithTargetPlatform(&targetPlatform)
   930  
   931  	root = descs[1]
   932  	err = src.Tag(ctx, root, ref)
   933  	if err != nil {
   934  		t.Fatal("fail to tag root node", err)
   935  	}
   936  
   937  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
   938  	if !errors.Is(err, errdef.ErrUnsupported) {
   939  		t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrUnsupported)
   940  	}
   941  
   942  	// generate incorrect test content
   943  	blobs = nil
   944  	descs = nil
   945  	appendBlob(docker.MediaTypeConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json",
   946  "created":"2022-07-29T08:13:55Z",
   947  "author":"test author 1",
   948  "architecture":"test-arc-1",
   949  "os":"test-os-1",
   950  "variant":"v1"}`)) // Blob 0
   951  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo1"))      // Blob 1
   952  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2
   953  	generateIndex(descs[2])                                      // Blob 3
   954  
   955  	ctx = context.Background()
   956  	for i := range blobs {
   957  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   958  		if err != nil {
   959  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   960  		}
   961  	}
   962  
   963  	dst = memory.New()
   964  	opts = oras.CopyOptions{}
   965  	targetPlatform = ocispec.Platform{
   966  		Architecture: arc_1,
   967  		OS:           os_1,
   968  	}
   969  	opts.WithTargetPlatform(&targetPlatform)
   970  
   971  	// test copy with platform filter for the manifest, but the manifest is
   972  	// invalid by having docker mediaType config in the manifest and oci
   973  	// mediaType in the image config. Should return error.
   974  	root = descs[2]
   975  	err = src.Tag(ctx, root, ref)
   976  	if err != nil {
   977  		t.Fatal("fail to tag root node", err)
   978  	}
   979  
   980  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
   981  	expected = fmt.Sprintf("fail to recognize platform from unknown config %s: expect %s", docker.MediaTypeConfig, ocispec.MediaTypeImageConfig)
   982  	if err.Error() != expected {
   983  		t.Fatalf("Copy() error = %v, wantErr %v", err, expected)
   984  	}
   985  
   986  	// generate test content with null config blob
   987  	blobs = nil
   988  	descs = nil
   989  	appendBlob(ocispec.MediaTypeImageConfig, []byte("null"))     // Blob 0
   990  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo2"))      // Blob 1
   991  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2
   992  	generateIndex(descs[2])                                      // Blob 3
   993  
   994  	ctx = context.Background()
   995  	for i := range blobs {
   996  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   997  		if err != nil {
   998  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   999  		}
  1000  	}
  1001  
  1002  	dst = memory.New()
  1003  	opts = oras.CopyOptions{}
  1004  	targetPlatform = ocispec.Platform{
  1005  		Architecture: arc_1,
  1006  		OS:           os_1,
  1007  	}
  1008  	opts.WithTargetPlatform(&targetPlatform)
  1009  
  1010  	// test copy with platform filter for the manifest with null config blob
  1011  	// should return not found error
  1012  	root = descs[2]
  1013  	err = src.Tag(ctx, root, ref)
  1014  	if err != nil {
  1015  		t.Fatal("fail to tag root node", err)
  1016  	}
  1017  
  1018  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
  1019  	expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound)
  1020  	if err.Error() != expected {
  1021  		t.Fatalf("Copy() error = %v, wantErr %v", err, expected)
  1022  	}
  1023  
  1024  	// generate test content with empty config blob
  1025  	blobs = nil
  1026  	descs = nil
  1027  	appendBlob(ocispec.MediaTypeImageConfig, []byte(""))         // Blob 0
  1028  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo3"))      // Blob 1
  1029  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2
  1030  	generateIndex(descs[2])                                      // Blob 3
  1031  
  1032  	ctx = context.Background()
  1033  	for i := range blobs {
  1034  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
  1035  		if err != nil {
  1036  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1037  		}
  1038  	}
  1039  
  1040  	dst = memory.New()
  1041  	opts = oras.CopyOptions{}
  1042  	targetPlatform = ocispec.Platform{
  1043  		Architecture: arc_1,
  1044  		OS:           os_1,
  1045  	}
  1046  	opts.WithTargetPlatform(&targetPlatform)
  1047  
  1048  	// test copy with platform filter for the manifest with empty config blob
  1049  	// should return not found error
  1050  	root = descs[2]
  1051  	err = src.Tag(ctx, root, ref)
  1052  	if err != nil {
  1053  		t.Fatal("fail to tag root node", err)
  1054  	}
  1055  
  1056  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
  1057  	expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound)
  1058  	if err.Error() != expected {
  1059  		t.Fatalf("Copy() error = %v, wantErr %v", err, expected)
  1060  	}
  1061  
  1062  	// test copy with no platform filter and nil opts.MapRoot
  1063  	// opts.MapRoot should be nil
  1064  	opts = oras.CopyOptions{}
  1065  	opts.WithTargetPlatform(nil)
  1066  	if opts.MapRoot != nil {
  1067  		t.Fatal("opts.MapRoot not equal to nil when platform is not provided")
  1068  	}
  1069  
  1070  	// test copy with no platform filter and custom opts.MapRoot
  1071  	// should return ErrNotFound
  1072  	opts = oras.CopyOptions{
  1073  		MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
  1074  			if root.MediaType == "test" {
  1075  				return root, nil
  1076  			} else {
  1077  				return ocispec.Descriptor{}, errdef.ErrNotFound
  1078  			}
  1079  		},
  1080  		CopyGraphOptions: oras.DefaultCopyGraphOptions,
  1081  	}
  1082  	opts.WithTargetPlatform(nil)
  1083  
  1084  	_, err = oras.Copy(ctx, src, ref, dst, "", opts)
  1085  	if !errors.Is(err, errdef.ErrNotFound) {
  1086  		t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrNotFound)
  1087  	}
  1088  }
  1089  
  1090  func TestCopy_RestoreDuplicates(t *testing.T) {
  1091  	src := memory.New()
  1092  	temp := t.TempDir()
  1093  	dst, err := file.New(temp)
  1094  	if err != nil {
  1095  		t.Fatal("file.New() error =", err)
  1096  	}
  1097  	defer dst.Close()
  1098  
  1099  	// generate test content
  1100  	var blobs [][]byte
  1101  	var descs []ocispec.Descriptor
  1102  	appendBlob := func(mediaType string, blob []byte, title string) {
  1103  		blobs = append(blobs, blob)
  1104  		desc := ocispec.Descriptor{
  1105  			MediaType: mediaType,
  1106  			Digest:    digest.FromBytes(blob),
  1107  			Size:      int64(len(blob)),
  1108  		}
  1109  		if title != "" {
  1110  			desc.Annotations = map[string]string{
  1111  				ocispec.AnnotationTitle: title,
  1112  			}
  1113  		}
  1114  		descs = append(descs, desc)
  1115  	}
  1116  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
  1117  		manifest := ocispec.Manifest{
  1118  			Config: config,
  1119  			Layers: layers,
  1120  		}
  1121  		manifestJSON, err := json.Marshal(manifest)
  1122  		if err != nil {
  1123  			t.Fatal(err)
  1124  		}
  1125  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON, "")
  1126  	}
  1127  
  1128  	appendBlob(ocispec.MediaTypeImageConfig, []byte("{}"), "") // Blob 0
  1129  	// 2 blobs with same content
  1130  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "foo.txt") // Blob 1
  1131  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "bar.txt") // Blob 2
  1132  	generateManifest(descs[0], descs[1:3]...)                           // Blob 3
  1133  
  1134  	ctx := context.Background()
  1135  	for i := range blobs {
  1136  		exists, err := src.Exists(ctx, descs[i])
  1137  		if err != nil {
  1138  			t.Fatalf("failed to check existence in src: %d: %v", i, err)
  1139  		}
  1140  		if exists {
  1141  			continue
  1142  		}
  1143  		if err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil {
  1144  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1145  		}
  1146  	}
  1147  
  1148  	root := descs[3]
  1149  	ref := "latest"
  1150  	err = src.Tag(ctx, root, ref)
  1151  	if err != nil {
  1152  		t.Fatal("fail to tag root node", err)
  1153  	}
  1154  
  1155  	// test copy
  1156  	gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{})
  1157  	if err != nil {
  1158  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
  1159  	}
  1160  	if !reflect.DeepEqual(gotDesc, root) {
  1161  		t.Errorf("Copy() = %v, want %v", gotDesc, root)
  1162  	}
  1163  
  1164  	// verify contents
  1165  	for i, desc := range descs {
  1166  		exists, err := dst.Exists(ctx, desc)
  1167  		if err != nil {
  1168  			t.Fatalf("dst.Exists(%d) error = %v", i, err)
  1169  		}
  1170  		if !exists {
  1171  			t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
  1172  		}
  1173  	}
  1174  
  1175  	// verify tag
  1176  	gotDesc, err = dst.Resolve(ctx, ref)
  1177  	if err != nil {
  1178  		t.Fatal("dst.Resolve() error =", err)
  1179  	}
  1180  	if !reflect.DeepEqual(gotDesc, root) {
  1181  		t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root)
  1182  	}
  1183  }
  1184  
  1185  func TestCopy_DiscardDuplicates(t *testing.T) {
  1186  	src := memory.New()
  1187  	temp := t.TempDir()
  1188  	dst, err := file.New(temp)
  1189  	if err != nil {
  1190  		t.Fatal("file.New() error =", err)
  1191  	}
  1192  	dst.ForceCAS = true
  1193  	defer dst.Close()
  1194  
  1195  	// generate test content
  1196  	var blobs [][]byte
  1197  	var descs []ocispec.Descriptor
  1198  	appendBlob := func(mediaType string, blob []byte, title string) {
  1199  		blobs = append(blobs, blob)
  1200  		desc := ocispec.Descriptor{
  1201  			MediaType: mediaType,
  1202  			Digest:    digest.FromBytes(blob),
  1203  			Size:      int64(len(blob)),
  1204  		}
  1205  		if title != "" {
  1206  			desc.Annotations = map[string]string{
  1207  				ocispec.AnnotationTitle: title,
  1208  			}
  1209  		}
  1210  		descs = append(descs, desc)
  1211  	}
  1212  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
  1213  		manifest := ocispec.Manifest{
  1214  			Config: config,
  1215  			Layers: layers,
  1216  		}
  1217  		manifestJSON, err := json.Marshal(manifest)
  1218  		if err != nil {
  1219  			t.Fatal(err)
  1220  		}
  1221  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON, "")
  1222  	}
  1223  
  1224  	appendBlob(ocispec.MediaTypeImageConfig, []byte("{}"), "") // Blob 0
  1225  	// 2 blobs with same content
  1226  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "foo.txt") // Blob 1
  1227  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "bar.txt") // Blob 2
  1228  	generateManifest(descs[0], descs[1:3]...)                           // Blob 3
  1229  
  1230  	ctx := context.Background()
  1231  	for i := range blobs {
  1232  		exists, err := src.Exists(ctx, descs[i])
  1233  		if err != nil {
  1234  			t.Fatalf("failed to check existence in src: %d: %v", i, err)
  1235  		}
  1236  		if exists {
  1237  			continue
  1238  		}
  1239  		if err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil {
  1240  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1241  		}
  1242  	}
  1243  
  1244  	root := descs[3]
  1245  	ref := "latest"
  1246  	err = src.Tag(ctx, root, ref)
  1247  	if err != nil {
  1248  		t.Fatal("fail to tag root node", err)
  1249  	}
  1250  
  1251  	// test copy
  1252  	gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{})
  1253  	if err != nil {
  1254  		t.Fatalf("Copy() error = %v, wantErr %v", err, false)
  1255  	}
  1256  	if !reflect.DeepEqual(gotDesc, root) {
  1257  		t.Errorf("Copy() = %v, want %v", gotDesc, root)
  1258  	}
  1259  
  1260  	// verify only one of foo.txt and bar.txt exists
  1261  	fooExists, err := dst.Exists(ctx, descs[1])
  1262  	if err != nil {
  1263  		t.Fatalf("dst.Exists(foo) error = %v", err)
  1264  	}
  1265  	barExists, err := dst.Exists(ctx, descs[2])
  1266  	if err != nil {
  1267  		t.Fatalf("dst.Exists(bar) error = %v", err)
  1268  	}
  1269  	if fooExists == barExists {
  1270  		t.Error("Only one of foo.txt and bar.txt should exist")
  1271  	}
  1272  }
  1273  
  1274  func TestCopyGraph_WithOptions(t *testing.T) {
  1275  	src := cas.NewMemory()
  1276  	dst := cas.NewMemory()
  1277  
  1278  	// generate test content
  1279  	var blobs [][]byte
  1280  	var descs []ocispec.Descriptor
  1281  	appendBlob := func(mediaType string, blob []byte) {
  1282  		blobs = append(blobs, blob)
  1283  		descs = append(descs, ocispec.Descriptor{
  1284  			MediaType: mediaType,
  1285  			Digest:    digest.FromBytes(blob),
  1286  			Size:      int64(len(blob)),
  1287  		})
  1288  	}
  1289  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
  1290  		manifest := ocispec.Manifest{
  1291  			Config: config,
  1292  			Layers: layers,
  1293  		}
  1294  		manifestJSON, err := json.Marshal(manifest)
  1295  		if err != nil {
  1296  			t.Fatal(err)
  1297  		}
  1298  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
  1299  	}
  1300  	generateIndex := func(manifests ...ocispec.Descriptor) {
  1301  		index := ocispec.Index{
  1302  			Manifests: manifests,
  1303  		}
  1304  		indexJSON, err := json.Marshal(index)
  1305  		if err != nil {
  1306  			t.Fatal(err)
  1307  		}
  1308  		appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
  1309  	}
  1310  
  1311  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
  1312  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))     // Blob 1
  1313  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))     // Blob 2
  1314  	generateManifest(descs[0], descs[1:3]...)                  // Blob 3
  1315  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"))   // Blob 4
  1316  	generateManifest(descs[0], descs[4])                       // Blob 5
  1317  	generateIndex(descs[3], descs[5])                          // Blob 6
  1318  
  1319  	ctx := context.Background()
  1320  	for i := range blobs {
  1321  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
  1322  		if err != nil {
  1323  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1324  		}
  1325  	}
  1326  
  1327  	// initial copy
  1328  	root := descs[3]
  1329  	opts := oras.DefaultCopyGraphOptions
  1330  	opts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
  1331  		successors, err := content.Successors(ctx, fetcher, desc)
  1332  		if err != nil {
  1333  			return nil, err
  1334  		}
  1335  		// filter media type
  1336  		var filtered []ocispec.Descriptor
  1337  		for _, s := range successors {
  1338  			if s.MediaType != ocispec.MediaTypeImageConfig {
  1339  				filtered = append(filtered, s)
  1340  			}
  1341  		}
  1342  		return filtered, nil
  1343  	}
  1344  	if err := oras.CopyGraph(ctx, src, dst, root, opts); err != nil {
  1345  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
  1346  	}
  1347  	// verify contents
  1348  	contents := dst.Map()
  1349  	if got, want := len(contents), len(blobs[1:4]); got != want {
  1350  		t.Fatalf("len(dst) = %v, wantErr %v", got, want)
  1351  	}
  1352  	for i := 1; i < 4; i++ {
  1353  		got, err := content.FetchAll(ctx, dst, descs[i])
  1354  		if err != nil {
  1355  			t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false)
  1356  		}
  1357  		if want := blobs[i]; !bytes.Equal(got, want) {
  1358  			t.Fatalf("content[%d] = %v, want %v", i, got, want)
  1359  		}
  1360  	}
  1361  
  1362  	// test successor descriptors not obtained from src
  1363  	root = descs[3]
  1364  	opts = oras.DefaultCopyGraphOptions
  1365  	opts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
  1366  		if content.Equal(desc, root) {
  1367  			return descs[1:3], nil
  1368  		}
  1369  		return content.Successors(ctx, fetcher, desc)
  1370  	}
  1371  	if err := oras.CopyGraph(ctx, src, cas.NewMemory(), root, opts); err != nil {
  1372  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
  1373  	}
  1374  
  1375  	// test partial copy
  1376  	var preCopyCount int64
  1377  	var postCopyCount int64
  1378  	var skippedCount int64
  1379  	opts = oras.CopyGraphOptions{
  1380  		PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error {
  1381  			atomic.AddInt64(&preCopyCount, 1)
  1382  			return nil
  1383  		},
  1384  		PostCopy: func(ctx context.Context, desc ocispec.Descriptor) error {
  1385  			atomic.AddInt64(&postCopyCount, 1)
  1386  			return nil
  1387  		},
  1388  		OnCopySkipped: func(ctx context.Context, desc ocispec.Descriptor) error {
  1389  			atomic.AddInt64(&skippedCount, 1)
  1390  			return nil
  1391  		},
  1392  	}
  1393  	root = descs[len(descs)-1]
  1394  	if err := oras.CopyGraph(ctx, src, dst, root, opts); err != nil {
  1395  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
  1396  	}
  1397  
  1398  	// verify contents
  1399  	contents = dst.Map()
  1400  	if got, want := len(contents), len(blobs); got != want {
  1401  		t.Errorf("len(dst) = %v, wantErr %v", got, want)
  1402  	}
  1403  	for i := range blobs {
  1404  		got, err := content.FetchAll(ctx, dst, descs[i])
  1405  		if err != nil {
  1406  			t.Errorf("content[%d] error = %v, wantErr %v", i, err, false)
  1407  			continue
  1408  		}
  1409  		if want := blobs[i]; !bytes.Equal(got, want) {
  1410  			t.Errorf("content[%d] = %v, want %v", i, got, want)
  1411  		}
  1412  	}
  1413  
  1414  	// verify API counts
  1415  	if got, want := preCopyCount, int64(4); got != want {
  1416  		t.Errorf("count(PreCopy()) = %v, want %v", got, want)
  1417  	}
  1418  	if got, want := postCopyCount, int64(4); got != want {
  1419  		t.Errorf("count(PostCopy()) = %v, want %v", got, want)
  1420  	}
  1421  	if got, want := skippedCount, int64(1); got != want {
  1422  		t.Errorf("count(OnCopySkipped()) = %v, want %v", got, want)
  1423  	}
  1424  
  1425  	// test CopyGraph with MaxMetadataBytes = 1
  1426  	root = descs[6]
  1427  	dst = cas.NewMemory()
  1428  	opts = oras.CopyGraphOptions{
  1429  		MaxMetadataBytes: 1,
  1430  	}
  1431  	if err := oras.CopyGraph(ctx, src, dst, root, opts); !errors.Is(err, errdef.ErrSizeExceedsLimit) {
  1432  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, errdef.ErrSizeExceedsLimit)
  1433  	}
  1434  }
  1435  
  1436  func TestCopyGraph_WithConcurrencyLimit(t *testing.T) {
  1437  	src := cas.NewMemory()
  1438  	// generate test content
  1439  	var blobs [][]byte
  1440  	var descs []ocispec.Descriptor
  1441  	appendBlob := func(mediaType string, blob []byte) {
  1442  		blobs = append(blobs, blob)
  1443  		descs = append(descs, ocispec.Descriptor{
  1444  			MediaType: mediaType,
  1445  			Digest:    digest.FromBytes(blob),
  1446  			Size:      int64(len(blob)),
  1447  		})
  1448  	}
  1449  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
  1450  		manifest := ocispec.Manifest{
  1451  			MediaType: ocispec.MediaTypeImageManifest,
  1452  			Config:    config,
  1453  			Layers:    layers,
  1454  		}
  1455  		manifestJSON, err := json.Marshal(manifest)
  1456  		if err != nil {
  1457  			t.Fatal(err)
  1458  		}
  1459  		appendBlob(manifest.MediaType, manifestJSON)
  1460  	}
  1461  	generateArtifact := func(subject *ocispec.Descriptor, artifactType string, blobs ...ocispec.Descriptor) {
  1462  		manifest := ocispec.Artifact{
  1463  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1464  			Subject:      subject,
  1465  			Blobs:        blobs,
  1466  			ArtifactType: artifactType,
  1467  		}
  1468  		manifestJSON, err := json.Marshal(manifest)
  1469  		if err != nil {
  1470  			t.Fatal(err)
  1471  		}
  1472  		appendBlob(manifest.MediaType, manifestJSON)
  1473  	}
  1474  	generateIndex := func(manifests ...ocispec.Descriptor) {
  1475  		index := ocispec.Index{
  1476  			MediaType: ocispec.MediaTypeImageIndex,
  1477  			Manifests: manifests,
  1478  		}
  1479  		indexJSON, err := json.Marshal(index)
  1480  		if err != nil {
  1481  			t.Fatal(err)
  1482  		}
  1483  		appendBlob(index.MediaType, indexJSON)
  1484  	}
  1485  
  1486  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
  1487  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))     // Blob 1
  1488  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))     // Blob 2
  1489  	generateManifest(descs[0], descs[1:3]...)                  // Blob 3
  1490  	generateArtifact(&descs[3], "artifact.1")                  // Blob 4
  1491  	generateArtifact(&descs[3], "artifact.2")                  // Blob 5
  1492  	generateArtifact(&descs[3], "artifact.3")                  // Blob 6
  1493  	generateArtifact(&descs[3], "artifact.4")                  // Blob 7
  1494  	generateIndex(descs[3:8]...)                               // Blob 8
  1495  
  1496  	ctx := context.Background()
  1497  	for i := range blobs {
  1498  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
  1499  		if err != nil {
  1500  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1501  		}
  1502  	}
  1503  
  1504  	// test different concurrency limit
  1505  	root := descs[len(descs)-1]
  1506  	directSuccessorsNum := 5
  1507  	opts := oras.DefaultCopyGraphOptions
  1508  	for i := 1; i <= directSuccessorsNum; i++ {
  1509  		dst := cas.NewMemory()
  1510  		opts.Concurrency = i
  1511  		if err := oras.CopyGraph(ctx, src, dst, root, opts); err != nil {
  1512  			t.Fatalf("CopyGraph(concurrency: %d) error = %v, wantErr %v", i, err, false)
  1513  		}
  1514  
  1515  		// verify contents
  1516  		contents := dst.Map()
  1517  		if got, want := len(contents), len(blobs); got != want {
  1518  			t.Errorf("len(dst) = %v, wantErr %v", got, want)
  1519  		}
  1520  		for i := range blobs {
  1521  			got, err := content.FetchAll(ctx, dst, descs[i])
  1522  			if err != nil {
  1523  				t.Errorf("content[%d] error = %v, wantErr %v", i, err, false)
  1524  				continue
  1525  			}
  1526  			if want := blobs[i]; !bytes.Equal(got, want) {
  1527  				t.Errorf("content[%d] = %v, want %v", i, got, want)
  1528  			}
  1529  		}
  1530  	}
  1531  }
  1532  
  1533  func TestCopyGraph_ForeignLayers(t *testing.T) {
  1534  	src := cas.NewMemory()
  1535  	dst := cas.NewMemory()
  1536  
  1537  	// generate test content
  1538  	var blobs [][]byte
  1539  	var descs []ocispec.Descriptor
  1540  	appendBlob := func(mediaType string, blob []byte) {
  1541  		desc := ocispec.Descriptor{
  1542  			MediaType: mediaType,
  1543  			Digest:    digest.FromBytes(blob),
  1544  			Size:      int64(len(blob)),
  1545  		}
  1546  		if mediaType == ocispec.MediaTypeImageLayerNonDistributable {
  1547  			desc.URLs = append(desc.URLs, "http://127.0.0.1/dummy")
  1548  			blob = nil
  1549  		}
  1550  		descs = append(descs, desc)
  1551  		blobs = append(blobs, blob)
  1552  	}
  1553  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
  1554  		manifest := ocispec.Manifest{
  1555  			Config: config,
  1556  			Layers: layers,
  1557  		}
  1558  		manifestJSON, err := json.Marshal(manifest)
  1559  		if err != nil {
  1560  			t.Fatal(err)
  1561  		}
  1562  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
  1563  	}
  1564  
  1565  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config"))               // Blob 0
  1566  	appendBlob(ocispec.MediaTypeImageLayerNonDistributable, []byte("hello")) // Blob 1
  1567  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))                   // Blob 2
  1568  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))                   // Blob 3
  1569  	generateManifest(descs[0], descs[1:4]...)                                // Blob 4
  1570  
  1571  	ctx := context.Background()
  1572  	for i := range blobs {
  1573  		if blobs[i] == nil {
  1574  			continue
  1575  		}
  1576  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
  1577  		if err != nil {
  1578  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1579  		}
  1580  	}
  1581  
  1582  	// test copy
  1583  	srcTracker := &storageTracker{Storage: src}
  1584  	dstTracker := &storageTracker{Storage: dst}
  1585  	root := descs[len(descs)-1]
  1586  	if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil {
  1587  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
  1588  	}
  1589  
  1590  	// verify contents
  1591  	contents := dst.Map()
  1592  	if got, want := len(contents), len(blobs)-1; got != want {
  1593  		t.Errorf("len(dst) = %v, wantErr %v", got, want)
  1594  	}
  1595  	for i := range blobs {
  1596  		if blobs[i] == nil {
  1597  			continue
  1598  		}
  1599  		got, err := content.FetchAll(ctx, dst, descs[i])
  1600  		if err != nil {
  1601  			t.Errorf("content[%d] error = %v, wantErr %v", i, err, false)
  1602  			continue
  1603  		}
  1604  		if want := blobs[i]; !bytes.Equal(got, want) {
  1605  			t.Errorf("content[%d] = %v, want %v", i, got, want)
  1606  		}
  1607  	}
  1608  
  1609  	// verify API counts
  1610  	if got, want := srcTracker.fetch, int64(len(blobs)-1); got != want {
  1611  		t.Errorf("count(src.Fetch()) = %v, want %v", got, want)
  1612  	}
  1613  	if got, want := srcTracker.push, int64(0); got != want {
  1614  		t.Errorf("count(src.Push()) = %v, want %v", got, want)
  1615  	}
  1616  	if got, want := srcTracker.exists, int64(0); got != want {
  1617  		t.Errorf("count(src.Exists()) = %v, want %v", got, want)
  1618  	}
  1619  	if got, want := dstTracker.fetch, int64(0); got != want {
  1620  		t.Errorf("count(dst.Fetch()) = %v, want %v", got, want)
  1621  	}
  1622  	if got, want := dstTracker.push, int64(len(blobs)-1); got != want {
  1623  		t.Errorf("count(dst.Push()) = %v, want %v", got, want)
  1624  	}
  1625  	if got, want := dstTracker.exists, int64(len(blobs)-1); got != want {
  1626  		t.Errorf("count(dst.Exists()) = %v, want %v", got, want)
  1627  	}
  1628  }
  1629  
  1630  func TestCopyGraph_ForeignLayers_Mixed(t *testing.T) {
  1631  	src := cas.NewMemory()
  1632  	dst := cas.NewMemory()
  1633  
  1634  	// generate test content
  1635  	var blobs [][]byte
  1636  	var descs []ocispec.Descriptor
  1637  	appendBlob := func(mediaType string, blob []byte) {
  1638  		desc := ocispec.Descriptor{
  1639  			MediaType: mediaType,
  1640  			Digest:    digest.FromBytes(blob),
  1641  			Size:      int64(len(blob)),
  1642  		}
  1643  		if mediaType == ocispec.MediaTypeImageLayerNonDistributable {
  1644  			desc.URLs = append(desc.URLs, "http://127.0.0.1/dummy")
  1645  			blob = nil
  1646  		}
  1647  		descs = append(descs, desc)
  1648  		blobs = append(blobs, blob)
  1649  	}
  1650  	generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
  1651  		manifest := ocispec.Manifest{
  1652  			Config: config,
  1653  			Layers: layers,
  1654  		}
  1655  		manifestJSON, err := json.Marshal(manifest)
  1656  		if err != nil {
  1657  			t.Fatal(err)
  1658  		}
  1659  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
  1660  	}
  1661  
  1662  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config"))               // Blob 0
  1663  	appendBlob(ocispec.MediaTypeImageLayerNonDistributable, []byte("hello")) // Blob 1
  1664  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"))                 // Blob 2
  1665  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))                   // Blob 3
  1666  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))                   // Blob 4
  1667  	generateManifest(descs[0], descs[1:5]...)                                // Blob 5
  1668  
  1669  	ctx := context.Background()
  1670  	for i := range blobs {
  1671  		if blobs[i] == nil {
  1672  			continue
  1673  		}
  1674  		err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
  1675  		if err != nil {
  1676  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
  1677  		}
  1678  	}
  1679  
  1680  	// test copy
  1681  	srcTracker := &storageTracker{Storage: src}
  1682  	dstTracker := &storageTracker{Storage: dst}
  1683  	root := descs[len(descs)-1]
  1684  	if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{
  1685  		Concurrency: 1,
  1686  	}); err != nil {
  1687  		t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false)
  1688  	}
  1689  
  1690  	// verify contents
  1691  	contents := dst.Map()
  1692  	if got, want := len(contents), len(blobs)-1; got != want {
  1693  		t.Errorf("len(dst) = %v, wantErr %v", got, want)
  1694  	}
  1695  	for i := range blobs {
  1696  		if blobs[i] == nil {
  1697  			continue
  1698  		}
  1699  		got, err := content.FetchAll(ctx, dst, descs[i])
  1700  		if err != nil {
  1701  			t.Errorf("content[%d] error = %v, wantErr %v", i, err, false)
  1702  			continue
  1703  		}
  1704  		if want := blobs[i]; !bytes.Equal(got, want) {
  1705  			t.Errorf("content[%d] = %v, want %v", i, got, want)
  1706  		}
  1707  	}
  1708  
  1709  	// verify API counts
  1710  	if got, want := srcTracker.fetch, int64(len(blobs)-1); got != want {
  1711  		t.Errorf("count(src.Fetch()) = %v, want %v", got, want)
  1712  	}
  1713  	if got, want := srcTracker.push, int64(0); got != want {
  1714  		t.Errorf("count(src.Push()) = %v, want %v", got, want)
  1715  	}
  1716  	if got, want := srcTracker.exists, int64(0); got != want {
  1717  		t.Errorf("count(src.Exists()) = %v, want %v", got, want)
  1718  	}
  1719  	if got, want := dstTracker.fetch, int64(0); got != want {
  1720  		t.Errorf("count(dst.Fetch()) = %v, want %v", got, want)
  1721  	}
  1722  	if got, want := dstTracker.push, int64(len(blobs)-1); got != want {
  1723  		t.Errorf("count(dst.Push()) = %v, want %v", got, want)
  1724  	}
  1725  	if got, want := dstTracker.exists, int64(len(blobs)-1); got != want {
  1726  		t.Errorf("count(dst.Exists()) = %v, want %v", got, want)
  1727  	}
  1728  }