mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-13 13:14:05 +00:00
Merge pull request #127929 from pohly/prune-junit-xml-failure
prune-junit-xml: simplify failure message
This commit is contained in:
234
cmd/prune-junit-xml/logparse/logparse.go
Normal file
234
cmd/prune-junit-xml/logparse/logparse.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 logparse provides a parser for the klog text format:
|
||||||
|
//
|
||||||
|
// I1007 13:16:55.727802 1146763 example.go:57] "Key/value encoding" logger="example" foo="bar" duration="1s" int=42 float=3.14 string="hello world" quotedString="hello \"world\"" multiLinString=<
|
||||||
|
// hello
|
||||||
|
// world
|
||||||
|
// >
|
||||||
|
// E1007 15:20:04.343375 1157755 example.go:41] Log using Errorf, err: fail
|
||||||
|
//
|
||||||
|
// It also supports the klog/ktesting unit test log output:
|
||||||
|
//
|
||||||
|
// === RUN TestKlogr
|
||||||
|
// example_test.go:45: I1007 13:28:21.908998] hello world
|
||||||
|
// example_test.go:46: E1007 13:28:21.909034] failed err="failed: some error"
|
||||||
|
// example_test.go:47: I1007 13:28:21.909042] verbosity 1
|
||||||
|
// example_test.go:48: I1007 13:28:21.909047] main/helper: with prefix
|
||||||
|
// example_test.go:50: I1007 13:28:21.909076] key/value pairs int=1 float=2 pair="(1, 2)" raw={"Name":"joe","NS":"kube-system"} slice=[1,2,3,"str"] map={"a":1,"b":2} kobj="kube-system/joe" string="hello world" quotedString="hello \"world\"" multiLineString=<
|
||||||
|
// hello
|
||||||
|
// world
|
||||||
|
// >
|
||||||
|
// example_test.go:63: I1007 13:28:21.909085] info message level 4
|
||||||
|
// example_test.go:64: I1007 13:28:21.909089] info message level 5
|
||||||
|
// --- PASS: TestKlogr (0.00s)
|
||||||
|
// PASS
|
||||||
|
// ok k8s.io/klog/v2/ktesting/example (cached)
|
||||||
|
//
|
||||||
|
// Arbitrary indention with space or tab is supported. All lines of
|
||||||
|
// a klog log entry must be indented the same way.
|
||||||
|
package logparse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"iter"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse splits log output provided by the reader into individual log entries.
|
||||||
|
// The original log can be reconstructed verbatim by concatenating these
|
||||||
|
// entries. If the reader fails with an error, the last log entry will
|
||||||
|
// capture that error.
|
||||||
|
func Parse(in io.Reader) []Entry {
|
||||||
|
var log []Entry
|
||||||
|
for entry := range All(in) {
|
||||||
|
log = append(log, entry)
|
||||||
|
}
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
// All is like Parse except that it can be used in a for/range loop:
|
||||||
|
//
|
||||||
|
// for entry := range logparse.All(reader) {
|
||||||
|
// // entry is the next log entry.
|
||||||
|
// }
|
||||||
|
func All(in io.Reader) iter.Seq[Entry] {
|
||||||
|
return func(yield func(Entry) bool) {
|
||||||
|
parse(in, yield)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry is the base type for a log entry.
|
||||||
|
//
|
||||||
|
// Use type assertions to check for additional information. All
|
||||||
|
// of the instances behind this interface are pointers.
|
||||||
|
type Entry interface {
|
||||||
|
// LogData returns a verbatim copy of the original log chunk,
|
||||||
|
// including one or more line breaks. Concatenating these chunks
|
||||||
|
// from all entries will reconstruct the parsed log.
|
||||||
|
LogData() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorEntry captures the error encountered while reading the log input.
|
||||||
|
type ErrorEntry struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Entry = &ErrorEntry{}
|
||||||
|
var _ fmt.Stringer = &ErrorEntry{}
|
||||||
|
|
||||||
|
func (e *ErrorEntry) LogData() string { return "" }
|
||||||
|
func (e *ErrorEntry) String() string { return e.Err.Error() }
|
||||||
|
|
||||||
|
// OtherEntry captures some log line which is not part of a klog log entry.
|
||||||
|
type OtherEntry struct {
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Entry = &OtherEntry{}
|
||||||
|
var _ fmt.Stringer = &OtherEntry{}
|
||||||
|
|
||||||
|
func (e *OtherEntry) LogData() string { return e.Data }
|
||||||
|
func (e *OtherEntry) String() string { return e.Data }
|
||||||
|
|
||||||
|
// LogEntry captures some log entry which was recognized as coming from klog.
|
||||||
|
type KlogEntry struct {
|
||||||
|
Data string
|
||||||
|
Severity Severity
|
||||||
|
}
|
||||||
|
|
||||||
|
type Severity byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
SeverityInfo = Severity('I')
|
||||||
|
SeverityWarning = Severity('W')
|
||||||
|
SeverityError = Severity('E')
|
||||||
|
SeverityFatal = Severity('F')
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Entry = &KlogEntry{}
|
||||||
|
var _ fmt.Stringer = &KlogEntry{}
|
||||||
|
|
||||||
|
func (e *KlogEntry) LogData() string { return e.Data }
|
||||||
|
func (e *KlogEntry) String() string { return e.Data }
|
||||||
|
|
||||||
|
func parse(in io.Reader, yield func(Entry) bool) {
|
||||||
|
// Read lines using arbitrary length, which can't be done with a
|
||||||
|
// bufio.Scanner.
|
||||||
|
reader := bufio.NewReader(in)
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
for {
|
||||||
|
// First deliver the current line. This may need to look
|
||||||
|
// ahead and thus returns the next line.
|
||||||
|
var nextLine string
|
||||||
|
var nextErr error
|
||||||
|
if len(line) > 0 {
|
||||||
|
var cont bool
|
||||||
|
nextLine, cont, nextErr = parseLine(reader, line, yield)
|
||||||
|
if !cont {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextLine, nextErr = reader.ReadString('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize parsing?
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
// Okay.
|
||||||
|
case errors.Is(err, io.EOF):
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
yield(&ErrorEntry{Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line = nextLine
|
||||||
|
err = nextErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
pid = `(?<pid>[[:digit:]]+)`
|
||||||
|
source = `(?<source>[^:]+:[[:digit:]]+)`
|
||||||
|
severity = `(?<severity>[IWEF])`
|
||||||
|
datetime = `(?<month>[[:digit:]]{2})(?<day>[[:digit:]]{2}) (?<hour>[[:digit:]]{2}):(?<minutes>[[:digit:]]{2}):(?<seconds>[[:digit:]]{2})\.(?<microseconds>[[:digit:]]{6})`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
klogPrefix = regexp.MustCompile(`^(?<indention>[[:blank:]]*)` +
|
||||||
|
`(?:` + source + `: )?` + // `go test` source code
|
||||||
|
severity +
|
||||||
|
datetime +
|
||||||
|
`(?: +` + pid + ` ` + source + `)?` + // klog pid + source code
|
||||||
|
`\] `)
|
||||||
|
|
||||||
|
indentionIndex = lookupSubmatch("indention")
|
||||||
|
severityIndex = lookupSubmatch("severity")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupSubmatch(name string) int {
|
||||||
|
names := klogPrefix.SubexpNames()
|
||||||
|
for i, n := range names {
|
||||||
|
if n == name {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("named submatch %q not found in %q", name, klogPrefix.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLine deals with one non-empty line. It returns the result of yield and
|
||||||
|
// potentially the next line and/or a read error. If it doesn't have any new
|
||||||
|
// data to process, it returns the empty string and a nil error.
|
||||||
|
func parseLine(reader *bufio.Reader, line string, yield func(Entry) bool) (string, bool, error) {
|
||||||
|
match := klogPrefix.FindStringSubmatchIndex(line)
|
||||||
|
if match == nil {
|
||||||
|
cont := yield(&OtherEntry{Data: line})
|
||||||
|
return "", cont, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
e := &KlogEntry{
|
||||||
|
Data: line,
|
||||||
|
Severity: Severity(line[match[2*severityIndex]]),
|
||||||
|
// TODO (?): store more of the fields that have been identified
|
||||||
|
}
|
||||||
|
// Deal with potential line continuation of multi-line string value,
|
||||||
|
// if necessary.
|
||||||
|
if !strings.HasSuffix(line, "=<\n") {
|
||||||
|
return "", yield(e), nil
|
||||||
|
}
|
||||||
|
indention := line[match[2*indentionIndex]:match[2*indentionIndex+1]]
|
||||||
|
for {
|
||||||
|
var err error
|
||||||
|
line, err = reader.ReadString('\n')
|
||||||
|
if !strings.HasPrefix(line, indention) ||
|
||||||
|
!strings.HasPrefix(line[len(indention):], "\t") && !strings.HasPrefix(line[len(indention):], " >") {
|
||||||
|
// Some other line (wrong indention or wrong continuation).
|
||||||
|
// Yield what we have so far and the go back to processing that new line.
|
||||||
|
cont := yield(e)
|
||||||
|
return line, cont, err
|
||||||
|
}
|
||||||
|
e.Data += line
|
||||||
|
if err != nil {
|
||||||
|
return "", yield(e), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
cmd/prune-junit-xml/logparse/logparse_test.go
Normal file
187
cmd/prune-junit-xml/logparse/logparse_test.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 logparse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
t.Logf("Full regular expression:\n%s", klogPrefix.String())
|
||||||
|
fakeErr := errors.New("fake error")
|
||||||
|
|
||||||
|
testcases := map[string]struct {
|
||||||
|
log string
|
||||||
|
err error
|
||||||
|
expectEntries []Entry
|
||||||
|
}{
|
||||||
|
"empty": {},
|
||||||
|
|
||||||
|
"one-other": {
|
||||||
|
log: "other",
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&OtherEntry{Data: "other"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"one-klog": {
|
||||||
|
log: `I1007 13:16:55.727802 1146763 example.go:57] "Key/value encoding" logger="example"`,
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&KlogEntry{
|
||||||
|
Data: `I1007 13:16:55.727802 1146763 example.go:57] "Key/value encoding" logger="example"`,
|
||||||
|
Severity: SeverityInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"one-unit": {
|
||||||
|
log: `example_test.go:45: E1007 13:28:21.908998] hello world`,
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&KlogEntry{
|
||||||
|
Data: `example_test.go:45: E1007 13:28:21.908998] hello world`,
|
||||||
|
Severity: SeverityError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"mixture": {
|
||||||
|
log: `other
|
||||||
|
I1007 13:16:55.727802 1146763 example.go:57] "a"
|
||||||
|
E1007 13:16:55.727802 1146763 example.go:58] "b" foo=<
|
||||||
|
indented
|
||||||
|
>
|
||||||
|
middle
|
||||||
|
example_test.go:45: E1007 13:28:21.908998] hello world
|
||||||
|
`,
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&OtherEntry{Data: "other\n"},
|
||||||
|
&KlogEntry{
|
||||||
|
Data: `I1007 13:16:55.727802 1146763 example.go:57] "a"
|
||||||
|
`,
|
||||||
|
Severity: SeverityInfo,
|
||||||
|
},
|
||||||
|
&KlogEntry{
|
||||||
|
Data: ` E1007 13:16:55.727802 1146763 example.go:58] "b" foo=<
|
||||||
|
indented
|
||||||
|
>
|
||||||
|
`, Severity: SeverityError,
|
||||||
|
},
|
||||||
|
&OtherEntry{Data: "middle\n"},
|
||||||
|
&KlogEntry{
|
||||||
|
Data: `example_test.go:45: E1007 13:28:21.908998] hello world
|
||||||
|
`,
|
||||||
|
Severity: SeverityError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"truncated": {
|
||||||
|
log: `other
|
||||||
|
E1007 13:16:55.727802 1146763 example.go:58] "b" foo=<
|
||||||
|
indented
|
||||||
|
middle
|
||||||
|
`,
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&OtherEntry{Data: "other\n"},
|
||||||
|
&KlogEntry{
|
||||||
|
Data: ` E1007 13:16:55.727802 1146763 example.go:58] "b" foo=<
|
||||||
|
indented
|
||||||
|
`, Severity: SeverityError,
|
||||||
|
},
|
||||||
|
&OtherEntry{Data: "middle\n"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
log: "hello\nworld",
|
||||||
|
err: fakeErr,
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&OtherEntry{Data: "hello\n"},
|
||||||
|
&OtherEntry{Data: "world"},
|
||||||
|
&ErrorEntry{Err: fakeErr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"truncated-error": {
|
||||||
|
log: `other
|
||||||
|
E1007 13:16:55.727802 1146763 example.go:58] "b" foo=<
|
||||||
|
indented`,
|
||||||
|
err: fakeErr,
|
||||||
|
expectEntries: []Entry{
|
||||||
|
&OtherEntry{Data: "other\n"},
|
||||||
|
&KlogEntry{
|
||||||
|
Data: ` E1007 13:16:55.727802 1146763 example.go:58] "b" foo=<
|
||||||
|
indented`, Severity: SeverityError,
|
||||||
|
},
|
||||||
|
&ErrorEntry{Err: fakeErr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testcases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
actualEntries := Parse(&fakeReader{log: tc.log, err: tc.err})
|
||||||
|
require.Equal(t, tc.expectEntries, actualEntries)
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
for _, entry := range actualEntries {
|
||||||
|
_, _ = buf.WriteString(entry.LogData())
|
||||||
|
}
|
||||||
|
require.Equal(t, tc.log, buf.String())
|
||||||
|
|
||||||
|
for i := 0; i < len(tc.expectEntries); i++ {
|
||||||
|
t.Run(fmt.Sprintf("stop-after-%d", i), func(t *testing.T) {
|
||||||
|
var actualEntries []Entry
|
||||||
|
seq := All(&fakeReader{log: tc.log, err: tc.err})
|
||||||
|
e := 0
|
||||||
|
seq(func(entry Entry) bool {
|
||||||
|
actualEntries = append(actualEntries, entry)
|
||||||
|
if e >= i {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
e++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
require.Equal(t, tc.expectEntries[0:i+1], actualEntries)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeReader struct {
|
||||||
|
log string
|
||||||
|
err error
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeReader) Read(buf []byte) (int, error) {
|
||||||
|
n := min(len(buf), len(f.log)-f.offset)
|
||||||
|
copy(buf, []byte(f.log[f.offset:f.offset+n]))
|
||||||
|
f.offset += n
|
||||||
|
var err error
|
||||||
|
if f.offset >= len(f.log) {
|
||||||
|
if f.err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
} else {
|
||||||
|
err = f.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
@@ -23,7 +23,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/cmd/prune-junit-xml/logparse"
|
||||||
"k8s.io/kubernetes/third_party/forked/gotestsum/junitxml"
|
"k8s.io/kubernetes/third_party/forked/gotestsum/junitxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,25 +66,57 @@ func main() {
|
|||||||
|
|
||||||
func pruneXML(suites *junitxml.JUnitTestSuites, maxBytes int) {
|
func pruneXML(suites *junitxml.JUnitTestSuites, maxBytes int) {
|
||||||
for _, suite := range suites.Suites {
|
for _, suite := range suites.Suites {
|
||||||
for _, testcase := range suite.TestCases {
|
for i := range suite.TestCases {
|
||||||
|
// Modify directly in the TestCases slice, if necessary.
|
||||||
|
testcase := &suite.TestCases[i]
|
||||||
if testcase.SkipMessage != nil {
|
if testcase.SkipMessage != nil {
|
||||||
if len(testcase.SkipMessage.Message) > maxBytes {
|
pruneStringIfNeeded(&testcase.SkipMessage.Message, maxBytes, "clipping skip message in test case : %s\n", testcase.Name)
|
||||||
fmt.Printf("clipping skip message in test case : %s\n", testcase.Name)
|
|
||||||
head := testcase.SkipMessage.Message[:maxBytes/2]
|
|
||||||
tail := testcase.SkipMessage.Message[len(testcase.SkipMessage.Message)-maxBytes/2:]
|
|
||||||
testcase.SkipMessage.Message = head + "[...clipped...]" + tail
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if testcase.Failure != nil {
|
if testcase.Failure != nil {
|
||||||
if len(testcase.Failure.Contents) > maxBytes {
|
// In Go unit tests, the entire test output
|
||||||
fmt.Printf("clipping failure message in test case : %s\n", testcase.Name)
|
// becomes the failure message because `go
|
||||||
head := testcase.Failure.Contents[:maxBytes/2]
|
// test` doesn't track why a test fails. This
|
||||||
tail := testcase.Failure.Contents[len(testcase.Failure.Contents)-maxBytes/2:]
|
// can make the failure message pretty large.
|
||||||
testcase.Failure.Contents = head + "[...clipped...]" + tail
|
//
|
||||||
|
// We cannot identify the real failure here
|
||||||
|
// either because Kubernetes has no convention
|
||||||
|
// for how to format test failures. What we can
|
||||||
|
// do is recognize log output added by klog.
|
||||||
|
//
|
||||||
|
// Therefore here we move the full text to
|
||||||
|
// to the test output and only keep those
|
||||||
|
// lines in the failure which are not from
|
||||||
|
// klog.
|
||||||
|
if testcase.SystemOut == "" {
|
||||||
|
var buf strings.Builder
|
||||||
|
// Iterate over all the log entries and decide what to keep as failure message.
|
||||||
|
for entry := range logparse.All(strings.NewReader(testcase.Failure.Contents)) {
|
||||||
|
if _, ok := entry.(*logparse.KlogEntry); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _ = buf.WriteString(entry.LogData())
|
||||||
|
}
|
||||||
|
if buf.Len() < len(testcase.Failure.Contents) {
|
||||||
|
// Update both strings because they became different.
|
||||||
|
testcase.SystemOut = testcase.Failure.Contents
|
||||||
|
pruneStringIfNeeded(&testcase.SystemOut, maxBytes, "clipping log output in test case: %s\n", testcase.Name)
|
||||||
|
testcase.Failure.Contents = buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pruneStringIfNeeded(&testcase.Failure.Contents, maxBytes, "clipping failure message in test case : %s\n", testcase.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pruneStringIfNeeded(str *string, maxBytes int, msg string, args ...any) {
|
||||||
|
if len(*str) <= maxBytes {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf(msg, args...)
|
||||||
|
head := (*str)[:maxBytes/2]
|
||||||
|
tail := (*str)[len(*str)-maxBytes/2:]
|
||||||
|
*str = head + "[...clipped...]" + tail
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function condenses the junit xml to have package name as top level identifier
|
// This function condenses the junit xml to have package name as top level identifier
|
||||||
|
@@ -28,7 +28,7 @@ import (
|
|||||||
func TestPruneXML(t *testing.T) {
|
func TestPruneXML(t *testing.T) {
|
||||||
sourceXML := `<?xml version="1.0" encoding="UTF-8"?>
|
sourceXML := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite tests="3" failures="1" time="271.610000" name="k8s.io/kubernetes/test/integration/apiserver" timestamp="">
|
<testsuite tests="4" failures="2" time="271.610000" name="k8s.io/kubernetes/test/integration/apiserver" timestamp="">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="go.version" value="go1.18 linux/amd64"></property>
|
<property name="go.version" value="go1.18 linux/amd64"></property>
|
||||||
</properties>
|
</properties>
|
||||||
@@ -39,12 +39,15 @@ func TestPruneXML(t *testing.T) {
|
|||||||
<testcase classname="k8s.io/kubernetes/test/integration/apimachinery" name="TestSchedulerInformers" time="-0.000000">
|
<testcase classname="k8s.io/kubernetes/test/integration/apimachinery" name="TestSchedulerInformers" time="-0.000000">
|
||||||
<failure message="Failed" type="">
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport/transport.go:169 +0x147
k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport.(*transportReader).Read(0xc0e5f8edb0, {0xc0efe16f88?, 0xc1169d3a88?, 0x1804787?})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport/transport.go:483 +0x32
io.ReadAtLeast({0x55c5720, 0xc0e5f8edb0}, {0xc0efe16f88, 0x5, 0x5}, 0x5)
	/usr/local/go/src/io/io.go:331 +0x9a
io.ReadFull(...)
	/usr/local/go/src/io/io.go:350
k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport.(*Stream).Read(0xc0f3cd67e0, {0xc0efe16f88, 0x5, 0x5})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport/transport.go:467 +0xa5
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*parser).recvMsg(0xc0efe16f78, 0x7fffffff)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/rpc_util.go:559 +0x47
k8s.io/kubernetes/vendor/google.golang.org/grpc.recvAndDecompress(0xc1169d3c58?, 0xc0f3cd67e0, {0x0, 0x0}, 0x7fffffff, 0x0, {0x0, 0x0})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/rpc_util.go:690 +0x66
k8s.io/kubernetes/vendor/google.golang.org/grpc.recv(0x172b28f?, {0x7f837c291d58, 0x7f84350}, 0x6f5a274d6e8f284c?, {0x0?, 0x0?}, {0x4be7d40, 0xc0f8c01d50}, 0x0?, 0x0, ...)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/rpc_util.go:758 +0x6e
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*csAttempt).recvMsg(0xc0eb72d800, {0x4be7d40?, 0xc0f8c01d50}, 0x2?)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:970 +0x2b0
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*clientStream).RecvMsg.func1(0x4be7d40?)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:821 +0x25
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*clientStream).withRetry(0xc0f3cd65a0, 0xc1169d3e78, 0xc1169d3e48)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:675 +0x2f6
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*clientStream).RecvMsg(0xc0f3cd65a0, {0x4be7d40?, 0xc0f8c01d50?})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:820 +0x11f
k8s.io/kubernetes/vendor/github.com/grpc-ecosystem/go-grpc-prometheus.(*monitoredClientStream).RecvMsg(0xc0efe16f90, {0x4be7d40?, 0xc0f8c01d50?})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_metrics.go:160</failure>
|
<failure message="Failed" type="">
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport/transport.go:169 +0x147
k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport.(*transportReader).Read(0xc0e5f8edb0, {0xc0efe16f88?, 0xc1169d3a88?, 0x1804787?})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport/transport.go:483 +0x32
io.ReadAtLeast({0x55c5720, 0xc0e5f8edb0}, {0xc0efe16f88, 0x5, 0x5}, 0x5)
	/usr/local/go/src/io/io.go:331 +0x9a
io.ReadFull(...)
	/usr/local/go/src/io/io.go:350
k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport.(*Stream).Read(0xc0f3cd67e0, {0xc0efe16f88, 0x5, 0x5})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/internal/transport/transport.go:467 +0xa5
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*parser).recvMsg(0xc0efe16f78, 0x7fffffff)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/rpc_util.go:559 +0x47
k8s.io/kubernetes/vendor/google.golang.org/grpc.recvAndDecompress(0xc1169d3c58?, 0xc0f3cd67e0, {0x0, 0x0}, 0x7fffffff, 0x0, {0x0, 0x0})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/rpc_util.go:690 +0x66
k8s.io/kubernetes/vendor/google.golang.org/grpc.recv(0x172b28f?, {0x7f837c291d58, 0x7f84350}, 0x6f5a274d6e8f284c?, {0x0?, 0x0?}, {0x4be7d40, 0xc0f8c01d50}, 0x0?, 0x0, ...)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/rpc_util.go:758 +0x6e
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*csAttempt).recvMsg(0xc0eb72d800, {0x4be7d40?, 0xc0f8c01d50}, 0x2?)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:970 +0x2b0
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*clientStream).RecvMsg.func1(0x4be7d40?)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:821 +0x25
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*clientStream).withRetry(0xc0f3cd65a0, 0xc1169d3e78, 0xc1169d3e48)
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:675 +0x2f6
k8s.io/kubernetes/vendor/google.golang.org/grpc.(*clientStream).RecvMsg(0xc0f3cd65a0, {0x4be7d40?, 0xc0f8c01d50?})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/google.golang.org/grpc/stream.go:820 +0x11f
k8s.io/kubernetes/vendor/github.com/grpc-ecosystem/go-grpc-prometheus.(*monitoredClientStream).RecvMsg(0xc0efe16f90, {0x4be7d40?, 0xc0f8c01d50?})
	/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_metrics.go:160</failure>
|
||||||
</testcase>
|
</testcase>
|
||||||
|
<testcase classname="k8s.io/kubernetes/test/integration/apimachinery" name="TestLog" time="-0.000000">
|
||||||
|
<failure message="Failed" type="">logparse_test.go:29: I1007 13:28:21.909085] klog output
FAIL: fake failure
</failure>
|
||||||
|
</testcase>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>`
|
</testsuites>`
|
||||||
|
|
||||||
outputXML := `<?xml version="1.0" encoding="UTF-8"?>
|
outputXML := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite tests="3" failures="1" time="271.610000" name="k8s.io/kubernetes/test/integration/apiserver" timestamp="">
|
<testsuite tests="4" failures="2" time="271.610000" name="k8s.io/kubernetes/test/integration/apiserver" timestamp="">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="go.version" value="go1.18 linux/amd64"></property>
|
<property name="go.version" value="go1.18 linux/amd64"></property>
|
||||||
</properties>
|
</properties>
|
||||||
@@ -55,6 +58,10 @@ func TestPruneXML(t *testing.T) {
|
|||||||
<testcase classname="k8s.io/kubernetes/test/integration/apimachinery" name="TestSchedulerInformers" time="-0.000000">
|
<testcase classname="k8s.io/kubernetes/test/integration/apimachinery" name="TestSchedulerInformers" time="-0.000000">
|
||||||
<failure message="Failed" type="">
	/home/prow/go/[...clipped...]t_metrics.go:160</failure>
|
<failure message="Failed" type="">
	/home/prow/go/[...clipped...]t_metrics.go:160</failure>
|
||||||
</testcase>
|
</testcase>
|
||||||
|
<testcase classname="k8s.io/kubernetes/test/integration/apimachinery" name="TestLog" time="-0.000000">
|
||||||
|
<failure message="Failed" type="">FAIL: fake failure
</failure>
|
||||||
|
<system-out>logparse_test.go[...clipped...]L: fake failure
</system-out>
|
||||||
|
</testcase>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>`
|
</testsuites>`
|
||||||
suites, _ := fetchXML(strings.NewReader(sourceXML))
|
suites, _ := fetchXML(strings.NewReader(sourceXML))
|
||||||
|
@@ -33,6 +33,8 @@ type JUnitTestCase struct {
|
|||||||
Time string `xml:"time,attr"`
|
Time string `xml:"time,attr"`
|
||||||
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
|
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
|
||||||
Failure *JUnitFailure `xml:"failure,omitempty"`
|
Failure *JUnitFailure `xml:"failure,omitempty"`
|
||||||
|
SystemOut string `xml:"system-out,omitempty"`
|
||||||
|
SystemErr string `xml:"system-err,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JUnitSkipMessage contains the reason why a testcase was skipped.
|
// JUnitSkipMessage contains the reason why a testcase was skipped.
|
||||||
|
Reference in New Issue
Block a user