oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/content/memory/memory_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 memory 17 18 import ( 19 "bytes" 20 "context" 21 _ "crypto/sha256" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "reflect" 27 "strings" 28 "testing" 29 30 "github.com/opencontainers/go-digest" 31 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 32 "golang.org/x/sync/errgroup" 33 "oras.land/oras-go/v2" 34 "oras.land/oras-go/v2/content" 35 "oras.land/oras-go/v2/errdef" 36 "oras.land/oras-go/v2/internal/cas" 37 "oras.land/oras-go/v2/internal/resolver" 38 "oras.land/oras-go/v2/internal/spec" 39 ) 40 41 func TestStoreInterface(t *testing.T) { 42 var store interface{} = &Store{} 43 if _, ok := store.(oras.Target); !ok { 44 t.Error("&Store{} does not conform oras.Target") 45 } 46 if _, ok := store.(content.PredecessorFinder); !ok { 47 t.Error("&Store{} does not conform content.PredecessorFinder") 48 } 49 } 50 51 func TestStoreSuccess(t *testing.T) { 52 content := []byte("hello world") 53 desc := ocispec.Descriptor{ 54 MediaType: "test", 55 Digest: digest.FromBytes(content), 56 Size: int64(len(content)), 57 } 58 ref := "foobar" 59 60 s := New() 61 ctx := context.Background() 62 63 err := s.Push(ctx, desc, bytes.NewReader(content)) 64 if err != nil { 65 t.Fatal("Store.Push() error =", err) 66 } 67 68 err = s.Tag(ctx, desc, ref) 69 if err != nil { 70 t.Fatal("Store.Tag() error =", err) 71 } 72 73 gotDesc, err := s.Resolve(ctx, ref) 74 if err != nil { 75 t.Fatal("Store.Resolve() error =", err) 76 } 77 if !reflect.DeepEqual(gotDesc, desc) { 78 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 79 } 80 internalResolver := s.resolver.(*resolver.Memory) 81 if got := len(internalResolver.Map()); got != 1 { 82 t.Errorf("resolver.Map() = %v, want %v", got, 1) 83 } 84 85 exists, err := s.Exists(ctx, desc) 86 if err != nil { 87 t.Fatal("Store.Exists() error =", err) 88 } 89 if !exists { 90 t.Errorf("Store.Exists() = %v, want %v", exists, true) 91 } 92 93 rc, err := s.Fetch(ctx, desc) 94 if err != nil { 95 t.Fatal("Store.Fetch() error =", err) 96 } 97 got, err := io.ReadAll(rc) 98 if err != nil { 99 t.Fatal("Store.Fetch().Read() error =", err) 100 } 101 err = rc.Close() 102 if err != nil { 103 t.Error("Store.Fetch().Close() error =", err) 104 } 105 if !bytes.Equal(got, content) { 106 t.Errorf("Store.Fetch() = %v, want %v", got, content) 107 } 108 internalStorage := s.storage.(*cas.Memory) 109 if got := len(internalStorage.Map()); got != 1 { 110 t.Errorf("storage.Map() = %v, want %v", got, 1) 111 } 112 } 113 114 func TestStoreContentNotFound(t *testing.T) { 115 content := []byte("hello world") 116 desc := ocispec.Descriptor{ 117 MediaType: "test", 118 Digest: digest.FromBytes(content), 119 Size: int64(len(content)), 120 } 121 122 s := New() 123 ctx := context.Background() 124 125 exists, err := s.Exists(ctx, desc) 126 if err != nil { 127 t.Error("Store.Exists() error =", err) 128 } 129 if exists { 130 t.Errorf("Store.Exists() = %v, want %v", exists, false) 131 } 132 133 _, err = s.Fetch(ctx, desc) 134 if !errors.Is(err, errdef.ErrNotFound) { 135 t.Errorf("Store.Fetch() error = %v, want %v", err, errdef.ErrNotFound) 136 } 137 } 138 139 func TestStoreContentAlreadyExists(t *testing.T) { 140 content := []byte("hello world") 141 desc := ocispec.Descriptor{ 142 MediaType: "test", 143 Digest: digest.FromBytes(content), 144 Size: int64(len(content)), 145 } 146 147 s := New() 148 ctx := context.Background() 149 150 err := s.Push(ctx, desc, bytes.NewReader(content)) 151 if err != nil { 152 t.Fatal("Store.Push() error =", err) 153 } 154 155 err = s.Push(ctx, desc, bytes.NewReader(content)) 156 if !errors.Is(err, errdef.ErrAlreadyExists) { 157 t.Errorf("Store.Push() error = %v, want %v", err, errdef.ErrAlreadyExists) 158 } 159 } 160 161 func TestStoreContentBadPush(t *testing.T) { 162 content := []byte("hello world") 163 desc := ocispec.Descriptor{ 164 MediaType: "test", 165 Digest: digest.FromBytes(content), 166 Size: int64(len(content)), 167 } 168 169 s := New() 170 ctx := context.Background() 171 172 err := s.Push(ctx, desc, strings.NewReader("foobar")) 173 if err == nil { 174 t.Errorf("Store.Push() error = %v, wantErr %v", err, true) 175 } 176 } 177 178 func TestStoreTagNotFound(t *testing.T) { 179 ref := "foobar" 180 181 s := New() 182 ctx := context.Background() 183 184 _, err := s.Resolve(ctx, ref) 185 if !errors.Is(err, errdef.ErrNotFound) { 186 t.Errorf("Store.Resolve() error = %v, want %v", err, errdef.ErrNotFound) 187 } 188 } 189 190 func TestStoreTagUnknownContent(t *testing.T) { 191 content := []byte("hello world") 192 desc := ocispec.Descriptor{ 193 MediaType: "test", 194 Digest: digest.FromBytes(content), 195 Size: int64(len(content)), 196 } 197 ref := "foobar" 198 199 s := New() 200 ctx := context.Background() 201 202 err := s.Tag(ctx, desc, ref) 203 if !errors.Is(err, errdef.ErrNotFound) { 204 t.Errorf("Store.Resolve() error = %v, want %v", err, errdef.ErrNotFound) 205 } 206 } 207 208 func TestStoreRepeatTag(t *testing.T) { 209 generate := func(content []byte) ocispec.Descriptor { 210 return ocispec.Descriptor{ 211 MediaType: "test", 212 Digest: digest.FromBytes(content), 213 Size: int64(len(content)), 214 } 215 } 216 ref := "foobar" 217 218 s := New() 219 ctx := context.Background() 220 221 // get internal resolver 222 internalResolver := s.resolver.(*resolver.Memory) 223 224 // initial tag 225 content := []byte("hello world") 226 desc := generate(content) 227 err := s.Push(ctx, desc, bytes.NewReader(content)) 228 if err != nil { 229 t.Fatal("Store.Push() error =", err) 230 } 231 232 err = s.Tag(ctx, desc, ref) 233 if err != nil { 234 t.Fatal("Store.Tag() error =", err) 235 } 236 237 gotDesc, err := s.Resolve(ctx, ref) 238 if err != nil { 239 t.Fatal("Store.Resolve() error =", err) 240 } 241 if !reflect.DeepEqual(gotDesc, desc) { 242 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 243 } 244 if got := len(internalResolver.Map()); got != 1 { 245 t.Errorf("resolver.Map() = %v, want %v", got, 1) 246 } 247 248 // repeat tag 249 content = []byte("foo") 250 desc = generate(content) 251 err = s.Push(ctx, desc, bytes.NewReader(content)) 252 if err != nil { 253 t.Fatal("Store.Push() error =", err) 254 } 255 256 err = s.Tag(ctx, desc, ref) 257 if err != nil { 258 t.Fatal("Store.Tag() error =", err) 259 } 260 261 gotDesc, err = s.Resolve(ctx, ref) 262 if err != nil { 263 t.Fatal("Store.Resolve() error =", err) 264 } 265 if !reflect.DeepEqual(gotDesc, desc) { 266 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 267 } 268 if got := len(internalResolver.Map()); got != 1 { 269 t.Errorf("resolver.Map() = %v, want %v", got, 1) 270 } 271 272 // repeat tag 273 content = []byte("bar") 274 desc = generate(content) 275 err = s.Push(ctx, desc, bytes.NewReader(content)) 276 if err != nil { 277 t.Fatal("Store.Push() error =", err) 278 } 279 280 err = s.Tag(ctx, desc, ref) 281 if err != nil { 282 t.Fatal("Store.Tag() error =", err) 283 } 284 285 gotDesc, err = s.Resolve(ctx, ref) 286 if err != nil { 287 t.Fatal("Store.Resolve() error =", err) 288 } 289 if !reflect.DeepEqual(gotDesc, desc) { 290 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 291 } 292 if got := len(internalResolver.Map()); got != 1 { 293 t.Errorf("resolver.Map() = %v, want %v", got, 1) 294 } 295 } 296 297 func TestStorePredecessors(t *testing.T) { 298 s := New() 299 ctx := context.Background() 300 301 // generate test content 302 var blobs [][]byte 303 var descs []ocispec.Descriptor 304 appendBlob := func(mediaType string, blob []byte) { 305 blobs = append(blobs, blob) 306 descs = append(descs, ocispec.Descriptor{ 307 MediaType: mediaType, 308 Digest: digest.FromBytes(blob), 309 Size: int64(len(blob)), 310 }) 311 } 312 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 313 manifest := ocispec.Manifest{ 314 Config: config, 315 Layers: layers, 316 } 317 manifestJSON, err := json.Marshal(manifest) 318 if err != nil { 319 t.Fatal(err) 320 } 321 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 322 } 323 generateIndex := func(manifests ...ocispec.Descriptor) { 324 index := ocispec.Index{ 325 Manifests: manifests, 326 } 327 indexJSON, err := json.Marshal(index) 328 if err != nil { 329 t.Fatal(err) 330 } 331 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 332 } 333 generateArtifactManifest := func(subject ocispec.Descriptor, blobs ...ocispec.Descriptor) { 334 var manifest spec.Artifact 335 manifest.Subject = &subject 336 manifest.Blobs = append(manifest.Blobs, blobs...) 337 manifestJSON, err := json.Marshal(manifest) 338 if err != nil { 339 t.Fatal(err) 340 } 341 appendBlob(spec.MediaTypeArtifactManifest, manifestJSON) 342 } 343 344 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 345 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 346 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 347 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 348 generateManifest(descs[0], descs[1:3]...) // Blob 4 349 generateManifest(descs[0], descs[3]) // Blob 5 350 generateManifest(descs[0], descs[1:4]...) // Blob 6 351 generateIndex(descs[4:6]...) // Blob 7 352 generateIndex(descs[6]) // Blob 8 353 generateIndex() // Blob 9 354 generateIndex(descs[7:10]...) // Blob 10 355 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_1")) // Blob 11 356 generateArtifactManifest(descs[6], descs[11]) // Blob 12 357 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_2")) // Blob 13 358 generateArtifactManifest(descs[10], descs[13]) // Blob 14 359 360 eg, egCtx := errgroup.WithContext(ctx) 361 for i := range blobs { 362 eg.Go(func(i int) func() error { 363 return func() error { 364 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 365 if err != nil { 366 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 367 } 368 return nil 369 } 370 }(i)) 371 } 372 if err := eg.Wait(); err != nil { 373 t.Fatal(err) 374 } 375 376 // verify predecessors 377 wants := [][]ocispec.Descriptor{ 378 descs[4:7], // Blob 0 379 {descs[4], descs[6]}, // Blob 1 380 {descs[4], descs[6]}, // Blob 2 381 {descs[5], descs[6]}, // Blob 3 382 {descs[7]}, // Blob 4 383 {descs[7]}, // Blob 5 384 {descs[8], descs[12]}, // Blob 6 385 {descs[10]}, // Blob 7 386 {descs[10]}, // Blob 8 387 {descs[10]}, // Blob 9 388 {descs[14]}, // Blob 10 389 {descs[12]}, // Blob 11 390 nil, // Blob 12 391 {descs[14]}, // Blob 13 392 nil, // Blob 14 393 } 394 for i, want := range wants { 395 predecessors, err := s.Predecessors(ctx, descs[i]) 396 if err != nil { 397 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 398 } 399 if !equalDescriptorSet(predecessors, want) { 400 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 401 } 402 } 403 } 404 405 func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool { 406 if len(actual) != len(expected) { 407 return false 408 } 409 contains := func(node ocispec.Descriptor) bool { 410 for _, candidate := range actual { 411 if reflect.DeepEqual(candidate, node) { 412 return true 413 } 414 } 415 return false 416 } 417 for _, node := range expected { 418 if !contains(node) { 419 return false 420 } 421 } 422 return true 423 }