aboutsummaryrefslogtreecommitdiff
path: root/src/testing/testing.go
diff options
context:
space:
mode:
authorJonathan Amsterdam <jba@google.com>2025-05-13 11:50:58 -0700
committerGopher Robot <gobot@golang.org>2025-05-13 12:26:50 -0700
commitafd6b0bc663cf49e74200729da91785219d28b53 (patch)
tree9a1daf41f44c1452c4dc939f8e493f4cbfdde76b /src/testing/testing.go
parent9bba799955e68972041c4f340ee4ea2d267e5c0e (diff)
downloadgo-afd6b0bc663cf49e74200729da91785219d28b53.tar.xz
Revert "testing: add Output"
This reverts commit 8d189f188e225e4919b34c0c097e75dfda255949. Reason for revert: failing test Change-Id: I951087eaef7818697acf87e3206003bcc8a81ee2 Reviewed-on: https://go-review.googlesource.com/c/go/+/672335 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> Auto-Submit: Michael Knyszek <mknyszek@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com>
Diffstat (limited to 'src/testing/testing.go')
-rw-r--r--src/testing/testing.go214
1 files changed, 64 insertions, 150 deletions
diff --git a/src/testing/testing.go b/src/testing/testing.go
index efbcd59dc0..d50abea32f 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -631,7 +631,6 @@ type common struct {
mu sync.RWMutex // guards this group of fields
output []byte // Output generated by test or benchmark.
w io.Writer // For flushToParent.
- o *outputWriter // Writes output.
ran bool // Test or benchmark (or one of its subtests) was executed.
failed bool // Test or benchmark has failed.
skipped bool // Test or benchmark has been skipped.
@@ -800,6 +799,44 @@ func (c *common) frameSkip(skip int) runtime.Frame {
return firstFrame
}
+// decorate prefixes the string with the file and line of the call site
+// and inserts the final newline if needed and indentation spaces for formatting.
+// This function must be called with c.mu held.
+func (c *common) decorate(s string, skip int) string {
+ frame := c.frameSkip(skip)
+ file := frame.File
+ line := frame.Line
+ if file != "" {
+ if *fullPath {
+ // If relative path, truncate file name at last file name separator.
+ } else if index := strings.LastIndexAny(file, `/\`); index >= 0 {
+ file = file[index+1:]
+ }
+ } else {
+ file = "???"
+ }
+ if line == 0 {
+ line = 1
+ }
+ buf := new(strings.Builder)
+ // Every line is indented at least 4 spaces.
+ buf.WriteString(" ")
+ fmt.Fprintf(buf, "%s:%d: ", file, line)
+ lines := strings.Split(s, "\n")
+ if l := len(lines); l > 1 && lines[l-1] == "" {
+ lines = lines[:l-1]
+ }
+ for i, line := range lines {
+ if i > 0 {
+ // Second and subsequent lines are indented an additional 4 spaces.
+ buf.WriteString("\n ")
+ }
+ buf.WriteString(line)
+ }
+ buf.WriteByte('\n')
+ return buf.String()
+}
+
// flushToParent writes c.output to the parent after first writing the header
// with the given format and arguments.
func (c *common) flushToParent(testName, format string, args ...any) {
@@ -845,8 +882,6 @@ type indenter struct {
c *common
}
-const indent = " "
-
func (w indenter) Write(b []byte) (n int, err error) {
n = len(b)
for len(b) > 0 {
@@ -863,6 +898,7 @@ func (w indenter) Write(b []byte) (n int, err error) {
w.c.output = append(w.c.output, marker)
line = line[1:]
}
+ const indent = " "
w.c.output = append(w.c.output, indent...)
w.c.output = append(w.c.output, line...)
b = b[end:]
@@ -1007,156 +1043,41 @@ func (c *common) FailNow() {
runtime.Goexit()
}
-// log generates the output. It is always at the same stack depth. log inserts
-// indentation and the final newline if necessary. It prefixes the string
-// with the file and line of the call site.
+// log generates the output. It's always at the same stack depth.
func (c *common) log(s string) {
- s = strings.TrimSuffix(s, "\n")
-
- // Second and subsequent lines are indented 4 spaces. This is in addition to
- // the indentation provided by outputWriter.
- s = strings.ReplaceAll(s, "\n", "\n"+indent)
- s += "\n"
-
- n := c.destination()
- if n == nil {
- // The test and all its parents are done. The log cannot be output.
- panic("Log in goroutine after " + c.name + " has completed: " + s)
- }
-
- // Prefix with the call site. It is located by skipping 3 functions:
- // callSite + log + public function
- s = n.callSite(3) + s
-
- // Output buffered logs.
- n.flushPartial()
-
- n.o.Write([]byte(s))
-}
-
-// destination selects the test to which output should be appended. It returns the
-// test if it is incomplete. Otherwise, it finds its closest incomplete parent.
-func (c *common) destination() *common {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- if !c.done {
- return c
- }
- for parent := c.parent; parent != nil; parent = parent.parent {
- parent.mu.Lock()
- defer parent.mu.Unlock()
- if !parent.done {
- return parent
- }
- }
- return nil
+ c.logDepth(s, 3) // logDepth + log + public function
}
-// callSite retrieves and formats the file and line of the call site.
-func (c *common) callSite(skip int) string {
+// logDepth generates the output at an arbitrary stack depth.
+func (c *common) logDepth(s string, depth int) {
c.mu.Lock()
defer c.mu.Unlock()
-
- frame := c.frameSkip(skip)
- file := frame.File
- line := frame.Line
- if file != "" {
- if *fullPath {
- // If relative path, truncate file name at last file name separator.
- } else {
- file = filepath.Base(file)
+ if c.done {
+ // This test has already finished. Try and log this message
+ // with our parent. If we don't have a parent, panic.
+ for parent := c.parent; parent != nil; parent = parent.parent {
+ parent.mu.Lock()
+ defer parent.mu.Unlock()
+ if !parent.done {
+ parent.output = append(parent.output, parent.decorate(s, depth+1)...)
+ return
+ }
}
+ panic("Log in goroutine after " + c.name + " has completed: " + s)
} else {
- file = "???"
- }
- if line == 0 {
- line = 1
- }
-
- return fmt.Sprintf("%s:%d: ", file, line)
-}
-
-// flushPartial checks the buffer for partial logs and outputs them.
-func (c *common) flushPartial() {
- partial := func() bool {
- c.mu.Lock()
- defer c.mu.Unlock()
- return (c.o != nil) && (len(c.o.partial) > 0)
- }
-
- if partial() {
- c.o.Write([]byte("\n"))
- }
-}
-
-// Output returns a Writer that writes to the same test output stream as TB.Log.
-// The output is indented like TB.Log lines, but Output does not
-// add source locations or newlines. The output is internally line
-// buffered, and a call to TB.Log or the end of the test will implicitly
-// flush the buffer, followed by a newline. After a test function and all its
-// parents return, neither Output nor the Write method may be called.
-func (c *common) Output() io.Writer {
- c.checkFuzzFn("Output")
- n := c.destination()
- if n == nil {
- panic("Output called after " + c.name + " has completed")
- }
- return n.o
-}
-
-// setOutputWriter initializes an outputWriter and sets it as a common field.
-func (c *common) setOutputWriter() {
- c.o = &outputWriter{c: c}
-}
-
-// outputWriter buffers, formats and writes log messages.
-type outputWriter struct {
- c *common
- partial []byte // incomplete ('\n'-free) suffix of last Write
-}
-
-// Write writes a log message to the test's output stream, properly formatted and
-// indented. It may not be called after a test function and all its parents return.
-func (o *outputWriter) Write(p []byte) (int, error) {
- if o.c.destination() == nil {
- panic("Write called after " + o.c.name + " has completed")
- }
-
- o.c.mu.Lock()
- defer o.c.mu.Unlock()
-
- // The last element is a partial line.
- lines := bytes.SplitAfter(p, []byte("\n"))
- last := len(lines) - 1 // Inv: 0 <= last
- for i, line := range lines[:last] {
- // Emit partial line from previous call.
- if i == 0 && len(o.partial) > 0 {
- line = slices.Concat(o.partial, line)
- o.partial = o.partial[:0]
- }
- o.writeLine(line)
- }
- // Save partial line for next call.
- o.partial = append(o.partial, lines[last]...)
-
- return len(p), nil
-}
+ if c.chatty != nil {
+ if c.bench {
+ // Benchmarks don't print === CONT, so we should skip the test
+ // printer and just print straight to stdout.
+ fmt.Print(c.decorate(s, depth+1))
+ } else {
+ c.chatty.Printf(c.name, "%s", c.decorate(s, depth+1))
+ }
-// writeLine generates the output for a given line.
-func (o *outputWriter) writeLine(b []byte) {
- if !o.c.done && (o.c.chatty != nil) {
- if o.c.bench {
- // Benchmarks don't print === CONT, so we should skip the test
- // printer and just print straight to stdout.
- fmt.Printf("%s%s", indent, b)
- } else {
- o.c.chatty.Printf(o.c.name, "%s%s", indent, b)
+ return
}
- return
+ c.output = append(c.output, c.decorate(s, depth+1)...)
}
- o.c.output = append(o.c.output, indent...)
- o.c.output = append(o.c.output, b...)
}
// Log formats its arguments using default formatting, analogous to [fmt.Println],
@@ -1818,8 +1739,6 @@ func tRunner(t *T, fn func(t *T)) {
root.duration += highPrecisionTimeSince(root.start)
d := root.duration
root.mu.Unlock()
- // Output buffered logs.
- root.flushPartial()
root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil {
fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)
@@ -1867,10 +1786,6 @@ func tRunner(t *T, fn func(t *T)) {
// test. See comment in Run method.
t.tstate.release()
}
- // Output buffered logs.
- for root := &t.common; root.parent != nil; root = root.parent {
- root.flushPartial()
- }
t.report() // Report after all subtests have finished.
// Do not lock t.done to allow race detector to detect race in case
@@ -1936,7 +1851,6 @@ func (t *T) Run(name string, f func(t *T)) bool {
tstate: t.tstate,
}
t.w = indenter{&t.common}
- t.setOutputWriter()
if t.chatty != nil {
t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)