Skip to content

Commit

Permalink
chore: Add test coverage for successors
Browse files Browse the repository at this point in the history
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
  • Loading branch information
TerryHowe committed Jul 22, 2024
1 parent d36a282 commit 06ba573
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 76 deletions.
7 changes: 3 additions & 4 deletions cmd/oras/internal/display/status/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ package status

import (
"context"
"oras.land/oras/internal/graph"
"sync"

"oras.land/oras/cmd/oras/internal/output"
"oras.land/oras/internal/descriptor"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras/cmd/oras/internal/output"
)

// TextPushHandler handles text status output for push events.
Expand Down Expand Up @@ -66,7 +65,7 @@ func (ph *TextPushHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetche
}
opts.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
successors, err := descriptor.Successors(ctx, desc, fetcher, DeduplicatedFilter(committed))
successors, err := graph.FilteredSuccessors(ctx, desc, fetcher, DeduplicatedFilter(committed))
if err != nil {
return err
}
Expand Down
7 changes: 3 additions & 4 deletions cmd/oras/internal/display/status/tty.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ package status

import (
"context"
"oras.land/oras/internal/graph"
"os"
"sync"

"oras.land/oras/cmd/oras/internal/display/status/track"
"oras.land/oras/internal/descriptor"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras/cmd/oras/internal/display/status/track"
)

