mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-10-25 18:09:10 +00:00
In Go unit tests, the entire test output becomes the failure message because `go test` doesn't track why a test fails. This can make the failure message pretty large, in particular in integration tests. We cannot identify the real failure either because Kubernetes has no convention for how to format test failures. What we can do is recognize log output added by klog. prune-junit-xml now moves the full text to to the test output and only keep those lines in the failure which are not from klog. The klog output parsing might eventually get moved to k8s.io/logtools/logparse. For now it is developed as a sub-package of prune-junit-xml.
188 lines
4.3 KiB
Go
188 lines
4.3 KiB
Go
/*
|
|
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
|
|
}
|