| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package runtime_test |
| |
| import ( |
| "bytes" |
| "math" |
| "runtime" |
| "strconv" |
| "strings" |
| "sync" |
| "testing" |
| ) |
| |
| // printlock is skipped while a goroutine's output is diverted into its own |
| // writebuf, so exercise that path from several goroutines at once and check |
| // that each one still gets exactly its own output. |
| func TestPrintConcurrentWritebuf(t *testing.T) { |
| const goroutines = 8 |
| const iters = 200 |
| |
| var wg sync.WaitGroup |
| for g := range goroutines { |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| for i := range iters { |
| v := g*iters + i |
| if got, want := runtime.DumpPrint(v), strconv.Itoa(v); got != want { |
| t.Errorf("DumpPrint(%d) = %q, want %q", v, got, want) |
| return |
| } |
| } |
| }() |
| } |
| wg.Wait() |
| } |
| |
| // Stack writes through the same path. Concurrent callers asking only for |
| // their own stack must each get one well-formed traceback. |
| func TestStackConcurrent(t *testing.T) { |
| const goroutines = 8 |
| const iters = 50 |
| |
| var wg sync.WaitGroup |
| for range goroutines { |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| buf := make([]byte, 8192) |
| for range iters { |
| s := string(buf[:runtime.Stack(buf, false)]) |
| if !strings.HasPrefix(s, "goroutine ") { |
| t.Errorf("Stack does not begin with %q: %.64q", "goroutine ", s) |
| return |
| } |
| if n := strings.Count(s, "\ngoroutine "); n != 0 { |
| t.Errorf("Stack(all=false) reported %d other goroutines:\n%s", n, s) |
| return |
| } |
| if !strings.Contains(s, "runtime_test.TestStackConcurrent") { |
| t.Errorf("Stack is missing its own frame:\n%s", s) |
| return |
| } |
| } |
| }() |
| } |
| wg.Wait() |
| } |
| |
| // Diverted output must not reach the print backlog, which is a global that |
| // recordForPanic maintains under printlock. If it did, concurrent Stack calls |
| // would race on it, and each call would also overwrite the crash context the |
| // backlog exists to preserve. |
| func TestStackLeavesPrintBacklogAlone(t *testing.T) { |
| var wg sync.WaitGroup |
| for range 8 { |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| buf := make([]byte, 8192) |
| for range 50 { |
| runtime.Stack(buf, false) |
| runtime.DumpPrint(12345) |
| } |
| }() |
| } |
| wg.Wait() |
| |
| // Look for text only a traceback produces. |
| backlog := runtime.PrintBacklog() |
| for _, marker := range []string{"TestStackLeavesPrintBacklogAlone", "goroutine "} { |
| if bytes.Contains(backlog, []byte(marker)) { |
| t.Errorf("print backlog contains traceback text %q:\n%s", |
| marker, bytes.Trim(backlog, "\x00")) |
| } |
| } |
| } |
| |
| func BenchmarkStack(b *testing.B) { |
| buf := make([]byte, 8192) |
| for b.Loop() { |
| runtime.Stack(buf, false) |
| } |
| } |
| |
| func BenchmarkStackParallel(b *testing.B) { |
| b.RunParallel(func(pb *testing.PB) { |
| buf := make([]byte, 8192) |
| for pb.Next() { |
| runtime.Stack(buf, false) |
| } |
| }) |
| } |
| |
| func BenchmarkStackAll(b *testing.B) { |
| var wg sync.WaitGroup |
| stop := make(chan struct{}) |
| for range 32 { |
| wg.Add(1) |
| go func() { defer wg.Done(); <-stop }() |
| } |
| buf := make([]byte, 1<<16) |
| for b.Loop() { |
| runtime.Stack(buf, true) |
| } |
| close(stop) |
| wg.Wait() |
| } |
| |
| func FuzzPrintFloat64(f *testing.F) { |
| f.Add(math.SmallestNonzeroFloat64) |
| f.Add(math.MaxFloat64) |
| f.Add(-1.7976931348623157e+308) // requires 24 digits |
| |
| f.Fuzz(func(t *testing.T, v float64) { |
| s := runtime.DumpPrint(v) |
| if len(s) > runtime.Float64Bytes { |
| t.Errorf("print(%f) got %s (len %d) want len <= %d", v, s, len(s), runtime.Float64Bytes) |
| } |
| }) |
| } |
| |
| func FuzzPrintFloat32(f *testing.F) { |
| f.Add(float32(math.SmallestNonzeroFloat32)) |
| f.Add(float32(math.MaxFloat32)) |
| f.Add(float32(-1.06338233e+37)) // requires 15 digits |
| |
| f.Fuzz(func(t *testing.T, v float32) { |
| s := runtime.DumpPrint(v) |
| if len(s) > runtime.Float32Bytes { |
| t.Errorf("print(%f) got %s (len %d) want len <= %d", v, s, len(s), runtime.Float32Bytes) |
| } |
| }) |
| } |
| |
| func FuzzPrintComplex128(f *testing.F) { |
| f.Add(math.SmallestNonzeroFloat64, math.SmallestNonzeroFloat64) |
| f.Add(math.MaxFloat64, math.MaxFloat64) |
| f.Add(-1.7976931348623157e+308, -1.7976931348623157e+308) // requires 51 digits |
| |
| f.Fuzz(func(t *testing.T, r, i float64) { |
| v := complex(r, i) |
| s := runtime.DumpPrint(v) |
| if len(s) > runtime.Complex128Bytes { |
| t.Errorf("print(%f) got %s (len %d) want len <= %d", v, s, len(s), runtime.Complex128Bytes) |
| } |
| }) |
| } |
| |
| func FuzzPrintComplex64(f *testing.F) { |
| f.Add(float32(math.SmallestNonzeroFloat32), float32(math.SmallestNonzeroFloat32)) |
| f.Add(float32(math.MaxFloat32), float32(math.MaxFloat32)) |
| f.Add(float32(-1.06338233e+37), float32(-1.06338233e+37)) // requires 33 digits |
| |
| f.Fuzz(func(t *testing.T, r, i float32) { |
| v := complex(r, i) |
| s := runtime.DumpPrint(v) |
| if len(s) > runtime.Complex64Bytes { |
| t.Errorf("print(%f) got %s (len %d) want len <= %d", v, s, len(s), runtime.Complex64Bytes) |
| } |
| }) |
| } |