Skip to content

Commit 1603a7d

Browse files
authored
Merge pull request #133 from maxatome/trace++
fix(trace): ignore internal testing/go-testdeep calls in error traces
2 parents 67b17fd + 544584c commit 1603a7d

7 files changed

Lines changed: 288 additions & 81 deletions

File tree

internal/trace/stack.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2021, Maxime Soulé
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the BSD-style license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
7+
package trace
8+
9+
import (
10+
"strings"
11+
)
12+
13+
// Level is a level when retrieving a stack trace.
14+
type Level struct {
15+
Package string
16+
Func string
17+
FileLine string
18+
}
19+
20+
// Stack is a simple stack trace.
21+
type Stack []Level
22+
23+
// Match returns true if the ith level of s matches pkg (if not empty)
24+
// and any function in anyFunc.
25+
//
26+
// If anyFunc is empty, only the package is tested.
27+
//
28+
// If a function in anyFunc ends with "*", only the prefix is checked.
29+
func (s Stack) Match(i int, pkg string, anyFunc ...string) bool {
30+
if i < 0 {
31+
i = len(s) + i
32+
}
33+
if i < 0 || i >= len(s) {
34+
return false
35+
}
36+
37+
level := s[i]
38+
39+
if pkg != "" && level.Package != pkg {
40+
return false
41+
}
42+
43+
if len(anyFunc) == 0 {
44+
return true
45+
}
46+
47+
for _, fn := range anyFunc {
48+
if strings.HasSuffix(fn, "*") {
49+
if strings.HasPrefix(level.Func, fn[:len(fn)-1]) {
50+
return true
51+
}
52+
} else if level.Func == fn {
53+
return true
54+
}
55+
}
56+
return false
57+
}

internal/trace/stack_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2021, Maxime Soulé
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the BSD-style license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
7+
package trace_test
8+
9+
import (
10+
"testing"
11+
12+
"github.com/maxatome/go-testdeep/internal/test"
13+
"github.com/maxatome/go-testdeep/internal/trace"
14+
)
15+
16+
func TestStack(t *testing.T) {
17+
s := trace.Stack{
18+
{Package: "A", Func: "Aaa.func1"},
19+
{Package: "A", Func: "Aaa.func2"},
20+
{Package: "B", Func: "Bbb"},
21+
{Package: "C", Func: "Ccc"},
22+
}
23+
24+
test.IsFalse(t, s.Match(100, "A"))
25+
test.IsFalse(t, s.Match(-100, "A"))
26+
27+
test.IsFalse(t, s.Match(3, "B"))
28+
test.IsFalse(t, s.Match(-1, "B"))
29+
30+
test.IsTrue(t, s.Match(3, "C"))
31+
test.IsTrue(t, s.Match(-1, "C"))
32+
33+
test.IsFalse(t, s.Match(1, "A", "Aaa.func3", "Aaa.func1"))
34+
test.IsTrue(t, s.Match(1, "A", "Aaa.func3", "Aaa.func2"))
35+
test.IsTrue(t, s.Match(1, "A", "Aaa.func3", "Aaa.func*"))
36+
}

internal/trace/trace.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@ var (
2121
goModDir string
2222
)
2323

