github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/internal/platform/platform_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 platform
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	_ "crypto/sha256"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"reflect"
    26  	"testing"
    27  
    28  	"github.com/opcr-io/oras-go/v2/errdef"
    29  	"github.com/opcr-io/oras-go/v2/internal/cas"
    30  	"github.com/opcr-io/oras-go/v2/internal/docker"
    31  	"github.com/opencontainers/go-digest"
    32  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    33  )
    34  
    35  func TestMatch(t *testing.T) {
    36  	tests := []struct {
    37  		got       ocispec.Platform
    38  		want      ocispec.Platform
    39  		isMatched bool
    40  	}{{
    41  		ocispec.Platform{Architecture: "amd64", OS: "linux"},
    42  		ocispec.Platform{Architecture: "amd64", OS: "linux"},
    43  		true,
    44  	}, {
    45  		ocispec.Platform{Architecture: "amd64", OS: "linux"},
    46  		ocispec.Platform{Architecture: "amd64", OS: "LINUX"},
    47  		false,
    48  	}, {
    49  		ocispec.Platform{Architecture: "amd64", OS: "linux"},
    50  		ocispec.Platform{Architecture: "arm64", OS: "linux"},
    51  		false,
    52  	}, {
    53  		ocispec.Platform{Architecture: "arm", OS: "linux"},
    54  		ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"},
    55  		false,
    56  	}, {
    57  		ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"},
    58  		ocispec.Platform{Architecture: "arm", OS: "linux"},
    59  		true,
    60  	}, {
    61  		ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"},
    62  		ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"},
    63  		true,
    64  	}, {
    65  		ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"},
    66  		ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.700"},
    67  		false,
    68  	}, {
    69  		ocispec.Platform{Architecture: "amd64", OS: "windows"},
    70  		ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"},
    71  		false,
    72  	}, {
    73  		ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"},
    74  		ocispec.Platform{Architecture: "amd64", OS: "windows"},
    75  		true,
    76  	}, {
    77  		ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"},
    78  		ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"},
    79  		true,
    80  	}, {
    81  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "d"}},
    82  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "c"}},
    83  		false,
    84  	}, {
    85  		ocispec.Platform{Architecture: "arm", OS: "linux"},
    86  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a"}},
    87  		false,
    88  	}, {
    89  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a"}},
    90  		ocispec.Platform{Architecture: "arm", OS: "linux"},
    91  		true,
    92  	}, {
    93  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "b"}},
    94  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "b"}},
    95  		true,
    96  	}, {
    97  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "d", "c", "b"}},
    98  		ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"d", "c", "a", "b"}},
    99  		true,
   100  	}}
   101  
   102  	for _, tt := range tests {
   103  		gotPlatformJSON, _ := json.Marshal(tt.got)
   104  		wantPlatformJSON, _ := json.Marshal(tt.want)
   105  		name := string(gotPlatformJSON) + string(wantPlatformJSON)
   106  		t.Run(name, func(t *testing.T) {
   107  			if actual := Match(&tt.got, &tt.want); actual != tt.isMatched {
   108  				t.Errorf("Match() = %v, want %v", actual, tt.isMatched)
   109  			}
   110  		})
   111  	}
   112  }
   113  
   114  func TestSelectManifest(t *testing.T) {
   115  	storage := cas.NewMemory()
   116  	arc_1 := "test-arc-1"
   117  	os_1 := "test-os-1"
   118  	variant_1 := "v1"
   119  	arc_2 := "test-arc-2"
   120  	os_2 := "test-os-2"
   121  	variant_2 := "v2"
   122  
   123  	// generate test content
   124  	var blobs [][]byte
   125  	var descs []ocispec.Descriptor
   126  	appendBlob := func(mediaType string, blob []byte) {
   127  		blobs = append(blobs, blob)
   128  		descs = append(descs, ocispec.Descriptor{
   129  			MediaType: mediaType,
   130  			Digest:    digest.FromBytes(blob),
   131  			Size:      int64(len(blob)),
   132  		})
   133  	}
   134  	appendManifest := func(arc, os, variant string, mediaType string, blob []byte) {
   135  		blobs = append(blobs, blob)
   136  		descs = append(descs, ocispec.Descriptor{
   137  			MediaType: mediaType,
   138  			Digest:    digest.FromBytes(blob),
   139  			Size:      int64(len(blob)),
   140  			Platform: &ocispec.Platform{
   141  				Architecture: arc,
   142  				OS:           os,
   143  				Variant:      variant,
   144  			},
   145  		})
   146  	}
   147  	generateManifest := func(arc, os, variant string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   148  		manifest := ocispec.Manifest{
   149  			Config: config,
   150  			Layers: layers,
   151  		}
   152  		manifestJSON, err := json.Marshal(manifest)
   153  		if err != nil {
   154  			t.Fatal(err)
   155  		}
   156  		appendManifest(arc, os, variant, ocispec.MediaTypeImageManifest, manifestJSON)
   157  	}
   158  	generateIndex := func(manifests ...ocispec.Descriptor) {
   159  		index := ocispec.Index{
   160  			Manifests: manifests,
   161  		}
   162  		indexJSON, err := json.Marshal(index)
   163  		if err != nil {
   164  			t.Fatal(err)
   165  		}
   166  		appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
   167  	}
   168  
   169  	appendBlob(ocispec.MediaTypeImageConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json",
   170  "created":"2022-07-29T08:13:55Z",
   171  "author":"test author",
   172  "architecture":"test-arc-1",
   173  "os":"test-os-1",
   174  "variant":"v1"}`)) // Blob 0
   175  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))            // Blob 1
   176  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))            // Blob 2
   177  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1:3]...) // Blob 3
   178  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello1"))         // Blob 4
   179  	generateManifest(arc_2, os_2, variant_1, descs[0], descs[4])      // Blob 5
   180  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello2"))         // Blob 6
   181  	generateManifest(arc_1, os_1, variant_2, descs[0], descs[6])      // Blob 7
   182  	generateIndex(descs[3], descs[5], descs[7])                       // Blob 8
   183  
   184  	ctx := context.Background()
   185  	for i := range blobs {
   186  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   187  		if err != nil {
   188  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   189  		}
   190  	}
   191  
   192  	// test SelectManifest on image index, only one matching manifest found
   193  	root := descs[8]
   194  	targetPlatform := ocispec.Platform{
   195  		Architecture: arc_2,
   196  		OS:           os_2,
   197  	}
   198  	wantDesc := descs[5]
   199  	gotDesc, err := SelectManifest(ctx, storage, root, &targetPlatform)
   200  	if err != nil {
   201  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, false)
   202  	}
   203  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   204  		t.Errorf("SelectManifest() = %v, want %v", gotDesc, wantDesc)
   205  	}
   206  
   207  	// test SelectManifest on image index,
   208  	// and multiple manifests match the required platform.
   209  	// Should return the first matching entry.
   210  	targetPlatform = ocispec.Platform{
   211  		Architecture: arc_1,
   212  		OS:           os_1,
   213  	}
   214  	wantDesc = descs[3]
   215  	gotDesc, err = SelectManifest(ctx, storage, root, &targetPlatform)
   216  	if err != nil {
   217  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, false)
   218  	}
   219  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   220  		t.Errorf("SelectManifest() = %v, want %v", gotDesc, wantDesc)
   221  	}
   222  
   223  	// test SelectManifest on manifest
   224  	root = descs[7]
   225  	targetPlatform = ocispec.Platform{
   226  		Architecture: arc_1,
   227  		OS:           os_1,
   228  	}
   229  	wantDesc = descs[7]
   230  	gotDesc, err = SelectManifest(ctx, storage, root, &targetPlatform)
   231  	if err != nil {
   232  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, false)
   233  	}
   234  	if !reflect.DeepEqual(gotDesc, wantDesc) {
   235  		t.Errorf("SelectManifest() = %v, want %v", gotDesc, wantDesc)
   236  	}
   237  
   238  	// test SelectManifest on manifest, but there is no matching node.
   239  	// Should return not found error.
   240  	root = descs[7]
   241  	targetPlatform = ocispec.Platform{
   242  		Architecture: arc_1,
   243  		OS:           os_1,
   244  		Variant:      variant_2,
   245  	}
   246  	_, err = SelectManifest(ctx, storage, root, &targetPlatform)
   247  	expected := fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound)
   248  	if err.Error() != expected {
   249  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected)
   250  	}
   251  
   252  	// test SelectManifest on manifest, but the node's media type is not
   253  	// supported. Should return unsupported error
   254  	targetPlatform = ocispec.Platform{
   255  		Architecture: arc_1,
   256  		OS:           os_1,
   257  	}
   258  	root = descs[1]
   259  	_, err = SelectManifest(ctx, storage, root, &targetPlatform)
   260  	if !errors.Is(err, errdef.ErrUnsupported) {
   261  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, errdef.ErrUnsupported)
   262  	}
   263  
   264  	// generate incorrect test content
   265  	blobs = nil
   266  	descs = nil
   267  	appendBlob(docker.MediaTypeConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json",
   268  "created":"2022-07-29T08:13:55Z",
   269  "author":"test author 1",
   270  "architecture":"test-arc-1",
   271  "os":"test-os-1",
   272  "variant":"v1"}`)) // Blob 0
   273  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo1"))      // Blob 1
   274  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2
   275  	generateIndex(descs[2])                                      // Blob 3
   276  
   277  	ctx = context.Background()
   278  	for i := range blobs {
   279  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   280  		if err != nil {
   281  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   282  		}
   283  	}
   284  
   285  	// test SelectManifest on manifest, but the manifest is
   286  	// invalid by having docker mediaType config in the manifest and oci
   287  	// mediaType in the image config. Should return error.
   288  	root = descs[2]
   289  	targetPlatform = ocispec.Platform{
   290  		Architecture: arc_1,
   291  		OS:           os_1,
   292  	}
   293  	_, err = SelectManifest(ctx, storage, root, &targetPlatform)
   294  	expected = fmt.Sprintf("fail to recognize platform from unknown config %s: expect %s", docker.MediaTypeConfig, ocispec.MediaTypeImageConfig)
   295  	if err.Error() != expected {
   296  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected)
   297  	}
   298  
   299  	// generate test content with null config blob
   300  	blobs = nil
   301  	descs = nil
   302  	appendBlob(ocispec.MediaTypeImageConfig, []byte("null"))     // Blob 0
   303  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo2"))      // Blob 1
   304  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2
   305  	generateIndex(descs[2])                                      // Blob 3
   306  
   307  	ctx = context.Background()
   308  	for i := range blobs {
   309  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   310  		if err != nil {
   311  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   312  		}
   313  	}
   314  
   315  	// test SelectManifest on manifest with null config blob,
   316  	// should return not found error.
   317  	root = descs[2]
   318  	targetPlatform = ocispec.Platform{
   319  		Architecture: arc_1,
   320  		OS:           os_1,
   321  	}
   322  	_, err = SelectManifest(ctx, storage, root, &targetPlatform)
   323  	expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound)
   324  	if err.Error() != expected {
   325  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected)
   326  	}
   327  
   328  	// generate test content with empty config blob
   329  	blobs = nil
   330  	descs = nil
   331  	appendBlob(ocispec.MediaTypeImageConfig, []byte(""))         // Blob 0
   332  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo3"))      // Blob 1
   333  	generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2
   334  	generateIndex(descs[2])                                      // Blob 3
   335  
   336  	ctx = context.Background()
   337  	for i := range blobs {
   338  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   339  		if err != nil {
   340  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   341  		}
   342  	}
   343  
   344  	// test SelectManifest on manifest with empty config blob
   345  	// should return not found error
   346  	root = descs[2]
   347  	targetPlatform = ocispec.Platform{
   348  		Architecture: arc_1,
   349  		OS:           os_1,
   350  	}
   351  	_, err = SelectManifest(ctx, storage, root, &targetPlatform)
   352  	expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound)
   353  	if err.Error() != expected {
   354  		t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected)
   355  	}
   356  }