Skip to content

Commit 0ed97c0

Browse files
committed
Enhance debugging output
Following on assertive debugging info cleanup, this revamp how we display failures. - previously, every assert failure would receive the entire execution information - we no longer do that and instead print out command information first, separately - a lot of headers were printed about different stages (setup, cleanup), even when there was nothing done in these stages, resulting in a lot of noise - the new display embraces a more structured, tabled output, that should significantly enhance legibility Signed-off-by: apostasie <[email protected]>
1 parent 46b45af commit 0ed97c0

File tree

13 files changed

+251
-140
lines changed

13 files changed

+251
-140
lines changed

Diff for: mod/tigron/.golangci.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ linters:
4646
- gomodguard # we use depguard instead
4747
- ireturn # too annoying with not enough value
4848
- lll # provided by golines
49+
- nestif # already provided ten different ways with revive cognitive complexity, etc
4950
- nonamedreturns # named returns are occasionally useful
5051
- prealloc # premature optimization
5152
- promlinter # no prometheus
@@ -64,7 +65,7 @@ linters:
6465
arguments: [60]
6566
- name: function-length
6667
# Default is 50, 75
67-
arguments: [80, 160]
68+
arguments: [80, 180]
6869
- name: cyclomatic
6970
# Default is 10
7071
arguments: [30]

Diff for: mod/tigron/expect/comparators.go