24-
// Level represents a level when retrieving a trace.
25-
type Level struct {
26-
Package string
27-
Func string
28-
FileLine string
29-
}
30-
3124
func getPackage(skip ...int) string {
3225
sk := 2
3326
if len(skip) > 0 {
@@ -149,8 +142,8 @@ var CallersFrames = func(callers []uintptr) Frames {
149142
}
150143

151144
// Retrieve retrieves a trace and returns it.
152-
func Retrieve(skip int, endFunction string) []Level {
153-
var trace []Level
145+
func Retrieve(skip int, endFunction string) Stack {
146+
var trace Stack
154147
var pc [40]uintptr
155148
if num := runtime.Callers(skip+2, pc[:]); num > 0 {
156149
checkIgnore := true

internal/trace/trace_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ import (
1414
"runtime"
1515
"testing"
1616

17-
"github.com/maxatome/go-testdeep/internal/trace"
18-
1917
"github.com/maxatome/go-testdeep/internal/test"
18+
"github.com/maxatome/go-testdeep/internal/trace"
2019
)
2120

2221
func TestIgnorePackage(t *testing.T) {

td/cmp_deeply.go

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,64 +25,92 @@ func init() {
2525
}
2626

2727
// stripTrace removes go-testdeep useless calls in a trace returned by
28-
// trace.Retrieve().
29-
//
30-
// Remove (*T).Run() call:
31-
// A() zz.go:20
32-
// TestSubtestTd.func1() zz_test.go:24
33-
// →(*T).Run.func1() github.com/maxatome/go-testdeep@xxx/td/t_struct.go:554
34-
//
35-
// Remove (*T).RunAssertRequire() call:
36-
// A() zz.go:20
37-
// TestSubtestTd.func1() zz_test.go:24
38-
// →(*T).RunAssertRequire.func1() github.com/maxatome/go-testdeep@xxx/td/t_struct.go:554
39-
//
40-
// Remove (*TestAPI).Run() call:
41-
// Subtest.func1() zz.go:20
42-
// →(*TestAPI).Run.func1() github.com/maxatome/go-testdeep@xxx/helpers/tdhttp/test_api.go:119
43-
// ⇒(*T).Run.func1() github.com/maxatome/go-testdeep@xxx/td/t_struct.go:554
44-
//
45-
// Remove github.com/maxatome/go-testdeep/helpers/tdsuite calls:
46-
// A() zz.go:20
47-
// Suite.TestSuite() zz_test.go:35
48-
// →Value.call() $GOROOT/src/reflect/value.go:476
49-
// →Value.Call() $GOROOT/src/reflect/value.go:337
50-
// →run.func2() github.com/maxatome/go-testdeep@xxx/helpers/tdsuite/suite.go:301
51-
// ⇒(*T).Run.func1() github.com/maxatome/go-testdeep@xxx/td/t_struct.go:554
52-
func stripTrace(tce []trace.Level) []trace.Level {
53-
if len(tce) <= 1 {
54-
return tce
28+
// trace.Retrieve() to make it clearer for the reader.
29+
func stripTrace(s trace.Stack) trace.Stack {
30+
if len(s) <= 1 {
31+
return s
5532
}
5633

34+
const (
35+
tdPkg = "github.com/maxatome/go-testdeep/td"
36+
tdhttpPkg = "github.com/maxatome/go-testdeep/helpers/tdhttp"
37+
tdsuitePkg = "github.com/maxatome/go-testdeep/helpers/tdsuite"
38+
)
39+
5740
// Remove useless possible (*T).Run() or (*T).RunAssertRequire() first call
58-
first := tce[len(tce)-1]
59-
if first.Package != "github.com/maxatome/go-testdeep/td" ||
60-
(first.Func != "(*T).Run.func1" &&
61-
first.Func != "(*T).RunAssertRequire.func1") {
62-
return tce
63-
}
41+
if s.Match(-1, tdPkg, "(*T).Run.func1", "(*T).RunAssertRequire.func1") {
42+
// Remove useless tdhttp (*TestAPI).Run() call
43+
//
44+
// ✓ xxx Subtest.func1()
45+
// ✗ …/tdhttp (*TestAPI).Run.func1
46+
// ✗ …/td (*T).Run.func1()
47+
if s.Match(-2, tdhttpPkg, "(*TestAPI).Run.func1") {
48+
return s[:len(s)-2]
49+
}
6450

65-
tce = tce[:len(tce)-1]
66-
first = tce[len(tce)-1]
51+
// Remove useless tdsuite calls
52+
//
53+
// ✓ xxx Suite.TestSuite
54+
// ✗ reflect Value.call
55+
// ✗ reflect Value.Call
56+
// ✗ …/tdsuite run.func2
57+
// ✗ …/td (*T).Run.func1() or (*T).RunAssertRequire.func1()
58+
//
59+
// or for PostTest
60+
// ✓ xxx Suite.PostTest
61+
// ✗ …/tdsuite run.func2.1
62+
// ✗ …/tdsuite run.func2
63+
// ✗ …/td (*T).Run.func1() or (*T).RunAssertRequire.func1()
64+
if s.Match(-2, tdsuitePkg, "run.func*") {
65+
// PostTest
66+
if s.Match(-3, tdsuitePkg, "run.func*") &&
67+
len(s) > 4 &&
68+
strings.HasSuffix(s[len(s)-4].Func, ".PostTest") {
69+
return s[:len(s)-3]
70+
}
71+
72+
for i := len(s) - 3; i >= 1; i-- {
73+
if !s.Match(i, "reflect") {
74+
return s[:i+1]
75+
}
76+
}
77+
return nil
78+
}
6779

68-
// Remove useless tdhttp (*TestAPI).Run() call
69-
if first.Package == "github.com/maxatome/go-testdeep/helpers/tdhttp" &&
70-
first.Func == "(*TestAPI).Run.func1" {
71-
return tce[:len(tce)-1]
80+
return s[:len(s)-1]
7281
}
7382

74-
// Remove useless tdsuite calls
75-
if first.Package != "github.com/maxatome/go-testdeep/helpers/tdsuite" ||
76-
!strings.HasPrefix(first.Func, "run.func") {
77-
return tce
83+
// Remove testing.Cleanup() stack
84+
//
85+
// ✓ xxx TestCleanup.func2
86+
// ✗ testing (*common).Cleanup.func1
87+
// ✗ testing (*common).runCleanup
88+
// ✗ testing tRunner.func2
89+
if s.Match(-1, "testing", "tRunner.func*") &&
90+
s.Match(-2, "testing", "(*common).runCleanup") &&
91+
s.Match(-3, "testing", "(*common).Cleanup.func1") {
92+
return s[:len(s)-3]
7893
}
7994

80-
for i := len(tce) - 2; i >= 1; i-- {
81-
if tce[i].Package != "reflect" {
82-
return tce[:i+1]
95+
// Remove tdsuite pre-Setup/BetweenTests/Destroy stack
96+
//
97+
// ✓ xxx Suite.Destroy
98+
// ✗ …/tdsuite run.func1
99+
// ✗ …/tdsuite run
100+
// ✗ …/tdsuite Run
101+
// ✓ xxx TestSuiteDestroy
102+
if !s.Match(-1, tdsuitePkg) &&
103+
s.Match(-2, tdsuitePkg, "Run") {
104+
for i := len(s) - 3; i >= 0; i-- {
105+
if !s.Match(i, tdsuitePkg) {
106+
s[i+1] = s[len(s)-1]
107+
return s[:i+2]
108+
}
83109
}
110+
return s[:1]
84111
}
85-
return nil
112+
113+
return s
86114
}
87115

88116
func formatError(t TestingT, isFatal bool, err *ctxerr.Error, args ...interface{}) {
@@ -106,19 +134,19 @@ func formatError(t TestingT, isFatal bool, err *ctxerr.Error, args ...interface{
106134
err.Append(&buf, "")
107135

108136
// Stask trace
109-
if tce := stripTrace(trace.Retrieve(0, "testing.tRunner")); len(tce) > 1 {
137+
if s := stripTrace(trace.Retrieve(0, "testing.tRunner")); len(s) > 1 {
110138
buf.WriteString("\nThis is how we got here:\n")
111139

112140
fnMaxLen := 0
113-
for _, level := range tce {
141+
for _, level := range s {
114142
if len(level.Func) > fnMaxLen {
115143
fnMaxLen = len(level.Func)
116144
}
117145
}
118146
fnMaxLen += 2
119147

120148
nl := ""
121-
for _, level := range tce {
149+
for _, level := range s {
122150
fmt.Fprintf(&buf, "%s\t%-*s %s", nl, fnMaxLen, level.Func+"()", level.FileLine)
123151
nl = "\n"
124152
}

0 commit comments

Comments
 (0)