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