Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

Commit 49f92e9

Browse files
oglokvMaroon
authored andcommitted
Add unit tests for prefix aware scorer
Signed-off-by: Ricardo Noriega De Soto <[email protected]>
1 parent 64b5845 commit 49f92e9

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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 scheduling
18+
19+
import (
20+
"context"
21+
"testing"
22+
"time"
23+
24+
k8stypes "k8s.io/apimachinery/pkg/types"
25+
"sigs.k8s.io/controller-runtime/pkg/log"
26+
backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
27+
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
28+
)
29+
30+
func TestPrefixAwareScorer(t *testing.T) {
31+
ctx := context.Background()
32+
logger := log.FromContext(ctx)
33+
ctx = log.IntoContext(ctx, logger)
34+
35+
// Create a prefix store with test configuration
36+
prefixStore := NewPrefixStore(PrefixStoreConfig{
37+
MaxEntries: 100,
38+
MinPrefixLen: 3,
39+
MaxPrefixLen: 10,
40+
EntryTTL: 1 * time.Hour,
41+
})
42+
43+
// Create test pods
44+
pod1 := &types.PodMetrics{
45+
Pod: &backendmetrics.Pod{
46+
NamespacedName: k8stypes.NamespacedName{
47+
Name: "pod1",
48+
Namespace: "default",
49+
},
50+
},
51+
Metrics: &backendmetrics.Metrics{},
52+
}
53+
pod2 := &types.PodMetrics{
54+
Pod: &backendmetrics.Pod{
55+
NamespacedName: k8stypes.NamespacedName{
56+
Name: "pod2",
57+
Namespace: "default",
58+
},
59+
},
60+
Metrics: &backendmetrics.Metrics{},
61+
}
62+
63+
tests := []struct {
64+
name string
65+
weight float64
66+
prompt string
67+
modelName string
68+
prefixToAdd string
69+
podToAdd k8stypes.NamespacedName
70+
prefixModel string // Model name to use when adding the prefix
71+
expectedScores []float64
72+
}{
73+
{
74+
name: "no prompt",
75+
weight: 1.0,
76+
prompt: "",
77+
modelName: "model1",
78+
prefixToAdd: "hello",
79+
podToAdd: pod1.Pod.NamespacedName,
80+
prefixModel: "model1",
81+
expectedScores: []float64{0, 0}, // No prompt means zero scores
82+
},
83+
{
84+
name: "exact prefix match",
85+
weight: 1.0,
86+
prompt: "hello world",
87+
modelName: "model1",
88+
prefixToAdd: "hello",
89+
podToAdd: pod1.Pod.NamespacedName,
90+
prefixModel: "model1",
91+
expectedScores: []float64{1.0, 0}, // pod1 matches, pod2 doesn't
92+
},
93+
{
94+
name: "no prefix match",
95+
weight: 1.0,
96+
prompt: "goodbye",
97+
modelName: "model1",
98+
prefixToAdd: "hello",
99+
podToAdd: pod1.Pod.NamespacedName,
100+
prefixModel: "model1",
101+
expectedScores: []float64{0, 0}, // No matching prefix
102+
},
103+
{
104+
name: "different model name",
105+
weight: 1.0,
106+
prompt: "hello world",
107+
modelName: "model2", // Try to find with model2
108+
prefixToAdd: "hello",
109+
podToAdd: pod1.Pod.NamespacedName,
110+
prefixModel: "model1", // But prefix was added with model1
111+
expectedScores: []float64{0, 0}, // Model name mismatch should result in no match
112+
},
113+
{
114+
name: "custom weight",
115+
weight: 0.5,
116+
prompt: "hello world",
117+
modelName: "model1",
118+
prefixToAdd: "hello",
119+
podToAdd: pod1.Pod.NamespacedName,
120+
prefixModel: "model1",
121+
expectedScores: []float64{0.5, 0}, // Weight affects score
122+
},
123+
}
124+
125+
for _, tt := range tests {
126+
t.Run(tt.name, func(t *testing.T) {
127+
// Reset prefix store for each test
128+
prefixStore = NewPrefixStore(PrefixStoreConfig{
129+
MaxEntries: 100,
130+
MinPrefixLen: 3,
131+
MaxPrefixLen: 10,
132+
EntryTTL: 1 * time.Hour,
133+
})
134+
135+
// Add prefix if specified
136+
if tt.prefixToAdd != "" {
137+
err := prefixStore.AddPrefix(ctx, tt.prefixToAdd, tt.podToAdd, tt.prefixModel)
138+
if err != nil {
139+
t.Fatalf("Failed to add prefix: %v", err)
140+
}
141+
}
142+
143+
// Create scorer with test weight
144+
scorer := NewPrefixAwareScorer(tt.weight, prefixStore)
145+
146+
// Create test context
147+
sCtx := types.NewContext(ctx, &types.LLMRequest{
148+
Prompt: tt.prompt,
149+
ResolvedTargetModel: tt.modelName,
150+
}, []*types.PodMetrics{})
151+
152+
// Score pods
153+
pods := []*types.PodMetrics{pod1, pod2}
154+
scores, err := scorer.ScoreTargets(sCtx, pods)
155+
if err != nil {
156+
t.Fatalf("Unexpected error: %v", err)
157+
}
158+
159+
// Verify scores
160+
if len(scores) != len(tt.expectedScores) {
161+
t.Fatalf("Expected %d scores, got %d", len(tt.expectedScores), len(scores))
162+
}
163+
164+
for i, score := range scores {
165+
if score.Score != tt.expectedScores[i] {
166+
t.Errorf("Pod %d: expected score %v, got %v", i, tt.expectedScores[i], score.Score)
167+
}
168+
}
169+
})
170+
}
171+
}

0 commit comments

Comments
 (0)