mirror of
https://github.com/outbackdingo/kubernetes.git
synced 2026-01-27 18:19:28 +00:00
Add a replacement for cmp.Diff using json+go-difflib
Co-authored-by: Jordan Liggitt <jordan@liggitt.net> Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
@@ -823,7 +823,7 @@ _____________________________________________________________________
|
||||
actual := rt.buf.String()
|
||||
if actual != rt.expected {
|
||||
|
||||
t.Errorf("failed PrintUpgradePlan:\n\nexpected:\n%s\n\nactual:\n%s\n\ndiff:\n%s", rt.expected, actual, diff.StringDiff(actual, rt.expected))
|
||||
t.Errorf("failed PrintUpgradePlan:\n\nexpected:\n%s\n\nactual:\n%s\n\ndiff:\n%s", rt.expected, actual, diff.Diff(actual, rt.expected))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -251,6 +251,8 @@ linters:
|
||||
- $all
|
||||
- "!$test"
|
||||
- "!**/test/**"
|
||||
- "!**/testing/**"
|
||||
- "!**/apitesting/**"
|
||||
deny:
|
||||
- pkg: "github.com/google/go-cmp/cmp"
|
||||
desc: "cmp is allowed only in test files"
|
||||
|
||||
@@ -265,6 +265,8 @@ linters:
|
||||
- $all
|
||||
- "!$test"
|
||||
- "!**/test/**"
|
||||
- "!**/testing/**"
|
||||
- "!**/apitesting/**"
|
||||
deny:
|
||||
- pkg: "github.com/google/go-cmp/cmp"
|
||||
desc: "cmp is allowed only in test files"
|
||||
|
||||
@@ -195,6 +195,8 @@ linters:
|
||||
- $all
|
||||
- "!$test"
|
||||
- "!**/test/**"
|
||||
- "!**/testing/**"
|
||||
- "!**/apitesting/**"
|
||||
deny:
|
||||
- pkg: "github.com/google/go-cmp/cmp"
|
||||
desc: "cmp is allowed only in test files"
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilcert "k8s.io/client-go/util/cert"
|
||||
@@ -304,7 +304,7 @@ func validateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.Certif
|
||||
case len(newConditions) > len(oldConditions):
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not add a condition of type %q", t)))
|
||||
case !apiequality.Semantic.DeepEqual(oldConditions, newConditions):
|
||||
conditionDiff := cmp.Diff(oldConditions, newConditions)
|
||||
conditionDiff := diff.Diff(oldConditions, newConditions)
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not modify a condition of type %q\n%v", t, conditionDiff)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
netutils "k8s.io/utils/net"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -42,6 +41,7 @@ import (
|
||||
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
@@ -2141,7 +2141,7 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts Pe
|
||||
|
||||
// PersistentVolumeSource should be immutable after creation.
|
||||
if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) {
|
||||
pvcSourceDiff := cmp.Diff(oldPv.Spec.PersistentVolumeSource, newPv.Spec.PersistentVolumeSource)
|
||||
pvcSourceDiff := diff.Diff(oldPv.Spec.PersistentVolumeSource, newPv.Spec.PersistentVolumeSource)
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), fmt.Sprintf("spec.persistentvolumesource is immutable after creation\n%v", pvcSourceDiff)))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
|
||||
@@ -2436,7 +2436,7 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
|
||||
statusSize := oldPvc.Status.Capacity["storage"]
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) {
|
||||
specDiff := cmp.Diff(oldPvcClone.Spec, newPvcClone.Spec)
|
||||
specDiff := diff.Diff(oldPvcClone.Spec, newPvcClone.Spec)
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims\n%v", specDiff)))
|
||||
}
|
||||
if newSize.Cmp(oldSize) < 0 {
|
||||
@@ -5416,7 +5416,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
||||
if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) {
|
||||
// This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is".
|
||||
// TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff
|
||||
specDiff := cmp.Diff(oldPod.Spec, mungedPodSpec)
|
||||
specDiff := diff.Diff(oldPod.Spec, mungedPodSpec)
|
||||
errs := field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than %s\n%v", strings.Join(updatablePodSpecFields, ","), specDiff))
|
||||
allErrs = append(allErrs, errs)
|
||||
}
|
||||
@@ -5629,7 +5629,7 @@ func ValidatePodEphemeralContainersUpdate(newPod, oldPod *core.Pod, opts PodVali
|
||||
if new, ok := newContainerIndex[old.Name]; !ok {
|
||||
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be removed\n", old.Name)))
|
||||
} else if !apiequality.Semantic.DeepEqual(old, *new) {
|
||||
specDiff := cmp.Diff(old, *new)
|
||||
specDiff := diff.Diff(old, *new)
|
||||
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be changed\n%v", old.Name, specDiff)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -31,7 +33,6 @@ import (
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
capi "k8s.io/api/certificates/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
)
|
||||
|
||||
func TestCertificateAuthority(t *testing.T) {
|
||||
@@ -237,7 +238,7 @@ func TestCertificateAuthority(t *testing.T) {
|
||||
"Version",
|
||||
"MaxPathLen",
|
||||
),
|
||||
diff.IgnoreUnset(),
|
||||
ignoreUnset(),
|
||||
cmp.Transformer("RoundTime", func(x time.Time) time.Time {
|
||||
return x.Truncate(time.Second)
|
||||
}),
|
||||
@@ -252,6 +253,44 @@ func TestCertificateAuthority(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ignoreUnset is an option that ignores fields that are unset on the right
|
||||
// hand side of a comparison. This is useful in testing to assert that an
|
||||
// object is a derivative.
|
||||
func ignoreUnset() cmp.Option {
|
||||
return cmp.Options{
|
||||
// ignore unset fields in v2
|
||||
cmp.FilterPath(func(path cmp.Path) bool {
|
||||
_, v2 := path.Last().Values()
|
||||
switch v2.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
if v2.IsNil() || v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
case reflect.String:
|
||||
if v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
if v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()),
|
||||
// ignore map entries that aren't set in v2
|
||||
cmp.FilterPath(func(path cmp.Path) bool {
|
||||
switch i := path.Last().(type) {
|
||||
case cmp.MapIndex:
|
||||
if _, v2 := i.Values(); !v2.IsValid() {
|
||||
fmt.Println("E")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()),
|
||||
}
|
||||
}
|
||||
|
||||
func errString(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
|
||||
@@ -23,15 +23,16 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
capi "k8s.io/api/certificates/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
testclient "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/util/cert"
|
||||
@@ -41,6 +42,44 @@ import (
|
||||
testingclock "k8s.io/utils/clock/testing"
|
||||
)
|
||||
|
||||
// ignoreUnset is an option that ignores fields that are unset on the right
|
||||
// hand side of a comparison. This is useful in testing to assert that an
|
||||
// object is a derivative.
|
||||
func ignoreUnset() cmp.Option {
|
||||
return cmp.Options{
|
||||
// ignore unset fields in v2
|
||||
cmp.FilterPath(func(path cmp.Path) bool {
|
||||
_, v2 := path.Last().Values()
|
||||
switch v2.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
if v2.IsNil() || v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
case reflect.String:
|
||||
if v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
if v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()),
|
||||
// ignore map entries that aren't set in v2
|
||||
cmp.FilterPath(func(path cmp.Path) bool {
|
||||
switch i := path.Last().(type) {
|
||||
case cmp.MapIndex:
|
||||
if _, v2 := i.Values(); !v2.IsValid() {
|
||||
fmt.Println("E")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()),
|
||||
}
|
||||
}
|
||||
|
||||
func TestSigner(t *testing.T) {
|
||||
fakeClock := testingclock.FakeClock{}
|
||||
|
||||
@@ -99,8 +138,8 @@ func TestSigner(t *testing.T) {
|
||||
MaxPathLen: -1,
|
||||
}
|
||||
|
||||
if !cmp.Equal(*certs[0], want, diff.IgnoreUnset()) {
|
||||
t.Errorf("unexpected diff: %v", cmp.Diff(certs[0], want, diff.IgnoreUnset()))
|
||||
if !cmp.Equal(*certs[0], want, ignoreUnset()) {
|
||||
t.Errorf("unexpected diff: %v", cmp.Diff(certs[0], want, ignoreUnset()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,13 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard // Discouraged for production use (https://github.com/kubernetes/kubernetes/issues/104821) but has no good alternative for logging.
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
@@ -546,7 +545,7 @@ func (tc *Controller) handleClaimChange(oldClaim, newClaim *resourceapi.Resource
|
||||
name := newNamespacedName(claim)
|
||||
if tc.eventLogger != nil {
|
||||
// This is intentionally very verbose for debugging.
|
||||
tc.eventLogger.Info("ResourceClaim changed", "claimObject", name, "oldClaim", klog.Format(oldClaim), "newClaim", klog.Format(newClaim), "diff", cmp.Diff(oldClaim, newClaim))
|
||||
tc.eventLogger.Info("ResourceClaim changed", "claimObject", name, "oldClaim", klog.Format(oldClaim), "newClaim", klog.Format(newClaim), "diff", diff.Diff(oldClaim, newClaim))
|
||||
}
|
||||
|
||||
// Deleted?
|
||||
@@ -686,7 +685,7 @@ func (tc *Controller) handleSliceChange(oldSlice, newSlice *resourceapi.Resource
|
||||
}
|
||||
if tc.eventLogger != nil {
|
||||
// This is intentionally very verbose for debugging.
|
||||
tc.eventLogger.Info("ResourceSlice changed", "pool", poolID, "oldSlice", klog.Format(oldSlice), "newSlice", klog.Format(newSlice), "diff", cmp.Diff(oldSlice, newSlice))
|
||||
tc.eventLogger.Info("ResourceSlice changed", "pool", poolID, "oldSlice", klog.Format(oldSlice), "newSlice", klog.Format(newSlice), "diff", diff.Diff(oldSlice, newSlice))
|
||||
}
|
||||
|
||||
// Determine old and new device taints. Only devices
|
||||
@@ -784,7 +783,7 @@ func (tc *Controller) handlePodChange(oldPod, newPod *v1.Pod) {
|
||||
}
|
||||
if tc.eventLogger != nil {
|
||||
// This is intentionally very verbose for debugging.
|
||||
tc.eventLogger.Info("Pod changed", "pod", klog.KObj(pod), "oldPod", klog.Format(oldPod), "newPod", klog.Format(newPod), "diff", cmp.Diff(oldPod, newPod))
|
||||
tc.eventLogger.Info("Pod changed", "pod", klog.KObj(pod), "oldPod", klog.Format(oldPod), "newPod", klog.Format(newPod), "diff", diff.Diff(oldPod, newPod))
|
||||
}
|
||||
if newPod == nil {
|
||||
// Nothing left to do for it. No need to emit an event here, it's gone.
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -33,6 +32,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -1027,7 +1027,7 @@ func (m *manager) needsReconcile(logger klog.Logger, uid types.UID, status v1.Po
|
||||
}
|
||||
logger.V(3).Info("Pod status is inconsistent with cached status for pod, a reconciliation should be triggered",
|
||||
"pod", klog.KObj(pod),
|
||||
"statusDiff", cmp.Diff(podStatus, &status))
|
||||
"statusDiff", diff.Diff(podStatus, &status))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
flowcontrolv1 "k8s.io/api/flowcontrol/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
@@ -285,13 +285,13 @@ func EnsureConfiguration[ObjectType configurationObjectType](ctx context.Context
|
||||
if !update {
|
||||
if klogV := klog.V(5); klogV.Enabled() {
|
||||
klogV.InfoS("No update required", "wrapper", bootstrap.GetObjectKind().GroupVersionKind().Kind, "type", configurationType, "name", name,
|
||||
"diff", cmp.Diff(current, bootstrap))
|
||||
"diff", diff.Diff(current, bootstrap))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = ops.Update(ctx, newObject, metav1.UpdateOptions{FieldManager: fieldManager}); err == nil {
|
||||
klog.V(2).Infof("Updated the %T type=%s name=%q diff: %s", bootstrap, configurationType, name, cmp.Diff(current, bootstrap))
|
||||
klog.V(2).Infof("Updated the %T type=%s name=%q diff: %s", bootstrap, configurationType, name, diff.Diff(current, bootstrap))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
@@ -33,6 +31,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/retry"
|
||||
@@ -258,7 +257,7 @@ func (pl *DynamicResources) isSchedulableAfterClaimChange(logger klog.Logger, po
|
||||
if apiequality.Semantic.DeepEqual(&originalClaim.Status, &modifiedClaim.Status) {
|
||||
if loggerV := logger.V(7); loggerV.Enabled() {
|
||||
// Log more information.
|
||||
loggerV.Info("claim for pod got modified where the pod doesn't care", "pod", klog.KObj(pod), "claim", klog.KObj(modifiedClaim), "diff", cmp.Diff(originalClaim, modifiedClaim))
|
||||
loggerV.Info("claim for pod got modified where the pod doesn't care", "pod", klog.KObj(pod), "claim", klog.KObj(modifiedClaim), "diff", diff.Diff(originalClaim, modifiedClaim))
|
||||
} else {
|
||||
logger.V(6).Info("claim for pod got modified where the pod doesn't care", "pod", klog.KObj(pod), "claim", klog.KObj(modifiedClaim))
|
||||
}
|
||||
|
||||
@@ -21,10 +21,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/component-helpers/resource"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -314,7 +313,7 @@ func (f *Fit) isSchedulableAfterPodEvent(logger klog.Logger, pod *v1.Pod, oldObj
|
||||
if !f.isSchedulableAfterPodScaleDown(pod, originalPod, modifiedPod) {
|
||||
if loggerV := logger.V(10); loggerV.Enabled() {
|
||||
// Log more information.
|
||||
loggerV.Info("pod got scaled down, but the modification isn't related to the resource requests of the target pod", "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod), "diff", cmp.Diff(originalPod, modifiedPod))
|
||||
loggerV.Info("pod got scaled down, but the modification isn't related to the resource requests of the target pod", "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod), "diff", diff.Diff(originalPod, modifiedPod))
|
||||
} else {
|
||||
logger.V(5).Info("pod got scaled down, but the modification isn't related to the resource requests of the target pod", "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod))
|
||||
}
|
||||
|
||||
@@ -22,9 +22,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/events"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
@@ -123,7 +122,7 @@ func (v *cfgValidator) validate(cfg config.KubeSchedulerProfile, f framework.Fra
|
||||
if v.queueSort != queueSort {
|
||||
return fmt.Errorf("different queue sort plugins for profile %q: %q, first: %q", cfg.SchedulerName, queueSort, v.queueSort)
|
||||
}
|
||||
if !cmp.Equal(v.queueSortArgs, queueSortArgs) {
|
||||
if diff.Diff(v.queueSortArgs, queueSortArgs) != "" {
|
||||
return fmt.Errorf("different queue sort plugin args for profile %q", cfg.SchedulerName)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -23,13 +23,12 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
@@ -476,7 +475,7 @@ func (p *Plugin) admitPVCStatus(nodeName string, a admission.Attributes) error {
|
||||
|
||||
// ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
|
||||
if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update fields other than status.quantity and status.conditions: %v", nodeName, cmp.Diff(oldPVC, newPVC)))
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update fields other than status.quantity and status.conditions: %v", nodeName, diff.Diff(oldPVC, newPVC)))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -16,6 +16,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/moby/spdystream v0.5.0
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/net v0.38.0
|
||||
@@ -44,7 +45,6 @@ require (
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 // indirect
|
||||
github.com/onsi/gomega v1.35.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
flag "github.com/spf13/pflag"
|
||||
"sigs.k8s.io/randfill"
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
// RoundtripToUnstructured verifies the roundtrip faithfulness of all external types in a scheme
|
||||
|
||||
31
staging/src/k8s.io/apimachinery/pkg/util/diff/cmp.go
Normal file
31
staging/src/k8s.io/apimachinery/pkg/util/diff/cmp.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build usegocmp
|
||||
// +build usegocmp
|
||||
|
||||
/*
|
||||
Copyright 2025 The Kubernetes 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 diff
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
)
|
||||
|
||||
// Diff returns a string representation of the difference between two objects.
|
||||
// When built with the usegocmp tag, it uses go-cmp/cmp to generate a diff
|
||||
// between the objects.
|
||||
func Diff(a, b any) string {
|
||||
return cmp.Diff(a, b)
|
||||
}
|
||||
356
staging/src/k8s.io/apimachinery/pkg/util/diff/complex_test.go
Normal file
356
staging/src/k8s.io/apimachinery/pkg/util/diff/complex_test.go
Normal file
@@ -0,0 +1,356 @@
|
||||
//go:build !usegocmp
|
||||
// +build !usegocmp
|
||||
|
||||
/*
|
||||
Copyright 2025 The Kubernetes 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 diff
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
// TestDiffWithRealGoCmp is a comprehensive test that compares our Diff with the actual go-cmp Diff
|
||||
// across a variety of data structures and types to ensure compatibility.
|
||||
func TestDiffWithRealGoCmp(t *testing.T) {
|
||||
// Test with simple types
|
||||
t.Run("SimpleTypes", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
a interface{}
|
||||
b interface{}
|
||||
}{
|
||||
{name: "Integers", a: 42, b: 43},
|
||||
{name: "Strings", a: "hello", b: "world"},
|
||||
{name: "Booleans", a: true, b: false},
|
||||
{name: "Floats", a: 3.14, b: 2.71},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ourDiff := Diff(tc.a, tc.b)
|
||||
goCmpDiff := cmp.Diff(tc.a, tc.b)
|
||||
|
||||
t.Logf("Our diff:\n%s\n\nGo-cmp diff:\n%s", ourDiff, goCmpDiff)
|
||||
|
||||
// Verify both diffs are non-empty
|
||||
if ourDiff == "" || goCmpDiff == "" {
|
||||
t.Errorf("Expected non-empty diffs, got ourDiff: %v, goCmpDiff: %v",
|
||||
ourDiff == "", goCmpDiff == "")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Test with a simple struct
|
||||
t.Run("SimpleStruct", func(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
Name string
|
||||
Age int
|
||||
Tags []string
|
||||
Map map[string]int
|
||||
}
|
||||
|
||||
a := TestStruct{
|
||||
Name: "Alice",
|
||||
Age: 30,
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Map: map[string]int{"a": 1, "b": 2},
|
||||
}
|
||||
|
||||
b := TestStruct{
|
||||
Name: "Bob",
|
||||
Age: 25,
|
||||
Tags: []string{"tag1", "tag3"},
|
||||
Map: map[string]int{"a": 1, "c": 3},
|
||||
}
|
||||
|
||||
ourDiff := Diff(a, b)
|
||||
goCmpDiff := cmp.Diff(a, b)
|
||||
|
||||
t.Logf("Our diff:\n%s\n\nGo-cmp diff:\n%s", ourDiff, goCmpDiff)
|
||||
|
||||
// Check that our diff contains key differences
|
||||
keyDifferences := []string{
|
||||
"Name", "Alice", "Bob",
|
||||
"Age", "30", "25",
|
||||
"Tags", "tag2", "tag3",
|
||||
"Map", "b", "c",
|
||||
}
|
||||
|
||||
for _, key := range keyDifferences {
|
||||
if !strings.Contains(ourDiff, key) {
|
||||
t.Errorf("Our diff doesn't contain expected key difference: %q", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Test with a complex nested struct
|
||||
t.Run("ComplexNestedStruct", func(t *testing.T) {
|
||||
type Address struct {
|
||||
Street string
|
||||
City string
|
||||
State string
|
||||
PostalCode string
|
||||
Country string
|
||||
}
|
||||
|
||||
type Contact struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
ID int
|
||||
FirstName string
|
||||
LastName string
|
||||
Age int
|
||||
Addresses map[string]Address
|
||||
Contacts []Contact
|
||||
Metadata map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
later := now.Add(24 * time.Hour)
|
||||
|
||||
person1 := Person{
|
||||
ID: 1,
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Age: 30,
|
||||
Addresses: map[string]Address{
|
||||
"home": {
|
||||
Street: "123 Main St",
|
||||
City: "Anytown",
|
||||
State: "CA",
|
||||
PostalCode: "12345",
|
||||
Country: "USA",
|
||||
},
|
||||
"work": {
|
||||
Street: "456 Market St",
|
||||
City: "Worktown",
|
||||
State: "CA",
|
||||
PostalCode: "54321",
|
||||
Country: "USA",
|
||||
},
|
||||
},
|
||||
Contacts: []Contact{
|
||||
{Type: "email", Value: "john.doe@example.com"},
|
||||
{Type: "phone", Value: "555-1234"},
|
||||
},
|
||||
Metadata: map[string]interface{}{
|
||||
"created": "2023-01-01",
|
||||
"loginCount": 42,
|
||||
"settings": map[string]bool{
|
||||
"notifications": true,
|
||||
"darkMode": false,
|
||||
},
|
||||
},
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
person2 := Person{
|
||||
ID: 1,
|
||||
FirstName: "John",
|
||||
LastName: "Smith", // Different
|
||||
Age: 31, // Different
|
||||
Addresses: map[string]Address{
|
||||
"home": {
|
||||
Street: "123 Main St",
|
||||
City: "Anytown",
|
||||
State: "CA",
|
||||
PostalCode: "12345",
|
||||
Country: "USA",
|
||||
},
|
||||
// "work" address is missing
|
||||
},
|
||||
Contacts: []Contact{
|
||||
{Type: "email", Value: "john.smith@example.com"}, // Different
|
||||
{Type: "phone", Value: "555-1234"},
|
||||
{Type: "fax", Value: "555-5678"}, // Additional
|
||||
},
|
||||
Metadata: map[string]interface{}{
|
||||
"created": "2023-01-01",
|
||||
"loginCount": 43, // Different
|
||||
"settings": map[string]bool{
|
||||
"notifications": false, // Different
|
||||
"darkMode": true, // Different
|
||||
},
|
||||
"newField": "new value", // New field
|
||||
},
|
||||
CreatedAt: later, // Different
|
||||
}
|
||||
|
||||
ourDiff := Diff(person1, person2)
|
||||
goCmpDiff := cmp.Diff(person1, person2)
|
||||
|
||||
t.Logf("Our diff:\n%s\n\nGo-cmp diff:\n%s", ourDiff, goCmpDiff)
|
||||
|
||||
// Check that our diff contains key differences
|
||||
keyDifferences := []string{
|
||||
"LastName", "Smith",
|
||||
"Age",
|
||||
"Addresses", "work",
|
||||
"Contacts", "john.smith@example.com", "fax",
|
||||
"Metadata", "loginCount", "settings", "notifications", "darkMode", "newField",
|
||||
}
|
||||
|
||||
for _, key := range keyDifferences {
|
||||
if !strings.Contains(ourDiff, key) {
|
||||
t.Errorf("Our diff doesn't contain expected key difference: %q", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that both diffs are non-empty
|
||||
if ourDiff == "" || goCmpDiff == "" {
|
||||
t.Errorf("Expected non-empty diffs, got ourDiff: %v, goCmpDiff: %v",
|
||||
ourDiff == "", goCmpDiff == "")
|
||||
}
|
||||
})
|
||||
|
||||
// Test with slices and maps
|
||||
t.Run("SlicesAndMaps", func(t *testing.T) {
|
||||
// Test with slices
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
b1 := []int{1, 2, 6, 4, 7}
|
||||
|
||||
ourDiff1 := Diff(a1, b1)
|
||||
goCmpDiff1 := cmp.Diff(a1, b1)
|
||||
|
||||
t.Logf("Our diff (slices):\n%s\n\nGo-cmp diff (slices):\n%s", ourDiff1, goCmpDiff1)
|
||||
|
||||
// Check that our diff contains the differences
|
||||
if !strings.Contains(ourDiff1, "3") || !strings.Contains(ourDiff1, "6") ||
|
||||
!strings.Contains(ourDiff1, "5") || !strings.Contains(ourDiff1, "7") {
|
||||
t.Errorf("Our diff doesn't contain all expected differences for slices")
|
||||
}
|
||||
|
||||
// Test with maps
|
||||
a2 := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
b2 := map[string]int{"a": 1, "b": 5, "d": 4}
|
||||
|
||||
ourDiff2 := Diff(a2, b2)
|
||||
goCmpDiff2 := cmp.Diff(a2, b2)
|
||||
|
||||
t.Logf("Our diff (maps):\n%s\n\nGo-cmp diff (maps):\n%s", ourDiff2, goCmpDiff2)
|
||||
|
||||
// Check that our diff contains the differences
|
||||
if !strings.Contains(ourDiff2, "b") || !strings.Contains(ourDiff2, "5") ||
|
||||
!strings.Contains(ourDiff2, "c") || !strings.Contains(ourDiff2, "d") {
|
||||
t.Errorf("Our diff doesn't contain all expected differences for maps")
|
||||
}
|
||||
})
|
||||
|
||||
// Test with unexported fields
|
||||
t.Run("UnexportedFields", func(t *testing.T) {
|
||||
type WithUnexported struct {
|
||||
Exported int
|
||||
unexported int
|
||||
}
|
||||
|
||||
a := WithUnexported{Exported: 1, unexported: 2}
|
||||
b := WithUnexported{Exported: 3, unexported: 4}
|
||||
|
||||
ourDiff := Diff(a, b)
|
||||
// Use cmpopts.IgnoreUnexported to ignore unexported fields in go-cmp
|
||||
goCmpDiff := cmp.Diff(a, b, cmpopts.IgnoreUnexported(WithUnexported{}))
|
||||
|
||||
t.Logf("Our diff (unexported):\n%s\n\nGo-cmp diff (unexported):\n%s", ourDiff, goCmpDiff)
|
||||
|
||||
// Check that our diff contains only the exported field difference
|
||||
if !strings.Contains(ourDiff, "Exported") || !strings.Contains(ourDiff, "1") || !strings.Contains(ourDiff, "3") {
|
||||
t.Errorf("Our diff doesn't contain the exported field difference")
|
||||
}
|
||||
})
|
||||
|
||||
// Test with embedded structs
|
||||
t.Run("EmbeddedStructs", func(t *testing.T) {
|
||||
type Embedded struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Embedded
|
||||
Extra string
|
||||
}
|
||||
|
||||
a := Container{Embedded: Embedded{Value: 1}, Extra: "a"}
|
||||
b := Container{Embedded: Embedded{Value: 2}, Extra: "b"}
|
||||
|
||||
ourDiff := Diff(a, b)
|
||||
goCmpDiff := cmp.Diff(a, b)
|
||||
|
||||
t.Logf("Our diff (embedded):\n%s\n\nGo-cmp diff (embedded):\n%s", ourDiff, goCmpDiff)
|
||||
|
||||
// Check that our diff contains the container field difference
|
||||
if !strings.Contains(ourDiff, "Extra") || !strings.Contains(ourDiff, "a") || !strings.Contains(ourDiff, "b") {
|
||||
t.Errorf("Our diff doesn't contain the container field difference")
|
||||
}
|
||||
})
|
||||
|
||||
// Test with interface values of same type
|
||||
t.Run("InterfaceValues", func(t *testing.T) {
|
||||
type Container struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Test with same type in interface
|
||||
c := Container{Value: 42}
|
||||
d := Container{Value: 43}
|
||||
|
||||
ourDiff := Diff(c, d)
|
||||
goCmpDiff := cmp.Diff(c, d)
|
||||
|
||||
t.Logf("Our diff (interface same type):\n%s\n\nGo-cmp diff (interface same type):\n%s", ourDiff, goCmpDiff)
|
||||
|
||||
// Check that our diff contains the value difference
|
||||
if !strings.Contains(ourDiff, "42") || !strings.Contains(ourDiff, "43") {
|
||||
t.Errorf("Our diff doesn't contain the value difference for interface values of same type")
|
||||
}
|
||||
})
|
||||
|
||||
// Test with objects that cannot be marshaled to JSON
|
||||
t.Run("UnmarshalableObjects", func(t *testing.T) {
|
||||
// Test with a circular reference, which cannot be marshaled to JSON
|
||||
type Node struct {
|
||||
Value int
|
||||
Next *Node
|
||||
}
|
||||
|
||||
// Create a circular reference
|
||||
nodeA := &Node{Value: 1}
|
||||
nodeA.Next = nodeA // Points to itself
|
||||
|
||||
nodeB := &Node{Value: 2}
|
||||
nodeB.Next = nodeB // Points to itself
|
||||
|
||||
// This should fall back to using dump.Pretty
|
||||
circularDiff := Diff(nodeA, nodeB)
|
||||
|
||||
t.Logf("Diff for circular references:\n%s", circularDiff)
|
||||
|
||||
// Verify the diff contains the values
|
||||
if !strings.Contains(circularDiff, "1") || !strings.Contains(circularDiff, "2") {
|
||||
t.Errorf("Diff doesn't contain expected value differences for circular references")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
//go:build !usegocmp
|
||||
// +build !usegocmp
|
||||
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,122 +20,43 @@ limitations under the License.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
)
|
||||
|
||||
func legacyDiff(a, b interface{}) string {
|
||||
return cmp.Diff(a, b)
|
||||
}
|
||||
// Diff returns a string representation of the difference between two objects.
|
||||
// When built without the usegocmp tag, it uses go-difflib/difflib to generate a
|
||||
// unified diff of the objects. It attempts to use JSON serialization first,
|
||||
// falling back to an object dump via the dump package if JSON marshaling fails.
|
||||
func Diff(a, b any) string {
|
||||
|
||||
// StringDiff diffs a and b and returns a human readable diff.
|
||||
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||
func StringDiff(a, b string) string {
|
||||
return legacyDiff(a, b)
|
||||
}
|
||||
|
||||
// ObjectDiff prints the diff of two go objects and fails if the objects
|
||||
// contain unhandled unexported fields.
|
||||
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||
func ObjectDiff(a, b interface{}) string {
|
||||
return legacyDiff(a, b)
|
||||
}
|
||||
|
||||
// ObjectGoPrintDiff prints the diff of two go objects and fails if the objects
|
||||
// contain unhandled unexported fields.
|
||||
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||
func ObjectGoPrintDiff(a, b interface{}) string {
|
||||
return legacyDiff(a, b)
|
||||
}
|
||||
|
||||
// ObjectReflectDiff prints the diff of two go objects and fails if the objects
|
||||
// contain unhandled unexported fields.
|
||||
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||
func ObjectReflectDiff(a, b interface{}) string {
|
||||
return legacyDiff(a, b)
|
||||
}
|
||||
|
||||
// ObjectGoPrintSideBySide prints a and b as textual dumps side by side,
|
||||
// enabling easy visual scanning for mismatches.
|
||||
func ObjectGoPrintSideBySide(a, b interface{}) string {
|
||||
sA := dump.Pretty(a)
|
||||
sB := dump.Pretty(b)
|
||||
|
||||
linesA := strings.Split(sA, "\n")
|
||||
linesB := strings.Split(sB, "\n")
|
||||
width := 0
|
||||
for _, s := range linesA {
|
||||
l := len(s)
|
||||
if l > width {
|
||||
width = l
|
||||
}
|
||||
aStr, aErr := toPrettyJSON(a)
|
||||
bStr, bErr := toPrettyJSON(b)
|
||||
if aErr != nil || bErr != nil {
|
||||
aStr = dump.Pretty(a)
|
||||
bStr = dump.Pretty(b)
|
||||
}
|
||||
for _, s := range linesB {
|
||||
l := len(s)
|
||||
if l > width {
|
||||
width = l
|
||||
}
|
||||
|
||||
diff := difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(aStr),
|
||||
B: difflib.SplitLines(bStr),
|
||||
Context: 3,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0)
|
||||
max := len(linesA)
|
||||
if len(linesB) > max {
|
||||
max = len(linesB)
|
||||
|
||||
diffstr, err := difflib.GetUnifiedDiffString(diff)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("error generating diff: %v", err)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
var a, b string
|
||||
if i < len(linesA) {
|
||||
a = linesA[i]
|
||||
}
|
||||
if i < len(linesB) {
|
||||
b = linesB[i]
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\n", a, b)
|
||||
}
|
||||
w.Flush()
|
||||
return buf.String()
|
||||
|
||||
return diffstr
|
||||
}
|
||||
|
||||
// IgnoreUnset is an option that ignores fields that are unset on the right
|
||||
// hand side of a comparison. This is useful in testing to assert that an
|
||||
// object is a derivative.
|
||||
func IgnoreUnset() cmp.Option {
|
||||
return cmp.Options{
|
||||
// ignore unset fields in v2
|
||||
cmp.FilterPath(func(path cmp.Path) bool {
|
||||
_, v2 := path.Last().Values()
|
||||
switch v2.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
if v2.IsNil() || v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
case reflect.String:
|
||||
if v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
if v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()),
|
||||
// ignore map entries that aren't set in v2
|
||||
cmp.FilterPath(func(path cmp.Path) bool {
|
||||
switch i := path.Last().(type) {
|
||||
case cmp.MapIndex:
|
||||
if _, v2 := i.Values(); !v2.IsValid() {
|
||||
fmt.Println("E")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()),
|
||||
}
|
||||
// toPrettyJSON converts an object to a pretty-printed JSON string.
|
||||
func toPrettyJSON(data any) (string, error) {
|
||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||
return string(jsonData), err
|
||||
}
|
||||
|
||||
67
staging/src/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go
Normal file
67
staging/src/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes 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 diff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
)
|
||||
|
||||
// ObjectGoPrintSideBySide prints a and b as textual dumps side by side,
|
||||
// enabling easy visual scanning for mismatches.
|
||||
func ObjectGoPrintSideBySide(a, b interface{}) string {
|
||||
sA := dump.Pretty(a)
|
||||
sB := dump.Pretty(b)
|
||||
|
||||
linesA := strings.Split(sA, "\n")
|
||||
linesB := strings.Split(sB, "\n")
|
||||
width := 0
|
||||
for _, s := range linesA {
|
||||
l := len(s)
|
||||
if l > width {
|
||||
width = l
|
||||
}
|
||||
}
|
||||
for _, s := range linesB {
|
||||
l := len(s)
|
||||
if l > width {
|
||||
width = l
|
||||
}
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0)
|
||||
max := len(linesA)
|
||||
if len(linesB) > max {
|
||||
max = len(linesB)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
var a, b string
|
||||
if i < len(linesA) {
|
||||
a = linesA[i]
|
||||
}
|
||||
if i < len(linesB) {
|
||||
b = linesB[i]
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\n", a, b)
|
||||
}
|
||||
_ = w.Flush()
|
||||
return buf.String()
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
apidiscoveryv2 "k8s.io/api/apidiscovery/v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
@@ -91,7 +91,7 @@ func (f *fakeResourceManager) Validate() error {
|
||||
defer f.expect.lock.RUnlock()
|
||||
|
||||
if !reflect.DeepEqual(f.expect.Actions, f.Actions) {
|
||||
return errors.New(cmp.Diff(f.expect.Actions, f.Actions))
|
||||
return errors.New(diff.Diff(f.expect.Actions, f.Actions))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
|
||||
@@ -28,11 +28,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
@@ -607,7 +607,7 @@ func (cfgCtlr *configController) digestConfigObjects(newPLs []*flowcontrol.Prior
|
||||
currResult.updatedItems.Insert(fsu.flowSchema.Name)
|
||||
if klogV := klog.V(4); klogV.Enabled() {
|
||||
klogV.Infof("%s writing Condition %s to FlowSchema %s, which had ResourceVersion=%s, because its previous value was %s, diff: %s",
|
||||
cfgCtlr.name, fsu.condition, fsu.flowSchema.Name, fsu.flowSchema.ResourceVersion, fcfmt.Fmt(fsu.oldValue), cmp.Diff(fsu.oldValue, fsu.condition))
|
||||
cfgCtlr.name, fsu.condition, fsu.flowSchema.Name, fsu.flowSchema.ResourceVersion, fcfmt.Fmt(fsu.oldValue), diff.Diff(fsu.oldValue, fsu.condition))
|
||||
}
|
||||
|
||||
if err := apply(cfgCtlr.flowcontrolClient.FlowSchemas(), fsu, cfgCtlr.asFieldManager); err != nil {
|
||||
|
||||
@@ -19,14 +19,14 @@ package consistencydetector
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
@@ -75,8 +75,8 @@ func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity
|
||||
sort.Sort(byUID(listItems))
|
||||
sort.Sort(byUID(retrievedItems))
|
||||
|
||||
if !cmp.Equal(listItems, retrievedItems) {
|
||||
klog.Infof("previously received data for %s is different than received by the standard list api call against etcd, diff: %v", identity, cmp.Diff(listItems, retrievedItems))
|
||||
if !reflect.DeepEqual(listItems, retrievedItems) {
|
||||
klog.Infof("previously received data for %s is different than received by the standard list api call against etcd, diff: %v", identity, diff.Diff(listItems, retrievedItems))
|
||||
msg := fmt.Sprintf("data inconsistency detected for %s, panicking!", identity)
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/google/go-cmp/cmp"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
@@ -27,13 +27,13 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/textlogger"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
"k8s.io/component-base/featuregate"
|
||||
@@ -240,7 +240,7 @@ func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate feature
|
||||
case ReapplyHandlingError:
|
||||
return errors.New("logging configuration was already applied earlier, changing it is not allowed")
|
||||
case ReapplyHandlingIgnoreUnchanged:
|
||||
if diff := cmp.Diff(oldP, p); diff != "" {
|
||||
if diff := diff.Diff(oldP, p); diff != "" {
|
||||
return fmt.Errorf("the logging configuration should not be changed after setting it once (- old setting, + new setting):\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -26,8 +26,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourceapi "k8s.io/api/resource/v1beta2"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
@@ -37,6 +35,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
@@ -456,7 +455,7 @@ func (c *Controller) initInformer(ctx context.Context) error {
|
||||
return
|
||||
}
|
||||
if loggerV := logger.V(6); loggerV.Enabled() {
|
||||
loggerV.Info("ResourceSlice update", "slice", klog.KObj(newSlice), "diff", cmp.Diff(oldSlice, newSlice))
|
||||
loggerV.Info("ResourceSlice update", "slice", klog.KObj(newSlice), "diff", diff.Diff(oldSlice, newSlice))
|
||||
} else {
|
||||
logger.V(5).Info("ResourceSlice update", "slice", klog.KObj(newSlice))
|
||||
}
|
||||
|
||||
@@ -23,12 +23,11 @@ import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourcealphaapi "k8s.io/api/resource/v1alpha3"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
resourcealphainformers "k8s.io/client-go/informers/resource/v1alpha3"
|
||||
@@ -405,7 +404,7 @@ func (t *Tracker) resourceSliceUpdate(ctx context.Context) func(oldObj, newObj a
|
||||
if loggerV := logger.V(6); loggerV.Enabled() {
|
||||
// While debugging, one needs a full dump of the objects for context *and*
|
||||
// a diff because otherwise small changes would be hard to spot.
|
||||
loggerV.Info("ResourceSlice update", "slice", klog.Format(oldSlice), "oldSlice", klog.Format(newSlice), "diff", cmp.Diff(oldSlice, newSlice))
|
||||
loggerV.Info("ResourceSlice update", "slice", klog.Format(oldSlice), "oldSlice", klog.Format(newSlice), "diff", diff.Diff(oldSlice, newSlice))
|
||||
} else {
|
||||
logger.V(5).Info("ResourceSlice update", "slice", klog.KObj(newSlice))
|
||||
}
|
||||
@@ -454,7 +453,7 @@ func (t *Tracker) deviceTaintUpdate(ctx context.Context) func(oldObj, newObj any
|
||||
return
|
||||
}
|
||||
if loggerV := logger.V(6); loggerV.Enabled() {
|
||||
loggerV.Info("DeviceTaintRule update", "patch", klog.KObj(newPatch), "diff", cmp.Diff(oldPatch, newPatch))
|
||||
loggerV.Info("DeviceTaintRule update", "patch", klog.KObj(newPatch), "diff", diff.Diff(oldPatch, newPatch))
|
||||
} else {
|
||||
logger.V(5).Info("DeviceTaintRule update", "patch", klog.KObj(newPatch))
|
||||
}
|
||||
@@ -514,7 +513,7 @@ func (t *Tracker) deviceClassUpdate(ctx context.Context) func(oldObj, newObj any
|
||||
return
|
||||
}
|
||||
if loggerV := logger.V(6); loggerV.Enabled() {
|
||||
loggerV.Info("DeviceClass update", "class", klog.KObj(newClass), "diff", cmp.Diff(oldClass, newClass))
|
||||
loggerV.Info("DeviceClass update", "class", klog.KObj(newClass), "diff", diff.Diff(oldClass, newClass))
|
||||
} else {
|
||||
logger.V(5).Info("DeviceClass update", "class", klog.KObj(newClass))
|
||||
}
|
||||
@@ -618,7 +617,7 @@ func (t *Tracker) syncSlice(ctx context.Context, name string, sendEvent bool) {
|
||||
}
|
||||
|
||||
if loggerV := logger.V(6); loggerV.Enabled() {
|
||||
loggerV.Info("ResourceSlice synced", "diff", cmp.Diff(oldPatchedObj, patchedSlice))
|
||||
loggerV.Info("ResourceSlice synced", "diff", diff.Diff(oldPatchedObj, patchedSlice))
|
||||
} else {
|
||||
logger.V(5).Info("ResourceSlice synced")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -24,6 +25,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
|
||||
@@ -34,6 +34,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
|
||||
Reference in New Issue
Block a user