Added tests for metrics and framework package of pkg/scheduler

Signed-off-by: Anuj Agrawal <anujagrawal380@gmail.com>
This commit is contained in:
Anuj Agrawal 2024-10-05 21:11:32 +05:30
parent 6b18b6e120
commit 39d3ef3b11
3 changed files with 545 additions and 0 deletions

View File

@ -0,0 +1,260 @@
/*
Copyright 2024 The Karmada Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"errors"
"sort"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPluginToResult_Merge(t *testing.T) {
tests := []struct {
name string
results PluginToResult
want *Result
}{
{
name: "empty results",
results: PluginToResult{},
want: nil,
},
{
name: "all success results",
results: PluginToResult{
"plugin1": NewResult(Success),
"plugin2": NewResult(Success),
},
want: NewResult(Success),
},
{
name: "mixed results with unschedulable",
results: PluginToResult{
"plugin1": NewResult(Success),
"plugin2": NewResult(Unschedulable, "reason1"),
"plugin3": NewResult(Success),
},
want: NewResult(Unschedulable, "reason1"),
},
{
name: "mixed results with error",
results: PluginToResult{
"plugin1": NewResult(Success),
"plugin2": NewResult(Unschedulable, "reason1"),
"plugin3": NewResult(Error, "error occurred"),
},
want: NewResult(Error, "reason1", "error occurred"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.results.Merge()
if tt.want == nil {
assert.Nil(t, got)
} else {
assert.NotNil(t, got)
assert.Equal(t, tt.want.code, got.code)
// Sort the reasons before comparing
sortedWantReasons := make([]string, len(tt.want.reasons))
copy(sortedWantReasons, tt.want.reasons)
sort.Strings(sortedWantReasons)
sortedGotReasons := make([]string, len(got.reasons))
copy(sortedGotReasons, got.reasons)
sort.Strings(sortedGotReasons)
assert.Equal(t, sortedWantReasons, sortedGotReasons)
if tt.want.err != nil {
assert.Error(t, got.err)
} else {
assert.NoError(t, got.err)
}
}
})
}
}
func TestResult_IsSuccess(t *testing.T) {
tests := []struct {
name string
result *Result
want bool
}{
{
name: "nil result",
result: nil,
want: true,
},
{
name: "success result",
result: NewResult(Success),
want: true,
},
{
name: "unschedulable result",
result: NewResult(Unschedulable),
want: false,
},
{
name: "error result",
result: NewResult(Error),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.result.IsSuccess())
})
}
}
func TestResult_AsError(t *testing.T) {
tests := []struct {
name string
result *Result
wantErr bool
errorMsg string
}{
{
name: "success result",
result: NewResult(Success),
wantErr: false,
},
{
name: "unschedulable result",
result: NewResult(Unschedulable, "reason1", "reason2"),
wantErr: true,
errorMsg: "reason1, reason2",
},
{
name: "error result",
result: NewResult(Error, "error occurred"),
wantErr: true,
errorMsg: "error occurred",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.result.AsError()
if tt.wantErr {
assert.Error(t, err)
assert.Equal(t, tt.errorMsg, err.Error())
} else {
assert.NoError(t, err)
}
})
}
}
func TestResult_AsResult(t *testing.T) {
tests := []struct {
name string
err error
wantCode Code
wantReasons []string
}{
{
name: "non-nil error",
err: errors.New("test error"),
wantCode: Error,
wantReasons: []string{"test error"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := AsResult(tt.err)
assert.Equal(t, tt.wantCode, got.code)
assert.Equal(t, tt.wantReasons, got.reasons)
assert.Equal(t, tt.err, got.err)
})
}
}
func TestResult_Code(t *testing.T) {
tests := []struct {
name string
result *Result
want Code
}{
{
name: "nil result",
result: nil,
want: Success,
},
{
name: "success result",
result: NewResult(Success),
want: Success,
},
{
name: "unschedulable result",
result: NewResult(Unschedulable),
want: Unschedulable,
},
{
name: "error result",
result: NewResult(Error),
want: Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.result.Code()
assert.Equal(t, tt.want, got)
})
}
}
func TestCode_String(t *testing.T) {
tests := []struct {
name string
code Code
want string
}{
{
name: "Success code",
code: Success,
want: "Success",
},
{
name: "Unschedulable code",
code: Unschedulable,
want: "Unschedulable",
},
{
name: "Error code",
code: Error,
want: "Error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.code.String()
assert.Equal(t, tt.want, got)
})
}
}

View File

@ -0,0 +1,183 @@
/*
Copyright 2024 The Karmada Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
)
func TestNewClusterInfo(t *testing.T) {
testCases := []struct {
name string
cluster *clusterv1alpha1.Cluster
want *ClusterInfo
}{
{
name: "Create ClusterInfo with valid cluster",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
want: &ClusterInfo{
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
},
},
{
name: "Create ClusterInfo with nil cluster",
cluster: nil,
want: &ClusterInfo{cluster: nil},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := NewClusterInfo(tc.cluster)
assert.Equal(t, tc.want, got)
})
}
}
func TestClusterInfo_Cluster(t *testing.T) {
testCases := []struct {
name string
clusterInfo *ClusterInfo
want *clusterv1alpha1.Cluster
}{
{
name: "Get cluster from valid ClusterInfo",
clusterInfo: &ClusterInfo{
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
},
want: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
},
{
name: "Get cluster from nil ClusterInfo",
clusterInfo: nil,
want: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := tc.clusterInfo.Cluster()
assert.Equal(t, tc.want, got)
})
}
}
func TestFitError_Error(t *testing.T) {
testCases := []struct {
name string
fitError FitError
expectedOutput string
expectedReasons []string
}{
{
name: "No clusters available",
fitError: FitError{
NumAllClusters: 0,
Diagnosis: Diagnosis{ClusterToResultMap: ClusterToResultMap{}},
},
expectedOutput: "0/0 clusters are available: no cluster exists.",
expectedReasons: []string{},
},
{
name: "Multiple reasons for unavailability",
fitError: FitError{
NumAllClusters: 3,
Diagnosis: Diagnosis{
ClusterToResultMap: ClusterToResultMap{
"cluster1": &Result{reasons: []string{"insufficient CPU", "insufficient memory"}},
"cluster2": &Result{reasons: []string{"insufficient CPU"}},
"cluster3": &Result{reasons: []string{"taint mismatch"}},
},
},
},
expectedOutput: "0/3 clusters are available:",
expectedReasons: []string{
"2 insufficient CPU",
"1 insufficient memory",
"1 taint mismatch",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := tc.fitError.Error()
// Check if the error message starts with the expected output
assert.True(t, strings.HasPrefix(got, tc.expectedOutput), "Error message should start with expected output")
if len(tc.expectedReasons) > 0 {
// Check each reason
for _, reason := range tc.expectedReasons {
assert.Contains(t, got, reason, "Error message should contain the reason: %s", reason)
}
// Check the total number of reasons
gotReasons := strings.Split(strings.TrimPrefix(got, tc.expectedOutput), ",")
assert.Equal(t, len(tc.expectedReasons), len(gotReasons), "Number of reasons should match")
} else {
// If no reasons are expected, the got message should exactly match the expected output
assert.Equal(t, tc.expectedOutput, got, "Error message should exactly match expected output when no reasons are provided")
}
})
}
}
func TestUnschedulableError_Error(t *testing.T) {
testCases := []struct {
name string
message string
}{
{
name: "Unschedulable due to insufficient resources",
message: "Insufficient CPU in all clusters",
},
{
name: "Unschedulable due to taint mismatch",
message: "No cluster matches required tolerations",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
unschedulableErr := UnschedulableError{Message: tc.message}
assert.Equal(t, tc.message, unschedulableErr.Error())
})
}
}

View File

@ -0,0 +1,102 @@
/*
Copyright 2024 The Karmada Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metrics
import (
"errors"
"testing"
"time"
)
func TestBindingSchedule(t *testing.T) {
tests := []struct {
name string
scheduleType string
duration float64
err error
}{
{
name: "Successful schedule",
scheduleType: "test",
duration: 1.5,
err: nil,
},
{
name: "Failed schedule",
scheduleType: "test",
duration: 0.5,
err: errors.New("schedule failed"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
// We can't easily test the metric values directly, so we'll just ensure the function doesn't panic
BindingSchedule(tt.scheduleType, tt.duration, tt.err)
})
}
}
func TestScheduleStep(t *testing.T) {
tests := []struct {
name string
action string
duration time.Duration
}{
{
name: "Filter step",
action: ScheduleStepFilter,
duration: 100 * time.Millisecond,
},
{
name: "Score step",
action: ScheduleStepScore,
duration: 200 * time.Millisecond,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
startTime := time.Now().Add(-tt.duration)
// Ensure the function doesn't panic
ScheduleStep(tt.action, startTime)
})
}
}
func TestCountSchedulerBindings(t *testing.T) {
tests := []struct {
name string
event string
}{
{
name: "Binding add event",
event: BindingAdd,
},
{
name: "Binding update event",
event: BindingUpdate,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
// Ensure the function doesn't panic
CountSchedulerBindings(tt.event)
})
}
}