Source file src/testing/benchmark_test.go

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

View as plain text