github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/casext/map_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 SUSE LLC
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package casext
    19  
    20  import (
    21  	crand "crypto/rand"
    22  	"io"
    23  	"math/rand"
    24  	"reflect"
    25  	"testing"
    26  
    27  	"github.com/mohae/deepcopy"
    28  	"github.com/opencontainers/go-digest"
    29  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    30  	"github.com/opencontainers/umoci/pkg/testutils"
    31  )
    32  
    33  func descriptorPtr(d ispec.Descriptor) *ispec.Descriptor { return &d }
    34  
    35  func randomDescriptor(t *testing.T) ispec.Descriptor {
    36  	var descriptor ispec.Descriptor
    37  
    38  	// Generate a random digest and length.
    39  	descriptor.Size = int64(rand.Intn(512 * 1024))
    40  	digester := digest.SHA256.Digester()
    41  	io.CopyN(digester.Hash(), crand.Reader, descriptor.Size)
    42  	descriptor.Digest = digester.Digest()
    43  
    44  	// Generate a random number of annotations, with random key/values.
    45  	descriptor.Annotations = map[string]string{}
    46  	n := rand.Intn(32)
    47  	for i := 0; i < n; i++ {
    48  		descriptor.Annotations[testutils.RandomString(32)] = testutils.RandomString(32)
    49  	}
    50  
    51  	return descriptor
    52  }
    53  
    54  // Make sure that an identity mapping doesn't change the struct, and that it
    55  // actually does visit all of the descriptors once.
    56  func TestMapDescriptors_Identity(t *testing.T) {
    57  	// List of interfaces to use MapDescriptors on, as well as how many
    58  	// *unique* descriptors they contain.
    59  	ociList := []struct {
    60  		num int
    61  		obj interface{}
    62  	}{
    63  		// Make sure that "base" types work.
    64  		{
    65  			num: 0,
    66  			obj: nil,
    67  		},
    68  		{
    69  			num: 1,
    70  			obj: randomDescriptor(t),
    71  		},
    72  		{
    73  			num: 1,
    74  			obj: descriptorPtr(randomDescriptor(t)),
    75  		},
    76  		{
    77  			num: 3,
    78  			obj: []ispec.Descriptor{
    79  				randomDescriptor(t),
    80  				randomDescriptor(t),
    81  				randomDescriptor(t),
    82  			},
    83  		},
    84  		{
    85  			num: 7,
    86  			obj: []*ispec.Descriptor{
    87  				descriptorPtr(randomDescriptor(t)),
    88  				descriptorPtr(randomDescriptor(t)),
    89  				descriptorPtr(randomDescriptor(t)),
    90  				descriptorPtr(randomDescriptor(t)),
    91  				descriptorPtr(randomDescriptor(t)),
    92  				descriptorPtr(randomDescriptor(t)),
    93  				descriptorPtr(randomDescriptor(t)),
    94  			},
    95  		},
    96  		// Make sure official OCI structs work.
    97  		{
    98  			num: 7,
    99  			obj: ispec.Manifest{
   100  				MediaType: ispec.MediaTypeImageManifest,
   101  				Config:    randomDescriptor(t),
   102  				Layers: []ispec.Descriptor{
   103  					randomDescriptor(t),
   104  					randomDescriptor(t),
   105  					randomDescriptor(t),
   106  					randomDescriptor(t),
   107  					randomDescriptor(t),
   108  					randomDescriptor(t),
   109  				},
   110  			},
   111  		},
   112  		{
   113  			num: 2,
   114  			obj: ispec.Index{
   115  				MediaType: ispec.MediaTypeImageIndex,
   116  				Manifests: []ispec.Descriptor{
   117  					randomDescriptor(t),
   118  					randomDescriptor(t),
   119  				},
   120  			},
   121  		},
   122  		// Check that pointers also work.
   123  		{
   124  			num: 5,
   125  			obj: &ispec.Manifest{
   126  				MediaType: ispec.MediaTypeImageManifest,
   127  				Config:    randomDescriptor(t),
   128  				Layers: []ispec.Descriptor{
   129  					randomDescriptor(t),
   130  					randomDescriptor(t),
   131  					randomDescriptor(t),
   132  					randomDescriptor(t),
   133  				},
   134  			},
   135  		},
   136  		{
   137  			num: 9,
   138  			obj: &ispec.Index{
   139  				MediaType: ispec.MediaTypeImageIndex,
   140  				Manifests: []ispec.Descriptor{
   141  					randomDescriptor(t),
   142  					randomDescriptor(t),
   143  					randomDescriptor(t),
   144  					randomDescriptor(t),
   145  					randomDescriptor(t),
   146  					randomDescriptor(t),
   147  					randomDescriptor(t),
   148  					randomDescriptor(t),
   149  					randomDescriptor(t),
   150  				},
   151  			},
   152  		},
   153  		// Make sure that an empty []ispec.Descriptor works properly.
   154  		{
   155  			num: 0,
   156  			obj: []ispec.Descriptor{},
   157  		},
   158  		{
   159  			num: 1,
   160  			obj: ispec.Manifest{
   161  				MediaType: ispec.MediaTypeImageManifest,
   162  				Config:    randomDescriptor(t),
   163  				Layers:    nil,
   164  			},
   165  		},
   166  		{
   167  			num: 0,
   168  			obj: ispec.Index{
   169  				MediaType: ispec.MediaTypeImageIndex,
   170  				Manifests: []ispec.Descriptor{},
   171  			},
   172  		},
   173  		// TODO: Add support for descending into maps.
   174  	}
   175  
   176  	for idx, test := range ociList {
   177  		// Make a copy for later comparison.
   178  		original := deepcopy.Copy(test.obj)
   179  
   180  		foundSet := map[digest.Digest]int{}
   181  
   182  		if err := MapDescriptors(test.obj, func(descriptor ispec.Descriptor) ispec.Descriptor {
   183  			foundSet[descriptor.Digest]++
   184  			return descriptor
   185  		}); err != nil {
   186  			t.Errorf("MapDescriptors(%d) unexpected error: %v", idx, err)
   187  			continue
   188  		}
   189  
   190  		// Make sure that we hit everything uniquely.
   191  		found := 0
   192  		for d, n := range foundSet {
   193  			found++
   194  			if n != 1 {
   195  				t.Errorf("MapDescriptors(%d) hit a descriptor more than once: %#v hit %d times", idx, d, n)
   196  			}
   197  		}
   198  		if found != test.num {
   199  			t.Errorf("MapDescriptors(%d) didn't hit the right number, expected %d got %d", idx, test.num, found)
   200  		}
   201  
   202  		if !reflect.DeepEqual(original, test.obj) {
   203  			t.Errorf("MapDescriptors(%d) descriptors were modified with identity mapping, expected %#v got %#v", idx, original, test.obj)
   204  		}
   205  	}
   206  }
   207  
   208  // Make sure that it is possible to modify a variety of different interfaces.
   209  func TestMapDescriptors_ModifyOCI(t *testing.T) {
   210  	// List of interfaces to use MapDescriptors on.
   211  	ociList := []struct {
   212  		obj interface{}
   213  	}{
   214  		// Make sure that "base" types work.
   215  		{
   216  			obj: descriptorPtr(randomDescriptor(t)),
   217  		},
   218  		{
   219  			obj: []ispec.Descriptor{
   220  				randomDescriptor(t),
   221  				randomDescriptor(t),
   222  				randomDescriptor(t),
   223  			},
   224  		},
   225  		{
   226  			obj: []*ispec.Descriptor{
   227  				descriptorPtr(randomDescriptor(t)),
   228  				descriptorPtr(randomDescriptor(t)),
   229  			},
   230  		},
   231  		// TODO: Add the ability to mutate map keys and values.
   232  		// Make sure official OCI structs work.
   233  		{
   234  			obj: &ispec.Manifest{
   235  				MediaType: ispec.MediaTypeImageManifest,
   236  				Config:    randomDescriptor(t),
   237  				Layers: []ispec.Descriptor{
   238  					randomDescriptor(t),
   239  					randomDescriptor(t),
   240  					randomDescriptor(t),
   241  					randomDescriptor(t),
   242  					randomDescriptor(t),
   243  					randomDescriptor(t),
   244  				},
   245  			},
   246  		},
   247  		{
   248  			obj: ispec.Index{
   249  				MediaType: ispec.MediaTypeImageIndex,
   250  				Manifests: []ispec.Descriptor{
   251  					randomDescriptor(t),
   252  					randomDescriptor(t),
   253  				},
   254  			},
   255  		},
   256  		{
   257  			obj: &ispec.Index{
   258  				MediaType: ispec.MediaTypeImageIndex,
   259  				Manifests: []ispec.Descriptor{
   260  					randomDescriptor(t),
   261  					randomDescriptor(t),
   262  				},
   263  			},
   264  		},
   265  	}
   266  
   267  	for idx, test := range ociList {
   268  		// Make a copy for later comparison.
   269  		original := deepcopy.Copy(test.obj)
   270  
   271  		if err := MapDescriptors(&test.obj, func(descriptor ispec.Descriptor) ispec.Descriptor {
   272  			// Create an entirely new descriptor.
   273  			return randomDescriptor(t)
   274  		}); err != nil {
   275  			t.Errorf("MapDescriptors(%d) unexpected error: %v", idx, err)
   276  			continue
   277  		}
   278  
   279  		if reflect.DeepEqual(original, test.obj) {
   280  			t.Errorf("MapDescriptors(%d) descriptor was unmodified when replacing with a random descriptor!", idx)
   281  		}
   282  	}
   283  }
   284  
   285  // TODO: We should be able to rewrite non-OCI structs in the future.