fix: minor fixes based on intermittent test failures (#3465)

1. preload busybox image to fix: https://github.com/project-zot/zot/actions/runs/18614431126/job/53077015870?pr=3465
2. stabilize test coverage in by using different error type: https://app.codecov.io/gh/project-zot/zot/pull/3444/indirect-changes
3. attempt to fx an intermitent sync test failure:
Failures:

  * /home/andaaron/zot/pkg/extensions/sync/sync_test.go
  Line 4857:
  Expected: digest.Digest("sha256:dc1377539a9db8bf077100bfa3118052feb6b5c67509ca09bdd841e4ac14c4cc")
  Actual:   digest.Digest("sha256:3a3fb31a422846a680f0a07b8b666bdcb1122d912d1adca79523c7bf2715996e")
  (Should equal)!

4. fix a race condition in sync by, I don't have a link, but this is the failure:

  * zotregistry.dev/zot/pkg/extensions/sync/sync_test.go
  Line 5963:
  Expected: 1
  Actual:   2
  (Should equal)!

1426 total assertions

--- FAIL: TestOnDemandPullsOnce (0.42s)
    sync_test.go:5921: Goroutine 0: Sending request to http://127.0.0.1:36421/v2/zot-test/manifests/0.0.1
    sync_test.go:5921: Goroutine 1: Sending request to http://127.0.0.1:36421/v2/zot-test/manifests/0.0.1
    sync_test.go:5921: Goroutine 4: Sending request to http://127.0.0.1:36421/v2/zot-test/manifests/0.0.1
    sync_test.go:5921: Goroutine 3: Sending request to http://127.0.0.1:36421/v2/zot-test/manifests/0.0.1
    sync_test.go:5921: Goroutine 2: Sending request to http://127.0.0.1:36421/v2/zot-test/manifests/0.0.1
FAIL
coverage: 21.4% of statements in ./...
FAIL	zotregistry.dev/zot/pkg/extensions/sync	255.189s

5. Fix flaky coverage in https://app.codecov.io/gh/project-zot/zot/pull/3465/indirect-changes

6. Stability fix for https://github.com/project-zot/zot/actions/runs/18632536285/job/53119244557?pr=3465

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
This commit is contained in:
Andrei Aaron
2025-10-20 03:59:32 +03:00
committed by GitHub
parent dfb5d1df54
commit 1fb2b67419
8 changed files with 152 additions and 22 deletions
+133 -4
View File
@@ -8,6 +8,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"testing"
"time"
@@ -125,17 +126,18 @@ func TestService(t *testing.T) {
Convey("test syncImage ReferrerList error with OnlySigned", t, func() {
onlySigned := true
conf := syncconf.RegistryConfig{
URLs: []string{"http://invalid-registry-that-does-not-exist:9999"},
URLs: []string{"http://localhost"},
OnlySigned: &onlySigned,
}
service, err := New(conf, "", nil, os.TempDir(), storage.StoreController{}, mocks.MetaDBMock{}, log.NewTestLogger())
So(err, ShouldBeNil)
// Create a mock remote that returns necessary data
// Create a mock remote that returns an invalid reference to trigger ReferrerList error
mockRemote := &mocks.SyncRemoteMock{
GetImageReferenceFn: func(repo string, tag string) (ref.Ref, error) {
return ref.New("invalid-registry-that-does-not-exist:9999/" + repo + ":" + tag)
// Return an invalid reference that will cause ReferrerList to fail with "ref is not set" error
return ref.Ref{}, nil
},
GetDigestFn: func(ctx context.Context, repo, tag string) (godigest.Digest, error) {
return godigest.Digest("sha256:abc123"), nil
@@ -154,8 +156,9 @@ func TestService(t *testing.T) {
ctx := context.Background()
err = service.syncImage(ctx, "localrepo", "remoterepo", "tag1", []string{}, true)
// We expect an error when ReferrerList fails (network/connection error in this case)
// We expect an error when ReferrerList fails with "ref is not set" error
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "ref is not set")
})
Convey("test LoadOrStore continue path by pre-populating requestStore", t, func() {
@@ -743,6 +746,132 @@ func TestDestinationRegistry(t *testing.T) {
So(err, ShouldNotBeNil)
})
Convey("trigger GetBlobContent error on manifest within image index in copyManifest()", func() {
// This test specifically targets the error where GetBlobContent fails for a manifest
// that is part of an image index.
// Create a destination registry using the existing syncImgStore as temp storage
storeController := storage.StoreController{DefaultStore: syncImgStore}
registry := NewDestinationRegistry(storeController, storeController, nil, log)
// Get an image reference - this will create a temp session directory
imageReference, err := registry.GetImageReference(repoName, "test-index")
So(err, ShouldBeNil)
// Get the temp image store from the image reference
tempImgStore := getImageStoreFromImageReference(repoName, imageReference, log)
// Create an image index with multiple manifests
var index ispec.Index
index.SchemaVersion = 2
index.MediaType = ispec.MediaTypeImageIndex
// Create child manifests
for i := 0; i < 2; i++ {
// Create blob content
content := []byte(fmt.Sprintf("this is blob %d", i))
buf := bytes.NewBuffer(content)
buflen := buf.Len()
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
// Upload blob
upload, err := tempImgStore.NewBlobUpload(repoName)
So(err, ShouldBeNil)
So(upload, ShouldNotBeEmpty)
blob, err := tempImgStore.PutBlobChunkStreamed(repoName, upload, buf)
So(err, ShouldBeNil)
So(blob, ShouldEqual, buflen)
err = tempImgStore.FinishBlobUpload(repoName, upload, buf, digest)
So(err, ShouldBeNil)
// Create config blob
cblob := []byte(fmt.Sprintf(`{"architecture":"amd64","os":"linux","config":{"User":"test%d"}}`, i))
cdigest := godigest.FromBytes(cblob)
So(cdigest, ShouldNotBeNil)
upload, err = tempImgStore.NewBlobUpload(repoName)
So(err, ShouldBeNil)
So(upload, ShouldNotBeEmpty)
cbuf := bytes.NewBuffer(cblob)
blob, err = tempImgStore.PutBlobChunkStreamed(repoName, upload, cbuf)
So(err, ShouldBeNil)
So(blob, ShouldEqual, len(cblob))
err = tempImgStore.FinishBlobUpload(repoName, upload, cbuf, cdigest)
So(err, ShouldBeNil)
// Create a manifest
manifest := ispec.Manifest{
Config: ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Digest: cdigest,
Size: int64(len(cblob)),
},
Layers: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageLayer,
Digest: digest,
Size: int64(buflen),
},
},
}
manifest.SchemaVersion = 2
manifestContent, err := json.Marshal(manifest)
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestContent)
So(manifestDigest, ShouldNotBeNil)
// Store the manifest in the temp image store
_, _, err = tempImgStore.PutImageManifest(repoName, manifestDigest.String(), ispec.MediaTypeImageManifest, manifestContent)
So(err, ShouldBeNil)
// Add to index
index.Manifests = append(index.Manifests, ispec.Descriptor{
Digest: manifestDigest,
MediaType: ispec.MediaTypeImageManifest,
Size: int64(len(manifestContent)),
})
}
// Create the index manifest
indexContent, err := json.Marshal(index)
So(err, ShouldBeNil)
indexDigest := godigest.FromBytes(indexContent)
So(indexDigest, ShouldNotBeNil)
// Store the index manifest in the temp image store
_, _, err = tempImgStore.PutImageManifest(repoName, indexDigest.String(), ispec.MediaTypeImageIndex, indexContent)
So(err, ShouldBeNil)
// Now remove one of the child manifest blobs to trigger the error
childManifestDigest := index.Manifests[1].Digest
err = os.Remove(tempImgStore.BlobPath(repoName, childManifestDigest))
So(err, ShouldBeNil)
// Create a descriptor for the index manifest
desc := ispec.Descriptor{
Digest: indexDigest,
MediaType: ispec.MediaTypeImageIndex,
Size: int64(len(indexContent)),
}
// Initialize the seen slice
seen := &[]godigest.Digest{}
// Call copyManifest directly with the index manifest - this should trigger the error path at lines 234-239
// when it tries to get blob content for the child manifest with the removed blob
err = registry.(*DestinationRegistry).copyManifest(repoName, desc, indexDigest.String(), tempImgStore, seen)
// Verify the error is returned and contains the expected message
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "blob not found")
})
Convey("push image", func() {
imageReference, err := registry.GetImageReference(repoName, "2.0")
So(err, ShouldBeNil)