Skip to content

Commit 18d7f88

Browse files
authored
Merge pull request kubernetes#80253 from pohly/failure-logging
Failure logging
2 parents 8a2def5 + cdaba2b commit 18d7f88

File tree

6 files changed

+197
-9
lines changed

6 files changed

+197
-9
lines changed

test/e2e/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ go_library(
6565
"//test/e2e/common:go_default_library",
6666
"//test/e2e/framework:go_default_library",
6767
"//test/e2e/framework/auth:go_default_library",
68-
"//test/e2e/framework/ginkgowrapper:go_default_library",
6968
"//test/e2e/framework/log:go_default_library",
7069
"//test/e2e/framework/pod:go_default_library",
7170
"//test/e2e/framework/providers/aws:go_default_library",

test/e2e/e2e.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
"k8s.io/component-base/logs"
3333
commontest "k8s.io/kubernetes/test/e2e/common"
3434
"k8s.io/kubernetes/test/e2e/framework"
35-
"k8s.io/kubernetes/test/e2e/framework/ginkgowrapper"
35+
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
3636

3737
// ensure auth plugins are loaded
3838
_ "k8s.io/client-go/plugin/pkg/client/auth"
@@ -71,7 +71,7 @@ func RunE2ETests(t *testing.T) {
7171
logs.InitLogs()
7272
defer logs.FlushLogs()
7373

74-
gomega.RegisterFailHandler(ginkgowrapper.Fail)
74+
gomega.RegisterFailHandler(e2elog.Fail)
7575
// Disable skipped tests unless they are explicitly requested.
7676
if config.GinkgoConfig.FocusString == "" && config.GinkgoConfig.SkipString == "" {
7777
config.GinkgoConfig.SkipString = `\[Flaky\]|\[Feature:.+\]`

test/e2e/framework/log/BUILD

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@io_bazel_rules_go//go:def.bzl", "go_library")
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "go_default_library",
@@ -24,3 +24,16 @@ filegroup(
2424
tags = ["automanaged"],
2525
visibility = ["//visibility:public"],
2626
)
27+
28+
go_test(
29+
name = "go_default_test",
30+
srcs = ["logger_test.go"],
31+
embed = [":go_default_library"],
32+
deps = [
33+
"//test/e2e/framework:go_default_library",
34+
"//vendor/github.com/onsi/ginkgo:go_default_library",
35+
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
36+
"//vendor/github.com/onsi/ginkgo/reporters:go_default_library",
37+
"//vendor/github.com/onsi/gomega:go_default_library",
38+
],
39+
)

test/e2e/framework/log/logger.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ func Failf(format string, args ...interface{}) {
4848
// (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
4949
func FailfWithOffset(offset int, format string, args ...interface{}) {
5050
msg := fmt.Sprintf(format, args...)
51-
log("INFO", msg)
51+
log("FAIL", msg)
5252
ginkgowrapper.Fail(nowStamp()+": "+msg, 1+offset)
5353
}
54+
55+
// Fail is a replacement for ginkgo.Fail which logs the problem as it occurs
56+
// and then calls ginkgowrapper.Fail.
57+
func Fail(msg string, callerSkip ...int) {
58+
skip := 1
59+
if len(callerSkip) > 0 {
60+
skip += callerSkip[0]
61+
}
62+
log("FAIL", msg)
63+
ginkgowrapper.Fail(nowStamp()+": "+msg, skip)
64+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package log_test
18+
19+
import (
20+
"errors"
21+
"regexp"
22+
"sort"
23+
"strings"
24+
"testing"
25+
26+
"github.com/onsi/ginkgo"
27+
"github.com/onsi/ginkgo/config"
28+
"github.com/onsi/ginkgo/reporters"
29+
"github.com/onsi/gomega"
30+
31+
"k8s.io/kubernetes/test/e2e/framework"
32+
"k8s.io/kubernetes/test/e2e/framework/log"
33+
)
34+
35+
var _ = ginkgo.Describe("log", func() {
36+
ginkgo.BeforeEach(func() {
37+
log.Logf("before")
38+
})
39+
ginkgo.It("fails", func() {
40+
func() {
41+
log.Failf("I'm failing.")
42+
}()
43+
})
44+
ginkgo.It("asserts", func() {
45+
gomega.Expect(false).To(gomega.Equal(true), "false is never true")
46+
})
47+
ginkgo.It("error", func() {
48+
err := errors.New("an error with a long, useless description")
49+
framework.ExpectNoError(err, "hard-coded error")
50+
})
51+
ginkgo.AfterEach(func() {
52+
log.Logf("after")
53+
gomega.Expect(true).To(gomega.Equal(false), "true is never false either")
54+
})
55+
})
56+
57+
func TestFailureOutput(t *testing.T) {
58+
// Run the Ginkgo suite with output collected by a custom
59+
// reporter in adddition to the default one. To see what the full
60+
// Ginkgo report looks like, run this test with "go test -v".
61+
config.DefaultReporterConfig.FullTrace = true
62+
gomega.RegisterFailHandler(log.Fail)
63+
fakeT := &testing.T{}
64+
reporter := reporters.NewFakeReporter()
65+
ginkgo.RunSpecsWithDefaultAndCustomReporters(fakeT, "Logging Suite", []ginkgo.Reporter{reporter})
66+
67+
// Now check the output.
68+
// TODO: all of the stacks are currently broken because Ginkgo doesn't properly skip
69+
// over the initial entries returned by runtime.Stack. Fix is pending in
70+
// https://github.com/onsi/ginkgo/pull/590, "stack" texts need to be updated
71+
// when updating to a fixed Ginkgo.
72+
g := gomega.NewGomegaWithT(t)
73+
actual := normalizeReport(*reporter)
74+
expected := suiteResults{
75+
testResult{
76+
name: "[Top Level] log asserts",
77+
output: "INFO: before\nFAIL: false is never true\nExpected\n <bool>: false\nto equal\n <bool>: true\nINFO: after\nFAIL: true is never false either\nExpected\n <bool>: true\nto equal\n <bool>: false\n",
78+
failure: "false is never true\nExpected\n <bool>: false\nto equal\n <bool>: true",
79+
// TODO: should start with k8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.3()
80+
stack: "\tassertion.go:75\nk8s.io/kubernetes/vendor/github.com/onsi/gomega/internal/assertion.(*Assertion).To()\n\tassertion.go:38\nk8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.3()\n\tlogger_test.go:45\nk8s.io/kubernetes/vendor/github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync()\n\tlogger_test.go:65\n",
81+
},
82+
testResult{
83+
name: "[Top Level] log error",
84+
output: "INFO: before\nFAIL: hard-coded error\nUnexpected error:\n <*errors.errorString>: {\n s: \"an error with a long, useless description\",\n }\n an error with a long, useless description\noccurred\nINFO: after\nFAIL: true is never false either\nExpected\n <bool>: true\nto equal\n <bool>: false\n",
85+
failure: "hard-coded error\nUnexpected error:\n <*errors.errorString>: {\n s: \"an error with a long, useless description\",\n }\n an error with a long, useless description\noccurred",
86+
// TODO: should start with k8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.4()
87+
stack: "\tutil.go:1362\nk8s.io/kubernetes/test/e2e/framework.ExpectNoError()\n\tutil.go:1356\nk8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.4()\n\tlogger_test.go:49\nk8s.io/kubernetes/vendor/github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync()\n\tlogger_test.go:65\n",
88+
},
89+
testResult{
90+
name: "[Top Level] log fails",
91+
output: "INFO: before\nFAIL: I'm failing.\nINFO: after\nFAIL: true is never false either\nExpected\n <bool>: true\nto equal\n <bool>: false\n",
92+
failure: "I'm failing.",
93+
// TODO: should start with k8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.2.1(...)
94+
stack: "\tlogger.go:52\nk8s.io/kubernetes/test/e2e/framework/log.Failf()\n\tlogger.go:44\nk8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.2.1(...)\n\tlogger_test.go:41\nk8s.io/kubernetes/test/e2e/framework/log_test.glob..func1.2()\n\tlogger_test.go:42\nk8s.io/kubernetes/vendor/github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync()\n\tlogger_test.go:65\n",
95+
},
96+
}
97+
// Compare individual fields. Comparing the slices leads to unreadable error output when there is any mismatch.
98+
g.Expect(len(actual)).To(gomega.Equal(len(expected)), "%d entries in %v", len(expected), actual)
99+
for i, a := range actual {
100+
b := expected[i]
101+
g.Expect(a.name).To(gomega.Equal(b.name), "name in %d", i)
102+
g.Expect(a.output).To(gomega.Equal(b.output), "output in %d", i)
103+
g.Expect(a.failure).To(gomega.Equal(b.failure), "failure in %d", i)
104+
g.Expect(a.stack).To(gomega.Equal(b.stack), "stack in %d", i)
105+
}
106+
}
107+
108+
type testResult struct {
109+
name string
110+
// output written to GinkgoWriter during test.
111+
output string
112+
// failure is SpecSummary.Failure.Message with varying parts stripped.
113+
failure string
114+
// stack is a normalized version (just file names, function parametes stripped) of
115+
// Ginkgo's FullStackTrace of a failure. Empty if no failure.
116+
stack string
117+
}
118+
119+
type suiteResults []testResult
120+
121+
func normalizeReport(report reporters.FakeReporter) suiteResults {
122+
var results suiteResults
123+
for _, spec := range report.SpecSummaries {
124+
results = append(results, testResult{
125+
name: strings.Join(spec.ComponentTexts, " "),
126+
output: stripAddresses(stripTimes(spec.CapturedOutput)),
127+
failure: stripAddresses(stripTimes(spec.Failure.Message)),
128+
stack: normalizeLocation(spec.Failure.Location.FullStackTrace),
129+
})
130+
}
131+
sort.Slice(results, func(i, j int) bool {
132+
return strings.Compare(results[i].name, results[j].name) < 0
133+
})
134+
return results
135+
}
136+
137+
// timePrefix matches "Jul 17 08:08:25.950: " at the beginning of each line.
138+
var timePrefix = regexp.MustCompile(`(?m)^[[:alpha:]]{3} [[:digit:]]{1,2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}.[[:digit:]]{3}: `)
139+
140+
func stripTimes(in string) string {
141+
return timePrefix.ReplaceAllString(in, "")
142+
}
143+
144+
// instanceAddr matches " | 0xc0003dec60>"
145+
var instanceAddr = regexp.MustCompile(` \| 0x[0-9a-fA-F]+>`)
146+
147+
func stripAddresses(in string) string {
148+
return instanceAddr.ReplaceAllString(in, ">")
149+
}
150+
151+
// stackLocation matches "<some path>/<file>.go:75 +0x1f1" after a slash (built
152+
// locally) or one of a few relative paths (built in the Kubernetes CI).
153+
var stackLocation = regexp.MustCompile(`(?:/|vendor/|test/|GOROOT/).*/([[:^space:]]+.go:[[:digit:]]+)( \+0x[0-9a-fA-F]+)?`)
154+
155+
// functionArgs matches "<function name>(...)".
156+
var functionArgs = regexp.MustCompile(`([[:alpha:]]+)\(.*\)`)
157+
158+
// testingStackEntries matches "testing.tRunner" and "created by" entries.
159+
var testingStackEntries = regexp.MustCompile(`(?m)(?:testing\.|created by).*\n\t.*\n`)
160+
161+
// normalizeLocation removes path prefix and function parameters and certain stack entries
162+
// that we don't care about.
163+
func normalizeLocation(in string) string {
164+
out := in
165+
out = stackLocation.ReplaceAllString(out, "$1")
166+
out = functionArgs.ReplaceAllString(out, "$1()")
167+
out = testingStackEntries.ReplaceAllString(out, "")
168+
return out
169+
}

test/e2e/framework/util.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,10 +1359,6 @@ func ExpectNoError(err error, explain ...interface{}) {
13591359
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
13601360
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
13611361
func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
1362-
if err != nil {
1363-
e2elog.Logf("Unexpected error occurred: %v", err)
1364-
debug.PrintStack()
1365-
}
13661362
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
13671363
}
13681364

0 commit comments

Comments
 (0)