// TTYPushHandler handles TTY status output for push command.
Expand Down Expand Up @@ -70,7 +69,7 @@ func (ph *TTYPushHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetcher
}
opts.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
successors, err := descriptor.Successors(ctx, desc, fetcher, DeduplicatedFilter(committed))
successors, err := graph.FilteredSuccessors(ctx, desc, fetcher, DeduplicatedFilter(committed))
if err != nil {
return err

Check warning on line 74 in cmd/oras/internal/display/status/tty.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/status/tty.go#L74

Added line #L74 was not covered by tests
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/oras/internal/output/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ func (p *Printer) Println(a ...any) error {
if err != nil {
err = fmt.Errorf("display output error: %w", err)
_, _ = fmt.Fprint(p.err, err)
return err
}
return err
return nil
}

// Printf prints objects concurrent-safely with newline.
Expand Down
5 changes: 2 additions & 3 deletions cmd/oras/root/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import (
oerrors "oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/cmd/oras/internal/output"
"oras.land/oras/internal/descriptor"
"oras.land/oras/internal/docker"
"oras.land/oras/internal/graph"
"oras.land/oras/internal/listener"
Expand Down Expand Up @@ -190,7 +189,7 @@ func doCopy(ctx context.Context, printer *output.Printer, src oras.ReadOnlyGraph
}
extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
successors, err := descriptor.Successors(ctx, desc, dst, status.DeduplicatedFilter(committed))
successors, err := graph.FilteredSuccessors(ctx, desc, dst, status.DeduplicatedFilter(committed))
if err != nil {
return err
}
Expand Down Expand Up @@ -219,7 +218,7 @@ func doCopy(ctx context.Context, printer *output.Printer, src oras.ReadOnlyGraph
}
extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
successors, err := descriptor.Successors(ctx, desc, tracked, status.DeduplicatedFilter(committed))
successors, err := graph.FilteredSuccessors(ctx, desc, tracked, status.DeduplicatedFilter(committed))
if err != nil {
return err

Check warning on line 223 in cmd/oras/root/cp.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/cp.go#L223

Added line #L223 was not covered by tests
}
Expand Down
20 changes: 0 additions & 20 deletions internal/descriptor/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@ limitations under the License.
package descriptor

import (
"context"

"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"

"oras.land/oras/internal/docker"
)

Expand Down Expand Up @@ -55,19 +51,3 @@ func GetTitleOrMediaType(desc ocispec.Descriptor) (name string, isTitle bool) {
func GenerateContentKey(desc ocispec.Descriptor) string {
return desc.Digest.String() + desc.Annotations[ocispec.AnnotationTitle]
}

// Successors fetches successors and returns filtered ones.
func Successors(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, filter func(ocispec.Descriptor) bool) ([]ocispec.Descriptor, error) {
allSuccessors, err := content.Successors(ctx, fetcher, desc)
if err != nil {
return nil, err
}

var successors []ocispec.Descriptor
for _, s := range allSuccessors {
if filter(s) {
successors = append(successors, s)
}
}
return successors, nil
}
6 changes: 3 additions & 3 deletions internal/descriptor/descriptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ var (
}

imageDesc = ocispec.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json",
MediaType: ocispec.MediaTypeImageManifest,
Digest: "sha256:2e0e0fe1fb3edbcdddad941c90d2b51e25a6bcd593e82545441a216de7bfa834",
Size: 474,
}

titledDesc = ocispec.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json",
MediaType: ocispec.MediaTypeImageManifest,
Digest: "sha256:2e0e0fe1fb3edbcdddad941c90d2b51e25a6bcd593e82545441a216de7bfa834",
Size: 474,
Annotations: map[string]string{"org.opencontainers.image.title": "shaboozey"},
Expand Down Expand Up @@ -88,6 +88,6 @@ func TestDescriptor_GenerateContentKey(t *testing.T) {
expected := "sha256:2e0e0fe1fb3edbcdddad941c90d2b51e25a6bcd593e82545441a216de7bfa834shaboozey"
got := descriptor.GenerateContentKey(titledDesc)
if expected != got {
t.Fatalf("GetTitleOrMediaType() got %v, want %v", got, expected)
t.Fatalf("GenerateContentKey got %v, want %v", got, expected)
}
}
16 changes: 16 additions & 0 deletions internal/graph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,19 @@ func FindPredecessors(ctx context.Context, src oras.ReadOnlyGraphTarget, descs [
}
return referrers, nil
}

// FilteredSuccessors fetches successors and returns filtered ones.
func FilteredSuccessors(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, filter func(ocispec.Descriptor) bool) ([]ocispec.Descriptor, error) {
allSuccessors, err := content.Successors(ctx, fetcher, desc)
if err != nil {
return nil, err
}

var successors []ocispec.Descriptor
for _, s := range allSuccessors {
if filter(s) {
successors = append(successors, s)
}
}
return successors, nil
}
122 changes: 81 additions & 41 deletions internal/graph/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,37 @@ import (
"bytes"
"context"
"encoding/json"
"github.com/opencontainers/go-digest"
"oras.land/oras-go/v2/content/memory"
"reflect"
"testing"

"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras/internal/docker"
)

type fetcher struct {
type contentFetcher struct {
content.Fetcher
}

func TestSuccessors(t *testing.T) {
func newTestFetcher(t *testing.T) (subject, config, ociImage, dockerImage, index ocispec.Descriptor, fetcher content.Fetcher) {
var blobs [][]byte
var descs []ocispec.Descriptor
appendBlob := func(mediaType string, blob []byte) {
ctx := context.Background()
memoryStorage := memory.New()
appendBlob := func(mediaType string, blob []byte) ocispec.Descriptor {
blobs = append(blobs, blob)
descs = append(descs, ocispec.Descriptor{
desc := ocispec.Descriptor{
MediaType: mediaType,
Digest: digest.FromBytes(blob),
Size: int64(len(blob)),
})
}
if err := memoryStorage.Push(ctx, desc, bytes.NewReader(blob)); err != nil {
t.Errorf("Error pushing %v\n", err)
}
return desc
}
generateImage := func(subject *ocispec.Descriptor, mediaType string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
generateImage := func(subject *ocispec.Descriptor, mediaType string, config ocispec.Descriptor, layers ...ocispec.Descriptor) ocispec.Descriptor {
manifest := ocispec.Manifest{
MediaType: mediaType,
Subject: subject,
Expand All @@ -55,40 +60,32 @@ func TestSuccessors(t *testing.T) {
if err != nil {
t.Fatal(err)
}
appendBlob(mediaType, manifestJSON)
return appendBlob(mediaType, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
generateIndex := func(manifests ...ocispec.Descriptor) ocispec.Descriptor {
index := ocispec.Index{
Manifests: manifests,
}
manifestJSON, err := json.Marshal(index)
indexJSON, err := json.Marshal(index)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageIndex, manifestJSON)
}
const (
subject = iota
config
ociImage
dockerImage
index
)
appendBlob(ocispec.MediaTypeImageLayer, []byte("blob"))
imageType := "test.image"
appendBlob(imageType, []byte("config content"))
generateImage(&descs[subject], ocispec.MediaTypeImageManifest, descs[config])
generateImage(&descs[subject], docker.MediaTypeManifest, descs[config])
generateIndex(descs[subject])
memory := memory.New()
ctx := context.Background()
for i := range descs {
if err := memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil {
t.Errorf("Error pushing %v\n", err)
}
return appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
}
fetcher := &fetcher{Fetcher: memory}

subject = appendBlob(ocispec.MediaTypeImageLayer, []byte("blob"))
imageType := "test.image"
config = appendBlob(imageType, []byte("config content"))
ociImage = generateImage(&subject, ocispec.MediaTypeImageManifest, config)
dockerImage = generateImage(&subject, docker.MediaTypeManifest, config)
index = generateIndex(subject)

return subject, config, ociImage, dockerImage, index, &contentFetcher{Fetcher: memoryStorage}
}

func TestSuccessors(t *testing.T) {
subject, config, ociImage, dockerImage, index, fetcher := newTestFetcher(t)
ctx := context.Background()
type args struct {
ctx context.Context
fetcher content.Fetcher
Expand All @@ -104,26 +101,69 @@ func TestSuccessors(t *testing.T) {
}{
{"should failed to get non-existent OCI image", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest}}, nil, nil, nil, true},
{"should failed to get non-existent docker image", args{ctx, fetcher, ocispec.Descriptor{MediaType: docker.MediaTypeManifest}}, nil, nil, nil, true},
{"should get success of a docker image", args{ctx, fetcher, descs[dockerImage]}, nil, &descs[subject], &descs[config], false},
{"should get success of an OCI image", args{ctx, fetcher, descs[ociImage]}, nil, &descs[subject], &descs[config], false},
{"should get success of an index", args{ctx, fetcher, descs[index]}, []ocispec.Descriptor{descs[subject]}, nil, nil, false},
{"should get success of a docker image", args{ctx, fetcher, dockerImage}, nil, &subject, &config, false},
{"should get success of an OCI image", args{ctx, fetcher, ociImage}, nil, &subject, &config, false},
{"should get success of an index", args{ctx, fetcher, index}, []ocispec.Descriptor{subject}, nil, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotNodes, gotSubject, gotConfig, err := Successors(tt.args.ctx, tt.args.fetcher, tt.args.node)
if (err != nil) != tt.wantErr {
t.Errorf("Successors() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("FilteredSuccessors() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotNodes, tt.wantNodes) {
t.Errorf("Successors() gotNodes = %v, want %v", gotNodes, tt.wantNodes)
t.Errorf("FilteredSuccessors() gotNodes = %v, want %v", gotNodes, tt.wantNodes)
}
if !reflect.DeepEqual(gotSubject, tt.wantSubject) {
t.Errorf("Successors() gotSubject = %v, want %v", gotSubject, tt.wantSubject)
t.Errorf("FilteredSuccessors() gotSubject = %v, want %v", gotSubject, tt.wantSubject)
}
if !reflect.DeepEqual(gotConfig, tt.wantConfig) {
t.Errorf("Successors() gotConfig = %v, want %v", gotConfig, tt.wantConfig)
t.Errorf("FilteredSuccessors() gotConfig = %v, want %v", gotConfig, tt.wantConfig)
}
})
}
}

func TestDescriptor_GetSuccessors(t *testing.T) {
subject, config, ociImage, _, _, fetcher := newTestFetcher(t)

allFilter := func(ocispec.Descriptor) bool {
return true
}
got, err := FilteredSuccessors(context.Background(), ociImage, fetcher, allFilter)
if nil != err {
t.Errorf("FilteredSuccessors unexpected error %v", err)
}
if len(got) != 2 {
t.Errorf("Expected 2 successors got %v", len(got))
}
if subject.Digest != got[0].Digest {
t.Errorf("FilteredSuccessors got %v, want %v", got[0], subject)
}
if config.Digest != got[1].Digest {
t.Errorf("FilteredSuccessors got %v, want %v", got[1], subject)
}

noConfig := func(desc ocispec.Descriptor) bool {
return desc.Digest != config.Digest
}
got, err = FilteredSuccessors(context.Background(), ociImage, fetcher, noConfig)
if nil != err {
t.Errorf("FilteredSuccessors unexpected error %v", err)
}
if len(got) != 1 {
t.Errorf("Expected 1 successors got %v", len(got))
}
if subject.Digest != got[0].Digest {
t.Errorf("FilteredSuccessors got %v, want %v", got[0], subject)
}

got, err = FilteredSuccessors(context.Background(), ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest}, fetcher, allFilter)
if nil == err {
t.Error("FilteredSuccessors expected error")
}
if got != nil {
t.Errorf("FilteredSuccessors unexpected %v", got)
}
}

0 comments on commit 06ba573

Please sign in to comment.