diff --git a/go.mod b/go.mod index bf3e67d90..4d6083943 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.23.7 require ( github.com/containerd/containerd/v2 v2.0.4 github.com/containerd/platforms v1.0.0-rc.1 - github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57 + github.com/docker/model-distribution v0.0.0-20250605145514-b377026db94a github.com/google/go-containerregistry v0.20.3 github.com/jaypipes/ghw v0.16.0 + github.com/mattn/go-shellwords v1.0.12 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/sirupsen/logrus v1.9.3 @@ -35,7 +36,6 @@ require ( github.com/jaypipes/pcidb v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/locker v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index 8f2290d64..21dbabc27 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57 h1:ZqfKknb+0/uJid8XLFwSl/osjE+WuS6o6I3dh3ZqO4U= -github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c= +github.com/docker/model-distribution v0.0.0-20250605145514-b377026db94a h1:rOxhNMoVwCchu79efuujmpL7kCbKKvzFFwvuWmJKbI4= +github.com/docker/model-distribution v0.0.0-20250605145514-b377026db94a/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= diff --git a/pkg/inference/models/manager_test.go b/pkg/inference/models/manager_test.go index 7edd357a4..0a3ab1ef6 100644 --- a/pkg/inference/models/manager_test.go +++ b/pkg/inference/models/manager_test.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "sync" "testing" "github.com/google/go-containerregistry/pkg/registry" @@ -20,6 +21,39 @@ import ( "github.com/sirupsen/logrus" ) +// mutexResponseRecorder wraps httptest.ResponseRecorder with a mutex for thread-safe access +type mutexResponseRecorder struct { + *httptest.ResponseRecorder + mu sync.Mutex +} + +func newMutexResponseRecorder() *mutexResponseRecorder { + return &mutexResponseRecorder{ + ResponseRecorder: httptest.NewRecorder(), + } +} + +// WriteHeader wraps the underlying WriteHeader with mutex protection +func (m *mutexResponseRecorder) WriteHeader(code int) { + m.mu.Lock() + defer m.mu.Unlock() + m.ResponseRecorder.WriteHeader(code) +} + +// Write wraps the underlying Write with mutex protection +func (m *mutexResponseRecorder) Write(b []byte) (int, error) { + m.mu.Lock() + defer m.mu.Unlock() + return m.ResponseRecorder.Write(b) +} + +// Header wraps the underlying Header with mutex protection +func (m *mutexResponseRecorder) Header() http.Header { + m.mu.Lock() + defer m.mu.Unlock() + return m.ResponseRecorder.Header() +} + // getProjectRoot returns the absolute path to the project root directory func getProjectRoot(t *testing.T) string { // Start from the current test file's directory @@ -119,7 +153,7 @@ func TestPullModel(t *testing.T) { r.Header.Set("Accept", tt.acceptHeader) } - w := httptest.NewRecorder() + w := newMutexResponseRecorder() err = m.PullModel(tag, r, w) if err != nil { t.Fatalf("Failed to pull model: %v", err) @@ -229,7 +263,7 @@ func TestHandleGetModel(t *testing.T) { // First pull the model if we're testing local access if !tt.remote && !strings.Contains(tt.modelName, "nonexistent") { r := httptest.NewRequest("POST", "/models/create", strings.NewReader(`{"from": "`+tt.modelName+`"}`)) - w := httptest.NewRecorder() + w := newMutexResponseRecorder() err = m.PullModel(tt.modelName, r, w) if err != nil { t.Fatalf("Failed to pull model: %v", err) @@ -242,7 +276,7 @@ func TestHandleGetModel(t *testing.T) { path += "?remote=true" } r := httptest.NewRequest("GET", path, nil) - w := httptest.NewRecorder() + w := newMutexResponseRecorder() // Set the path value for {name} so r.PathValue("name") works r.SetPathValue("name", tt.modelName)