From 1766f85bf9edba2cccff494a0a41bb30e244a01b Mon Sep 17 00:00:00 2001
From: sudoforge <no-reply@sudoforge.com>
Date: Thu, 8 May 2025 02:43:53 -0700
Subject: [PATCH] test: implement support for Failed() and FailedNow() (#1399)

This change adds support to //internal/test for Failed() and
FailedNow(), and expands the support for setting and detecting the
failed status on //internal/test%recorder.

Change-Id: I04e7a978cbf0ead8d28722c0a3a0fc34136e72e1
---
 internal/test/recorder.go | 34 ++++++++++++++++++++++++++++------
 internal/test/test.go     | 25 ++++++++++++++++++-------
 2 files changed, 46 insertions(+), 13 deletions(-)

diff --git a/internal/test/recorder.go b/internal/test/recorder.go
index 35ddb0d0..7d9f4a5f 100644
--- a/internal/test/recorder.go
+++ b/internal/test/recorder.go
@@ -5,24 +5,46 @@ import (
 	"testing"
 )
 
+const (
+	RecorderFailNow int = iota
+)
+
 type recorder struct {
 	testing.TB
-	fail  func(string)
-	fatal func(string)
+	fail   func(string)
+	fatal  func(string)
+	failed bool
+}
+
+func (r *recorder) Error(args ...any) {
+	r.failed = true
+	r.fail(fmt.Sprint(args...))
 }
 
 func (r *recorder) Errorf(format string, args ...any) {
+	r.failed = true
 	r.fail(fmt.Sprintf(format, args...))
 }
 
-func (r *recorder) Fatalf(format string, args ...any) {
-	r.fatal(fmt.Sprintf(format, args...))
+func (r *recorder) Fail() {
+	r.failed = true
+}
+
+func (r *recorder) FailNow() {
+	r.failed = true
+	panic(RecorderFailNow)
+}
+
+func (r *recorder) Failed() bool {
+	return r.failed
 }
 
 func (r *recorder) Fatal(args ...any) {
+	r.failed = true
 	r.fatal(fmt.Sprint(args...))
 }
 
-func (r *recorder) Error(args ...any) {
-	r.fail(fmt.Sprint(args...))
+func (r *recorder) Fatalf(format string, args ...any) {
+	r.failed = true
+	r.fatal(fmt.Sprintf(format, args...))
 }
diff --git a/internal/test/test.go b/internal/test/test.go
index 1dc052bf..8a40e30a 100644
--- a/internal/test/test.go
+++ b/internal/test/test.go
@@ -38,15 +38,26 @@ func (f *flaky) Run(fn func(t testing.TB)) {
 	var last error
 
 	for attempt := 1; attempt <= f.o.MaxAttempts; attempt++ {
-		var failed bool
+		f.t.Logf("attempt %d of %d", attempt, f.o.MaxAttempts)
 
-		fn(&recorder{
+		r := &recorder{
 			TB:    f.t,
-			fail:  func(e string) { failed = true; last = errors.New(e) },
-			fatal: func(e string) { failed = true; last = errors.New(e) },
-		})
+			fail:  func(s string) { last = errors.New(s) },
+			fatal: func(s string) { last = errors.New(s) },
+		}
+
+		func() {
+			defer func() {
+				if v := recover(); v != nil {
+					if code, ok := v.(int); ok && code != RecorderFailNow {
+						panic(v)
+					}
+				}
+			}()
+			fn(r)
+		}()
 
-		if !failed {
+		if !r.Failed() {
 			return
 		}
 
@@ -56,7 +67,7 @@ func (f *flaky) Run(fn func(t testing.TB)) {
 		}
 	}
 
-	f.t.Fatalf("[%s] test failed after %d attempts: %s", f.t.Name(), f.o.MaxAttempts, last)
+	f.t.Fatalf("[%s] test failed after %d attempts: %v", f.t.Name(), f.o.MaxAttempts, last)
 }
 
 func applyJitter(d time.Duration, jitter float64) time.Duration {
-- 
GitLab