Files
coredns/plugin/kubernetes/object/deepcopy_test.go
Yong Tang 4382b80a35 core: upgrade Go requirement to 1.26.0 (#8466)
* core: upgrade Go requirement to 1.26.0

As golang 1.27 has been released, this PR
- Bump Go version requirement to 1.26.0
- Update Go build version to 1.27.0

This is also for solving the issue encountered in 8092 of k8s update

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Bump golang ci

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Fix

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Fix

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Fix

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Migrate faillint to forbidigo, as failint has not bee updated for more than a year

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

---------

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
2026-09-10 19:34:44 -07:00

205 lines
6.2 KiB
Go

package object
import (
"encoding/json"
"fmt"
"reflect"
"testing"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
mcs "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
)
//go:fix inline
// dump renders an object for a failure message. %+v prints an aliased pointer field as
// an address, which hides the value that actually differs, so render as JSON instead.
func dump(v any) string {
b, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("%+v", v)
}
return string(b)
}
// deepCopyCases holds one fully populated value of every type in this package that
// implements runtime.Object. Every exported field must be set to a non-zero value:
// assertAllFieldsSet enforces that, so a field added to one of these types fails here
// until it is set, which in turn makes TestDeepCopyObjectCopiesEveryField cover it.
func deepCopyCases() []struct {
name string
obj runtime.Object
} {
endpoints := &Endpoints{
Version: "1",
Name: "svc1-slice1",
Namespace: "testns",
Index: EndpointsKey("svc1", "testns"),
IndexIP: []string{"172.0.0.1"},
Subsets: []EndpointSubset{{
Addresses: []EndpointAddress{{
IP: "172.0.0.1",
Hostname: "ep1a",
NodeName: "node1",
TargetRefName: "pod1",
}},
Ports: []EndpointPort{{Port: 80, Name: "http", Protocol: "tcp"}},
}},
Zones: map[string]string{"172.0.0.1": "us-east-1a"},
}
return []struct {
name string
obj runtime.Object
}{
{"Pod", &Pod{
Version: "1",
PodIP: "10.244.0.1",
Name: "pod1",
Namespace: "testns",
Labels: map[string]string{"app": "nginx", "tier": "frontend"},
}},
{"Endpoints", endpoints},
{"MultiClusterEndpoints", &MultiClusterEndpoints{
Endpoints: *endpoints,
ClusterId: "cluster1",
}},
{"Service", &Service{
Version: "1",
Name: "svc1",
Namespace: "testns",
Index: ServiceKey("svc1", "testns"),
ClusterIPs: []string{"10.0.0.1"},
Type: api.ServiceTypeClusterIP,
ExternalName: "coredns.io",
Ports: []api.ServicePort{{
Name: "http", Protocol: api.ProtocolTCP, Port: 80,
// A pointer field, so a slice copy alone leaves it shared.
AppProtocol: new("kubernetes.io/h2c"),
}},
ExternalIPs: []string{"1.2.3.4"},
}},
{"ServiceImport", &ServiceImport{
Version: "1",
Name: "svc1",
Namespace: "testns",
Index: ServiceImportKey("svc1", "testns"),
ClusterIPs: []string{"10.0.0.1"},
Type: mcs.ClusterSetIP,
Ports: []mcs.ServicePort{{
Name: "http", Protocol: api.ProtocolTCP, Port: 80,
AppProtocol: new("kubernetes.io/h2c"),
}},
}},
{"Namespace", &Namespace{Version: "1", Name: "testns"}},
}
}
// assertAllFieldsSet reports any exported field left at its zero value. The embedded
// *Empty carries no data, so it is skipped.
func assertAllFieldsSet(t *testing.T, name string, obj any) {
t.Helper()
v := reflect.ValueOf(obj).Elem()
typ := v.Type()
for i := range typ.NumField() {
f := typ.Field(i)
if !f.IsExported() || f.Type == reflect.TypeFor[*Empty]() {
continue
}
if v.Field(i).IsZero() {
t.Errorf("%s: test fixture leaves field %q at its zero value; set it so DeepCopyObject is actually checked for it", name, f.Name)
}
}
}
func TestDeepCopyObjectCopiesEveryField(t *testing.T) {
for _, tc := range deepCopyCases() {
t.Run(tc.name, func(t *testing.T) {
assertAllFieldsSet(t, tc.name, tc.obj)
got := tc.obj.DeepCopyObject()
if !reflect.DeepEqual(tc.obj, got) {
t.Errorf("DeepCopyObject() dropped or altered a field\n got: %s\nwant: %s", dump(got), dump(tc.obj))
}
})
}
}
// mutateEverything changes every value in v that is reachable through a pointer,
// slice, or map. Anything the copy still shares with the original then shows up as a
// change to the copy. Values reached only by value are changed too; that is harmless,
// because the caller inspects the copy rather than the original.
func mutateEverything(v reflect.Value) {
switch v.Kind() {
case reflect.Pointer, reflect.Interface:
if !v.IsNil() {
mutateEverything(v.Elem())
}
case reflect.Slice, reflect.Array:
for i := range v.Len() {
mutateEverything(v.Index(i))
}
case reflect.Map:
for _, k := range v.MapKeys() {
// Map values are not addressable, so mutate a copy and write it back.
e := reflect.New(v.Type().Elem()).Elem()
e.Set(v.MapIndex(k))
mutateEverything(e)
v.SetMapIndex(k, e)
}
case reflect.Struct:
for i := range v.NumField() {
if v.Type().Field(i).IsExported() {
mutateEverything(v.Field(i))
}
}
case reflect.String:
if v.CanSet() {
v.SetString(v.String() + "-mutated")
}
case reflect.Bool:
if v.CanSet() {
v.SetBool(!v.Bool())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.CanSet() {
v.SetInt(v.Int() + 1)
}
}
}
// TestDeepCopyObjectIsDeep checks that a copy shares nothing with the original: after
// mutating every reference-reachable value in the original, the copy must still equal
// a pristine fixture. Driving this off deepCopyCases keeps the guarantee package-wide,
// so a type that gains a pointer, slice, or map field is covered here as soon as
// assertAllFieldsSet forces the fixture to populate it.
func TestDeepCopyObjectIsDeep(t *testing.T) {
for i, tc := range deepCopyCases() {
t.Run(tc.name, func(t *testing.T) {
// Rebuild per subtest: the fixtures deliberately share backing arrays
// (MultiClusterEndpoints is built from the same Endpoints value), so
// mutating one case would otherwise corrupt another.
obj := deepCopyCases()[i].obj
pristine := deepCopyCases()[i].obj
cp := obj.DeepCopyObject()
mutateEverything(reflect.ValueOf(obj))
if !reflect.DeepEqual(cp, pristine) {
t.Errorf("mutating the original was visible through the copy, so DeepCopyObject aliases it\n got: %s\nwant: %s", dump(cp), dump(pristine))
}
})
}
}
// Nil maps and slices must stay nil rather than becoming empty, so a round trip does
// not change how a value compares.
func TestDeepCopyObjectPreservesNilLabels(t *testing.T) {
pod := &Pod{Version: "1", PodIP: "10.244.0.1", Name: "pod1", Namespace: "testns"}
cp := pod.DeepCopyObject().(*Pod)
if cp.Labels != nil {
t.Errorf("nil Labels became %#v after a round trip", cp.Labels)
}
}