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