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