+13-13
Original file line numberDiff line numberDiff line change
@@ -30,59 +30,59 @@ import (
3030

3131
// All can be used as a parameter for expected.Output to group a set of comparators.
3232
func All(comparators ...test.Comparator) test.Comparator {
33-
return func(stdout, info string, t *testing.T) {
33+
return func(stdout, _ string, t *testing.T) {
3434
t.Helper()
3535

3636
for _, comparator := range comparators {
37-
comparator(stdout, info, t)
37+
comparator(stdout, "", t)
3838
}
3939
}
4040
}
4141

4242
// Contains can be used as a parameter for expected.Output and ensures a comparison string is found contained in the
4343
// output.
4444
func Contains(compare string) test.Comparator {
45-
return func(stdout, info string, t *testing.T) {
45+
return func(stdout, _ string, t *testing.T) {
4646
t.Helper()
47-
assertive.Contains(assertive.WithFailLater(t), stdout, compare, info)
47+
assertive.Contains(assertive.WithFailLater(t), stdout, compare, "Inspecting output (contains)")
4848
}
4949
}
5050

5151
// DoesNotContain is to be used for expected.Output to ensure a comparison string is NOT found in the output.
5252
func DoesNotContain(compare string) test.Comparator {
53-
return func(stdout, info string, t *testing.T) {
53+
return func(stdout, _ string, t *testing.T) {
5454
t.Helper()
55-
assertive.DoesNotContain(assertive.WithFailLater(t), stdout, compare, info)
55+
assertive.DoesNotContain(assertive.WithFailLater(t), stdout, compare, "Inspecting output (does not contain)")
5656
}
5757
}
5858

5959
// Equals is to be used for expected.Output to ensure it is exactly the output.
6060
func Equals(compare string) test.Comparator {
61-
return func(stdout, info string, t *testing.T) {
61+
return func(stdout, _ string, t *testing.T) {
6262
t.Helper()
63-
assertive.IsEqual(assertive.WithFailLater(t), stdout, compare, info)
63+
assertive.IsEqual(assertive.WithFailLater(t), stdout, compare, "Inspecting output (equals)")
6464
}
6565
}
6666

6767
// Match is to be used for expected.Output to ensure we match a regexp.
6868
func Match(reg *regexp.Regexp) test.Comparator {
69-
return func(stdout, info string, t *testing.T) {
69+
return func(stdout, _ string, t *testing.T) {
7070
t.Helper()
71-
assertive.Match(assertive.WithFailLater(t), stdout, reg, info)
71+
assertive.Match(assertive.WithFailLater(t), stdout, reg, "Inspecting output (match)")
7272
}
7373
}
7474

7575
// JSON allows to verify that the output can be marshalled into T, and optionally can be further verified by a provided
7676
// method.
7777
func JSON[T any](obj T, verifier func(T, string, tig.T)) test.Comparator {
78-
return func(stdout, info string, t *testing.T) {
78+
return func(stdout, _ string, t *testing.T) {
7979
t.Helper()
8080

8181
err := json.Unmarshal([]byte(stdout), &obj)
82-
assertive.ErrorIsNil(assertive.WithFailLater(t), err, "failed to unmarshal JSON from stdout")
82+
assertive.ErrorIsNil(assertive.WithFailLater(t), err, "Unmarshalling JSON from stdout must succeed")
8383

8484
if verifier != nil && err == nil {
85-
verifier(obj, info, t)
85+
verifier(obj, "Inspecting output (JSON)", t)
8686
}
8787
}
8888
}

Diff for: mod/tigron/internal/assertive/assertive.go

+58-28
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ import (
3131
"github.com/containerd/nerdctl/mod/tigron/tig"
3232
)
3333

34-
// TODO: once debugging output will be cleaned-up, reintroduce hexdump.
35-
3634
const (
35+
markLineLength = 20
3736
expectedSuccessDecorator = "✅️ does verify:\t\t"
38-
expectedFailDecorator = "❌ does not verify:\t"
37+
expectedFailDecorator = "❌ FAILED!\t\t"
3938
receivedDecorator = "👀 testing:\t\t"
39+
annotationDecorator = "🖊️"
4040
hyperlinkDecorator = "🔗"
4141
)
4242

@@ -205,49 +205,79 @@ func evaluate(testing tig.T, isSuccess bool, actual, expected any, msg ...string
205205
func decorate(testing tig.T, isSuccess bool, actual, expected any, msg ...string) {
206206
testing.Helper()
207207

208-
header := "\t"
208+
if _, ok := testing.(*silentSuccess); !isSuccess || !ok {
209+
head := strings.Repeat("<", markLineLength)
210+
footer := strings.Repeat(">", markLineLength)
211+
header := "\t"
209212

210-
hyperlink := getTopFrameFile()
211-
if hyperlink != "" {
212-
msg = append([]string{hyperlink + "\n"}, msg...)
213-
}
213+
custom := fmt.Sprintf("\t%s %s", annotationDecorator, strings.Join(msg, "\n"))
214214

215-
msg = append(msg, fmt.Sprintf("\t%s`%v`", receivedDecorator, actual))
215+
msg = append([]string{"\n" + head}, custom)
216216

217-
if isSuccess {
218-
msg = append(msg,
219-
fmt.Sprintf("\t%s%v", expectedSuccessDecorator, expected),
220-
)
221-
} else {
222-
msg = append(msg,
223-
fmt.Sprintf("\t%s%v", expectedFailDecorator, expected),
224-
)
225-
}
217+
hyperlink := getTopFrameFile()
218+
if hyperlink != "" {
219+
msg = append([]string{hyperlink}, msg...)
220+
}
226221

227-
if _, ok := testing.(*silentSuccess); !isSuccess || !ok {
228-
testing.Log(header + strings.Join(msg, "\n") + "\n")
222+
msg = append(msg, fmt.Sprintf("\t%s`%v`", receivedDecorator, actual))
223+
224+
if isSuccess {
225+
msg = append(msg,
226+
fmt.Sprintf("\t%s%v", expectedSuccessDecorator, expected),
227+
)
228+
} else {
229+
msg = append(msg,
230+
fmt.Sprintf("\t%s%v", expectedFailDecorator, expected),
231+
)
232+
}
233+
234+
testing.Log(header + strings.Join(msg, "\n") + "\n" + footer + "\n")
229235
}
230236
}
231237

238+
// XXX FIXME #expert
239+
// Because of how golang testing works, the upper frame is the one from where t.Run is being called,
240+
// as (presumably) the passed function is starting with its own stack in a go routine.
241+
// In the case of subtests, t.Run being called from inside Tigron will make it so that the top frame
242+
// is case.go around line 233 (where we call Command.Run(), which is the one calling assertive).
243+
// To possibly address this:
244+
// plan a. just drop entirely OSC8 links and source extracts and trash all of this
245+
// plan b. get the top frame from the root test, and pass it to subtests on a custom property, the somehow into here
246+
// plan c. figure out a hack to call t.Run from the test file without ruining the Tigron UX
247+
// Dereference t.Run? Return a closure to be called from the top? w/o disabling inlining in the right place?
248+
// Short term, blacklisting /tigron (and /nerdtest) will at least prevent the wrong links from appearing in the output.
232249
func getTopFrameFile() string {
233-
// Get the frames.
250+
// Get the frames. Skip the first two frames - current one and caller.
234251
//nolint:mnd // Whatever mnd...
235-
pc := make([]uintptr, 20)
252+
pc := make([]uintptr, 40)
236253
//nolint:mnd // Whatever mnd...
237254
n := runtime.Callers(2, pc)
238255
callersFrames := runtime.CallersFrames(pc[:n])
239256

240-
var file string
257+
var (
258+
file string
259+
lineNumber int
260+
frame runtime.Frame
261+
)
241262

242-
var lineNumber int
263+
more := true
264+
for more {
265+
frame, more = callersFrames.Next()
243266

244-
var frame runtime.Frame
245-
for range 20 {
246-
frame, _ = callersFrames.Next()
267+
// Once we are in the go main stack, bail out
247268
if !strings.Contains(frame.Function, "/") {
248269
break
249270
}
250271

272+
// XXX see note above
273+
if strings.Contains(frame.File, "/tigron") {
274+
continue
275+
}
276+
277+
if strings.Contains(frame.File, "/nerdtest") {
278+
continue
279+
}
280+
251281
file = frame.File
252282
lineNumber = frame.Line
253283
}
@@ -282,6 +312,6 @@ func getTopFrameFile() string {
282312
return hyperlinkDecorator + " " + (&formatter.OSC8{
283313
Text: line,
284314
Location: "file://" + file,
285-
Line: frame.Line,
315+
Line: lineNumber,
286316
}).String()
287317
}

Diff for: mod/tigron/internal/com/command_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,9 @@ func TestTimeoutDelayed(t *testing.T) {
372372
assertive.ErrorIsNil(t, err, "Err")
373373

374374
start := time.Now()
375+
375376
time.Sleep(2 * time.Second)
377+
376378
res, err := command.Wait()
377379
end := time.Now()
378380

Diff for: mod/tigron/internal/formatter/formatter.go

+4-10
Original file line numberDiff line numberDiff line change
@@ -64,32 +64,26 @@ func chunk(s string, length int) []string {
6464
// Maybe the csv writer could be cheat-used to get the right widths.
6565
//
6666
//nolint:mnd // Too annoying
67-
func Table(data [][]any) string {
67+
func Table(data [][]any, mark string) string {
6868
var output string
6969

7070
for _, row := range data {
7171
key := fmt.Sprintf("%v", row[0])
7272
value := strings.ReplaceAll(fmt.Sprintf("%v", row[1]), "\t", " ")
7373

74-
output += fmt.Sprintf("+%s+\n", strings.Repeat("-", maxLineLength-2))
74+
output += fmt.Sprintf("+%s+\n", strings.Repeat(mark, maxLineLength-2))
7575

7676
if utf8.RuneCountInString(key) > kMaxLength {
7777
key = string([]rune(key)[:kMaxLength-3]) + "..."
7878
}
7979

8080
for _, line := range chunk(value, maxLineLength-kMaxLength-7) {
81-
output += fmt.Sprintf(
82-
"| %-*s | %-*s |\n",
83-
kMaxLength,
84-
key,
85-
maxLineLength-kMaxLength-7,
86-
line,
87-
)
81+
output += fmt.Sprintf("| %-*s | %-*s |\n", kMaxLength, key, maxLineLength-kMaxLength-7, line)
8882
key = ""
8983
}
9084
}
9185

92-
output += fmt.Sprintf("+%s+", strings.Repeat("-", maxLineLength-2))
86+
output += fmt.Sprintf("+%s+", strings.Repeat(mark, maxLineLength-2))
9387

9488
return output
9589
}

Diff for: mod/tigron/internal/mimicry/mocker.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,7 @@ func (mi *Core) Register(fun, handler any) {
137137

138138
func getFunID(fun any) string {
139139
// The point of keeping only the func name is to avoid type mismatch dependent on what interface
140-
// is used by the
141-
// consumer.
140+
// is used by the consumer.
142141
origin := runtime.FuncForPC(reflect.ValueOf(fun).Pointer()).Name()
143142
seg := strings.Split(origin, ".")
144143
origin = seg[len(seg)-1]

Diff for: mod/tigron/internal/mimicry/print.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func PrintCall(call *Call) string {
4040
}
4141

4242
output := []string{
43-
formatter.Table(debug),
43+
formatter.Table(debug, "-"),
4444
sectionSeparator,
4545
}
4646

0 commit comments

Comments
 (0)