1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package testing_test
6
7import (
8	"bytes"
9	"cmp"
10	"runtime"
11	"slices"
12	"strings"
13	"sync/atomic"
14	"testing"
15	"text/template"
16	"time"
17)
18
19var prettyPrintTests = []struct {
20	v        float64
21	expected string
22}{
23	{0, "         0 x"},
24	{1234.1, "      1234 x"},
25	{-1234.1, "     -1234 x"},
26	{999.950001, "      1000 x"},
27	{999.949999, "       999.9 x"},
28	{99.9950001, "       100.0 x"},
29	{99.9949999, "        99.99 x"},
30	{-99.9949999, "       -99.99 x"},
31	{0.000999950001, "         0.001000 x"},
32	{0.000999949999, "         0.0009999 x"}, // smallest case
33	{0.0000999949999, "         0.0001000 x"},
34}
35
36func TestPrettyPrint(t *testing.T) {
37	for _, tt := range prettyPrintTests {
38		buf := new(strings.Builder)
39		testing.PrettyPrint(buf, tt.v, "x")
40		if tt.expected != buf.String() {
41			t.Errorf("prettyPrint(%v): expected %q, actual %q", tt.v, tt.expected, buf.String())
42		}
43	}
44}
45
46func TestResultString(t *testing.T) {
47	// Test fractional ns/op handling
48	r := testing.BenchmarkResult{
49		N: 100,
50		T: 240 * time.Nanosecond,
51	}
52	if r.NsPerOp() != 2 {
53		t.Errorf("NsPerOp: expected 2, actual %v", r.NsPerOp())
54	}
55	if want, got := "     100\t         2.400 ns/op", r.String(); want != got {
56		t.Errorf("String: expected %q, actual %q", want, got)
57	}
58
59	// Test sub-1 ns/op (issue #31005)
60	r.T = 40 * time.Nanosecond
61	if want, got := "     100\t         0.4000 ns/op", r.String(); want != got {
62		t.Errorf("String: expected %q, actual %q", want, got)
63	}
64
65	// Test 0 ns/op
66	r.T = 0
67	if want, got := "     100", r.String(); want != got {
68		t.Errorf("String: expected %q, actual %q", want, got)
69	}
70}
71
72func TestRunParallel(t *testing.T) {
73	if testing.Short() {
74		t.Skip("skipping in short mode")
75	}
76	testing.Benchmark(func(b *testing.B) {
77		procs := uint32(0)
78		iters := uint64(0)
79		b.SetParallelism(3)
80		b.RunParallel(func(pb *testing.PB) {
81			atomic.AddUint32(&procs, 1)
82			for pb.Next() {
83				atomic.AddUint64(&iters, 1)
84			}
85		})
86		if want := uint32(3 * runtime.GOMAXPROCS(0)); procs != want {
87			t.Errorf("got %v procs, want %v", procs, want)
88		}
89		if iters != uint64(b.N) {
90			t.Errorf("got %v iters, want %v", iters, b.N)
91		}
92	})
93}
94
95func TestRunParallelFail(t *testing.T) {
96	testing.Benchmark(func(b *testing.B) {
97		b.RunParallel(func(pb *testing.PB) {
98			// The function must be able to log/abort
99			// w/o crashing/deadlocking the whole benchmark.
100			b.Log("log")
101			b.Error("error")
102		})
103	})
104}
105
106func TestRunParallelFatal(t *testing.T) {
107	testing.Benchmark(func(b *testing.B) {
108		b.RunParallel(func(pb *testing.PB) {
109			for pb.Next() {
110				if b.N > 1 {
111					b.Fatal("error")
112				}
113			}
114		})
115	})
116}
117
118func TestRunParallelSkipNow(t *testing.T) {
119	testing.Benchmark(func(b *testing.B) {
120		b.RunParallel(func(pb *testing.PB) {
121			for pb.Next() {
122				if b.N > 1 {
123					b.SkipNow()
124				}
125			}
126		})
127	})
128}
129
130func ExampleB_RunParallel() {
131	// Parallel benchmark for text/template.Template.Execute on a single object.
132	testing.Benchmark(func(b *testing.B) {
133		templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
134		// RunParallel will create GOMAXPROCS goroutines
135		// and distribute work among them.
136		b.RunParallel(func(pb *testing.PB) {
137			// Each goroutine has its own bytes.Buffer.
138			var buf bytes.Buffer
139			for pb.Next() {
140				// The loop body is executed b.N times total across all goroutines.
141				buf.Reset()
142				templ.Execute(&buf, "World")
143			}
144		})
145	})
146}
147
148func TestReportMetric(t *testing.T) {
149	res := testing.Benchmark(func(b *testing.B) {
150		b.ReportMetric(12345, "ns/op")
151		b.ReportMetric(0.2, "frobs/op")
152	})
153	// Test built-in overriding.
154	if res.NsPerOp() != 12345 {
155		t.Errorf("NsPerOp: expected %v, actual %v", 12345, res.NsPerOp())
156	}
157	// Test stringing.
158	res.N = 1 // Make the output stable
159	want := "       1\t     12345 ns/op\t         0.2000 frobs/op"
160	if want != res.String() {
161		t.Errorf("expected %q, actual %q", want, res.String())
162	}
163}
164
165func ExampleB_ReportMetric() {
166	// This reports a custom benchmark metric relevant to a
167	// specific algorithm (in this case, sorting).
168	testing.Benchmark(func(b *testing.B) {
169		var compares int64
170		for i := 0; i < b.N; i++ {
171			s := []int{5, 4, 3, 2, 1}
172			slices.SortFunc(s, func(a, b int) int {
173				compares++
174				return cmp.Compare(a, b)
175			})
176		}
177		// This metric is per-operation, so divide by b.N and
178		// report it as a "/op" unit.
179		b.ReportMetric(float64(compares)/float64(b.N), "compares/op")
180		// This metric is per-time, so divide by b.Elapsed and
181		// report it as a "/ns" unit.
182		b.ReportMetric(float64(compares)/float64(b.Elapsed().Nanoseconds()), "compares/ns")
183	})
184}
185
186func ExampleB_ReportMetric_parallel() {
187	// This reports a custom benchmark metric relevant to a
188	// specific algorithm (in this case, sorting) in parallel.
189	testing.Benchmark(func(b *testing.B) {
190		var compares atomic.Int64
191		b.RunParallel(func(pb *testing.PB) {
192			for pb.Next() {
193				s := []int{5, 4, 3, 2, 1}
194				slices.SortFunc(s, func(a, b int) int {
195					// Because RunParallel runs the function many
196					// times in parallel, we must increment the
197					// counter atomically to avoid racing writes.
198					compares.Add(1)
199					return cmp.Compare(a, b)
200				})
201			}
202		})
203
204		// NOTE: Report each metric once, after all of the parallel
205		// calls have completed.
206
207		// This metric is per-operation, so divide by b.N and
208		// report it as a "/op" unit.
209		b.ReportMetric(float64(compares.Load())/float64(b.N), "compares/op")
210		// This metric is per-time, so divide by b.Elapsed and
211		// report it as a "/ns" unit.
212		b.ReportMetric(float64(compares.Load())/float64(b.Elapsed().Nanoseconds()), "compares/ns")
213	})
214}
215