diff options
| author | Jonathan Amsterdam <jba@google.com> | 2025-05-13 11:50:58 -0700 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-05-13 12:26:50 -0700 |
| commit | afd6b0bc663cf49e74200729da91785219d28b53 (patch) | |
| tree | 9a1daf41f44c1452c4dc939f8e493f4cbfdde76b /src/testing/testing.go | |
| parent | 9bba799955e68972041c4f340ee4ea2d267e5c0e (diff) | |
| download | go-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.go | 214 |
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) |
