Skip to content

Commit 7479e30

Browse files
authored
test: enhance plugin state test coverage and readability (#1349)
Signed-off-by: Kay Yan <[email protected]>
1 parent 2d7613c commit 7479e30

File tree

2 files changed

+142
-8
lines changed

2 files changed

+142
-8
lines changed

pkg/epp/plugins/plugin_state.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,23 @@ func (s *PluginState) cleanup(ctx context.Context) {
109109
log.FromContext(ctx).V(logutil.DEFAULT).Info("Shutting down plugin state cleanup")
110110
return
111111
case <-ticker.C:
112-
s.requestToLastAccessTime.Range(func(k, v any) bool {
113-
requestID := k.(string)
114-
lastAccessTime := v.(time.Time)
115-
if time.Since(lastAccessTime) > stalenessThreshold {
116-
s.Delete(requestID) // cleanup stale requests (this is safe in sync.Map)
117-
}
118-
return true
119-
})
112+
s.cleanStaleRequests()
120113
}
121114
}
115+
}
122116

117+
// cleanStaleRequests iterates through all requests and removes those that haven't been
118+
// accessed for longer than stalenessThreshold. This operation is safe to run concurrently
119+
// with other operations on the PluginState.
120+
func (s *PluginState) cleanStaleRequests() {
121+
s.requestToLastAccessTime.Range(func(k, v any) bool {
122+
requestID := k.(string)
123+
lastAccessTime := v.(time.Time)
124+
if time.Since(lastAccessTime) > stalenessThreshold {
125+
s.Delete(requestID) // cleanup stale requests (this is safe in sync.Map)
126+
}
127+
return true
128+
})
123129
}
124130

125131
// ReadPluginStateKey retrieves data with the given key from PluginState and asserts it to type T.

pkg/epp/plugins/plugin_state_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package plugins
18+
19+
import (
20+
"context"
21+
"testing"
22+
"time"
23+
24+
"github.com/stretchr/testify/assert"
25+
26+
logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
27+
)
28+
29+
// pluginTestData implements the StateData interface for testing purposes.
30+
// It provides a simple string value that can be stored and retrieved.
31+
type pluginTestData struct {
32+
value string
33+
}
34+
35+
// Clone implements the StateData interface, creating a deep copy of the data.
36+
func (d *pluginTestData) Clone() StateData {
37+
if d == nil {
38+
return nil
39+
}
40+
return &pluginTestData{value: d.value}
41+
}
42+
43+
// TestPluginState_ReadWrite verifies the basic operations of PluginState:
44+
// - Writing data for a request
45+
// - Reading the data back
46+
// - Deleting the data and confirming it's removed
47+
func TestPluginState_ReadWrite(t *testing.T) {
48+
ctx := logutil.NewTestLoggerIntoContext(context.Background())
49+
50+
state := NewPluginState(ctx)
51+
52+
req1 := "req1"
53+
key := StateKey("key")
54+
data1 := "bar1"
55+
req2 := "req2"
56+
data2 := "bar2"
57+
58+
// Write data to the state storage
59+
state.Write(req1, key, &pluginTestData{value: data1})
60+
state.Write(req2, key, &pluginTestData{value: data2})
61+
62+
// Read back the req1 data and verify its content
63+
readData, err := state.Read(req1, key)
64+
assert.NoError(t, err)
65+
td, ok := readData.(*pluginTestData)
66+
assert.True(t, ok, "should be able to cast to pluginTestData")
67+
assert.Equal(t, data1, td.value)
68+
69+
// Delete the req2 data and verify it's removed
70+
state.Delete(req2)
71+
readData, err = state.Read(req2, key)
72+
assert.Equal(t, ErrNotFound, err)
73+
assert.Nil(t, readData, "expected no data after delete")
74+
75+
// Read back the req1 data and verify its content after the req2 deleted
76+
readData, err = state.Read(req1, key)
77+
assert.NoError(t, err)
78+
td, ok = readData.(*pluginTestData)
79+
assert.True(t, ok, "should be able to cast to pluginTestData")
80+
assert.Equal(t, data1, td.value)
81+
}
82+
83+
// TestReadPluginStateKey tests the generic helper function ReadPluginStateKey which provides
84+
// type-safe access to stored data. It verifies:
85+
// - Successful type assertion and data retrieval
86+
// - Error handling for non-existent keys
87+
func TestReadPluginStateKey(t *testing.T) {
88+
ctx := logutil.NewTestLoggerIntoContext(context.Background())
89+
state := NewPluginState(ctx)
90+
91+
requestID := "req-1"
92+
key := StateKey("foo")
93+
data := &pluginTestData{value: "bar"}
94+
95+
state.Write(requestID, key, data)
96+
97+
// Read
98+
val, err := ReadPluginStateKey[*pluginTestData](state, requestID, key)
99+
assert.NoError(t, err)
100+
assert.Equal(t, "bar", val.value)
101+
102+
// Not Found
103+
_, err = ReadPluginStateKey[*pluginTestData](state, "not-exist", key)
104+
assert.Equal(t, ErrNotFound, err)
105+
}
106+
107+
// TestPluginState_Cleanup verifies the automatic cleanup of stale data.
108+
// It tests that data which hasn't been accessed for longer than stalenessThreshold
109+
// is properly removed from the storage.
110+
func TestPluginState_Cleanup(t *testing.T) {
111+
ctx := logutil.NewTestLoggerIntoContext(context.Background())
112+
113+
state := NewPluginState(ctx)
114+
115+
requestID := "req-stale"
116+
key := StateKey("foo")
117+
data := &pluginTestData{value: "bar"}
118+
119+
state.Write(requestID, key, data)
120+
121+
// Manually set last access time to far in the past
122+
state.requestToLastAccessTime.Store(requestID, time.Now().Add(-2*stalenessThreshold))
123+
// Manually CleanUp
124+
state.cleanStaleRequests()
125+
126+
_, err := state.Read(requestID, key)
127+
assert.Equal(t, ErrNotFound, err)
128+
}

0 commit comments

Comments
 (0)