Skip to content

Commit a2fd706

Browse files
EricMountaink8s-infra-cherrypick-robot
authored andcommitted
Test showing RuntimeHandlers in Status() are unordered
Signed-off-by: Eric Mountain <[email protected]>
1 parent 41bb88c commit a2fd706

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

internal/cri/server/status_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@
1717
package server
1818

1919
import (
20+
"context"
21+
"reflect"
2022
"testing"
23+
"unsafe"
2124

2225
"github.com/containerd/containerd/api/services/introspection/v1"
26+
containerd "github.com/containerd/containerd/v2/client"
27+
coreintrospection "github.com/containerd/containerd/v2/core/introspection"
28+
"github.com/google/uuid"
2329
"github.com/stretchr/testify/assert"
2430
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
2531
)
@@ -48,3 +54,87 @@ func TestRuntimeConditionContainerdHasNoDeprecationWarnings(t *testing.T) {
4854
Status: true,
4955
}, cond)
5056
}
57+
58+
// fakeIntrospectionService is a minimal stub that implements the
59+
// coreintrospection.Service. We need this because criService.Status()
60+
// invokes the client.IntrospectionService() method.
61+
type fakeIntrospectionService struct{}
62+
63+
var _ coreintrospection.Service = fakeIntrospectionService{}
64+
65+
func (fakeIntrospectionService) Plugins(ctx context.Context, _ ...string) (*introspection.PluginsResponse, error) {
66+
return &introspection.PluginsResponse{}, nil
67+
}
68+
69+
func (fakeIntrospectionService) Server(ctx context.Context) (*introspection.ServerResponse, error) {
70+
return &introspection.ServerResponse{}, nil
71+
}
72+
73+
func (fakeIntrospectionService) PluginInfo(ctx context.Context, _ string, _ string, _ any) (*introspection.PluginInfoResponse, error) {
74+
return &introspection.PluginInfoResponse{}, nil
75+
}
76+
77+
// newFakeContainerdClient returns a *containerd.Client with a stub
78+
// IntrospectionService injected via reflection. This avoids needing a real
79+
// gRPC connection while satisfying criService.Status().
80+
func newFakeContainerdClient() *containerd.Client {
81+
c := &containerd.Client{}
82+
sv := reflect.ValueOf(c).Elem().FieldByName("services")
83+
if !sv.IsValid() {
84+
return c
85+
}
86+
f := sv.FieldByName("introspectionService")
87+
if !f.IsValid() {
88+
return c
89+
}
90+
// Make the unexported/private field introspectionService settable
91+
f = reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
92+
f.Set(reflect.ValueOf(fakeIntrospectionService{}).Convert(f.Type()))
93+
return c
94+
}
95+
96+
// newStatusTestCRIService creates a minimal CRI service for testing
97+
func newStatusTestCRIService() *criService {
98+
return &criService{
99+
client: newFakeContainerdClient(),
100+
runtimeHandlers: make(map[string]*runtime.RuntimeHandler),
101+
}
102+
}
103+
104+
// TestStatusRuntimeHandlersOrdering checks that the runtime handlers
105+
// returned by Status() are in the same order every time
106+
func TestStatusRuntimeHandlersOrdering(t *testing.T) {
107+
c := newStatusTestCRIService()
108+
109+
// Forge many runtime handlers to lower risk of accidental stable
110+
// ordering on consecutive Status() calls
111+
const numHandlers = 100
112+
handlers := make(map[string]*runtime.RuntimeHandler, numHandlers)
113+
for range numHandlers {
114+
h := &runtime.RuntimeHandler{Name: "random-" + uuid.New().String()}
115+
handlers[h.Name] = h
116+
}
117+
c.runtimeHandlers = handlers
118+
119+
// Call Status() twice
120+
resp1, err := c.Status(context.Background(), &runtime.StatusRequest{})
121+
assert.NoError(t, err)
122+
assert.Len(t, resp1.RuntimeHandlers, len(handlers), "Unexpected number of runtime handlers")
123+
124+
resp2, err := c.Status(context.Background(), &runtime.StatusRequest{})
125+
assert.NoError(t, err)
126+
assert.Len(t, resp2.RuntimeHandlers, len(handlers), "Unexpected number of runtime handlers")
127+
128+
// Check runtime handlers are in the same order
129+
sameOrder := true
130+
for i := 0; i < len(resp1.RuntimeHandlers); i++ {
131+
if resp1.RuntimeHandlers[i].Name != resp2.RuntimeHandlers[i].Name {
132+
sameOrder = false
133+
break
134+
}
135+
}
136+
137+
// The test will fail if the order is the same, showing that the ordering is stable
138+
// In the current implementation, order is not stable
139+
assert.False(t, sameOrder, "RuntimeHandlers order is stable across Status() calls - unexpected")
140+
}

0 commit comments

Comments
 (0)