mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 02:11:09 +00:00
Merge pull request #113384 from pohly/e2e-formatting
e2e: formatting enhancements
This commit is contained in:
commit
a9f87ad6c8
@ -67,6 +67,7 @@ import (
|
|||||||
_ "k8s.io/kubernetes/test/e2e/framework/debug/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/debug/init"
|
||||||
_ "k8s.io/kubernetes/test/e2e/framework/metrics/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/metrics/init"
|
||||||
_ "k8s.io/kubernetes/test/e2e/framework/node/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/node/init"
|
||||||
|
_ "k8s.io/kubernetes/test/utils/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleFlags sets up all flags and parses the command line.
|
// handleFlags sets up all flags and parses the command line.
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
"github.com/onsi/ginkgo/v2/reporters"
|
"github.com/onsi/ginkgo/v2/reporters"
|
||||||
"github.com/onsi/ginkgo/v2/types"
|
"github.com/onsi/ginkgo/v2/types"
|
||||||
|
gomegaformat "github.com/onsi/gomega/format"
|
||||||
|
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
@ -307,6 +308,9 @@ func (tc TestContextType) ClusterIsIPv6() bool {
|
|||||||
// options themselves, copy flags from test/e2e/framework/config
|
// options themselves, copy flags from test/e2e/framework/config
|
||||||
// as shown in HandleFlags.
|
// as shown in HandleFlags.
|
||||||
func RegisterCommonFlags(flags *flag.FlagSet) {
|
func RegisterCommonFlags(flags *flag.FlagSet) {
|
||||||
|
// The default is too low for objects like pods, even when using YAML. We double the default.
|
||||||
|
flags.IntVar(&gomegaformat.MaxLength, "gomega-max-length", 8000, "Sets the maximum size for the gomega formatter (= gomega.MaxLength). Use 0 to disable truncation.")
|
||||||
|
|
||||||
flags.StringVar(&TestContext.GatherKubeSystemResourceUsageData, "gather-resource-usage", "false", "If set to 'true' or 'all' framework will be monitoring resource usage of system all add-ons in (some) e2e tests, if set to 'master' framework will be monitoring master node only, if set to 'none' of 'false' monitoring will be turned off.")
|
flags.StringVar(&TestContext.GatherKubeSystemResourceUsageData, "gather-resource-usage", "false", "If set to 'true' or 'all' framework will be monitoring resource usage of system all add-ons in (some) e2e tests, if set to 'master' framework will be monitoring master node only, if set to 'none' of 'false' monitoring will be turned off.")
|
||||||
flags.BoolVar(&TestContext.GatherLogsSizes, "gather-logs-sizes", false, "If set to true framework will be monitoring logs sizes on all machines running e2e tests.")
|
flags.BoolVar(&TestContext.GatherLogsSizes, "gather-logs-sizes", false, "If set to true framework will be monitoring logs sizes on all machines running e2e tests.")
|
||||||
flags.IntVar(&TestContext.MaxNodesToGather, "max-nodes-to-gather-from", 20, "The maximum number of nodes to gather extended info from on test failure.")
|
flags.IntVar(&TestContext.MaxNodesToGather, "max-nodes-to-gather-from", 20, "The maximum number of nodes to gather extended info from on test failure.")
|
||||||
@ -526,6 +530,14 @@ func AfterReadingAllFlags(t *TestContextType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// This is the traditional gomega.Format default of 4000 for an object
|
||||||
|
// dump plus some extra room for the message.
|
||||||
|
maxFailureMessageSize = 5000
|
||||||
|
|
||||||
|
truncatedMsg = "\n[... see output for full dump ...]\n"
|
||||||
|
)
|
||||||
|
|
||||||
// writeJUnitReport generates a JUnit file in the e2e report directory that is
|
// writeJUnitReport generates a JUnit file in the e2e report directory that is
|
||||||
// shorter than the one normally written by `ginkgo --junit-report`. This is
|
// shorter than the one normally written by `ginkgo --junit-report`. This is
|
||||||
// needed because the full report can become too large for tools like Spyglass
|
// needed because the full report can become too large for tools like Spyglass
|
||||||
@ -542,6 +554,18 @@ func writeJUnitReport(report ginkgo.Report) {
|
|||||||
if specReport.State != types.SpecStateFailed {
|
if specReport.State != types.SpecStateFailed {
|
||||||
specReport.CapturedGinkgoWriterOutput = ""
|
specReport.CapturedGinkgoWriterOutput = ""
|
||||||
specReport.CapturedStdOutErr = ""
|
specReport.CapturedStdOutErr = ""
|
||||||
|
} else {
|
||||||
|
// Truncate the failure message if it is too large.
|
||||||
|
msgLen := len(specReport.Failure.Message)
|
||||||
|
if msgLen > maxFailureMessageSize {
|
||||||
|
// Insert full message at the beginning where it is easy to find.
|
||||||
|
specReport.CapturedGinkgoWriterOutput =
|
||||||
|
"Full failure message:\n" +
|
||||||
|
specReport.Failure.Message + "\n\n" +
|
||||||
|
strings.Repeat("=", 70) + "\n\n" +
|
||||||
|
specReport.CapturedGinkgoWriterOutput
|
||||||
|
specReport.Failure.Message = specReport.Failure.Message[0:maxFailureMessageSize/2] + truncatedMsg + specReport.Failure.Message[msgLen-maxFailureMessageSize/2:msgLen]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove report entries generated by ginkgo.By("doing
|
// Remove report entries generated by ginkgo.By("doing
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
_ "k8s.io/kubernetes/test/e2e/framework/debug/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/debug/init"
|
||||||
_ "k8s.io/kubernetes/test/e2e/framework/metrics/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/metrics/init"
|
||||||
_ "k8s.io/kubernetes/test/e2e/framework/node/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/node/init"
|
||||||
|
_ "k8s.io/kubernetes/test/utils/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -57,6 +57,7 @@ import (
|
|||||||
_ "k8s.io/kubernetes/test/e2e/framework/debug/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/debug/init"
|
||||||
_ "k8s.io/kubernetes/test/e2e/framework/metrics/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/metrics/init"
|
||||||
_ "k8s.io/kubernetes/test/e2e/framework/node/init"
|
_ "k8s.io/kubernetes/test/e2e/framework/node/init"
|
||||||
|
_ "k8s.io/kubernetes/test/utils/format"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
"github.com/onsi/gomega"
|
"github.com/onsi/gomega"
|
||||||
|
80
test/utils/format/format.go
Normal file
80
test/utils/format/format.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 format is an extension of Gomega's format package which
|
||||||
|
// improves printing of objects that can be serialized well as YAML,
|
||||||
|
// like the structs in the Kubernetes API.
|
||||||
|
//
|
||||||
|
// Just importing it is enough to activate this special YAML support
|
||||||
|
// in Gomega.
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterCustomFormatter(handleYAML)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object makes Gomega's [format.Object] available without having to import that
|
||||||
|
// package.
|
||||||
|
func Object(object interface{}, indentation uint) string {
|
||||||
|
return format.Object(object, indentation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleYAML formats all values as YAML where the result
|
||||||
|
// is likely to look better as YAML:
|
||||||
|
// - pointer to struct or struct where all fields
|
||||||
|
// have `json` tags
|
||||||
|
// - slices containing such a value
|
||||||
|
// - maps where the key or value are such a value
|
||||||
|
func handleYAML(object interface{}) (string, bool) {
|
||||||
|
value := reflect.ValueOf(object)
|
||||||
|
if !useYAML(value.Type()) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
y, err := yaml.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return "\n" + strings.TrimSpace(string(y)), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func useYAML(t reflect.Type) bool {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Pointer, reflect.Slice, reflect.Array:
|
||||||
|
return useYAML(t.Elem())
|
||||||
|
case reflect.Map:
|
||||||
|
return useYAML(t.Key()) || useYAML(t.Elem())
|
||||||
|
case reflect.Struct:
|
||||||
|
// All fields must have a `json` tag.
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if _, ok := field.Tag.Lookup("json"); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
81
test/utils/format/format_test.go
Normal file
81
test/utils/format/format_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 format_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGomegaFormatObject(t *testing.T) {
|
||||||
|
for name, test := range map[string]struct {
|
||||||
|
value interface{}
|
||||||
|
expected string
|
||||||
|
indentation uint
|
||||||
|
}{
|
||||||
|
"int": {value: 1, expected: `<int>: 1`},
|
||||||
|
"string": {value: "hello world", expected: `<string>: "hello world"`},
|
||||||
|
"struct": {value: myStruct{a: 1, b: 2}, expected: `<format_test.myStruct>: {a: 1, b: 2}`},
|
||||||
|
"gomegastringer": {value: typeWithGomegaStringer(2), expected: `<format_test.typeWithGomegaStringer>: my stringer 2`},
|
||||||
|
"pod": {value: v1.Pod{}, expected: `<v1.Pod>:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
containers: null
|
||||||
|
status: {}`},
|
||||||
|
"pod-indented": {value: v1.Pod{}, indentation: 1, expected: ` <v1.Pod>:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
containers: null
|
||||||
|
status: {}`},
|
||||||
|
"pod-ptr": {value: &v1.Pod{}, expected: `<*v1.Pod | <hex>>:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
containers: null
|
||||||
|
status: {}`},
|
||||||
|
"pod-hash": {value: map[string]v1.Pod{}, expected: `<map[string]v1.Pod | len:0>:
|
||||||
|
{}`},
|
||||||
|
"podlist": {value: v1.PodList{}, expected: `<v1.PodList>:
|
||||||
|
items: null
|
||||||
|
metadata: {}`},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
actual := format.Object(test.value, test.indentation)
|
||||||
|
actual = regexp.MustCompile(`\| 0x[a-z0-9]+`).ReplaceAllString(actual, `| <hex>`)
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeWithGomegaStringer int
|
||||||
|
|
||||||
|
func (v typeWithGomegaStringer) GomegaString() string {
|
||||||
|
return fmt.Sprintf("my stringer %d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type myStruct struct {
|
||||||
|
a, b int
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user