testing: add AllocsPerRun

This CL also replaces similar loops in other stdlib
package tests with calls to AllocsPerRun.

Fixes #4461.

R=minux.ma, rsc
CC=golang-dev
https://golang.org/cl/7002055
diff --git a/src/pkg/encoding/gob/timing_test.go b/src/pkg/encoding/gob/timing_test.go
index 9a0e51d..13eb119 100644
--- a/src/pkg/encoding/gob/timing_test.go
+++ b/src/pkg/encoding/gob/timing_test.go
@@ -9,7 +9,6 @@
 	"fmt"
 	"io"
 	"os"
-	"runtime"
 	"testing"
 )
 
@@ -50,49 +49,43 @@
 }
 
 func TestCountEncodeMallocs(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+	const N = 1000
+
 	var buf bytes.Buffer
 	enc := NewEncoder(&buf)
 	bench := &Bench{7, 3.2, "now is the time", []byte("for all good men")}
-	memstats := new(runtime.MemStats)
-	runtime.ReadMemStats(memstats)
-	mallocs := 0 - memstats.Mallocs
-	const count = 1000
-	for i := 0; i < count; i++ {
+
+	allocs := testing.AllocsPerRun(N, func() {
 		err := enc.Encode(bench)
 		if err != nil {
 			t.Fatal("encode:", err)
 		}
-	}
-	runtime.ReadMemStats(memstats)
-	mallocs += memstats.Mallocs
-	fmt.Printf("mallocs per encode of type Bench: %d\n", mallocs/count)
+	})
+	fmt.Printf("mallocs per encode of type Bench: %v\n", allocs)
 }
 
 func TestCountDecodeMallocs(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+	const N = 1000
+
 	var buf bytes.Buffer
 	enc := NewEncoder(&buf)
 	bench := &Bench{7, 3.2, "now is the time", []byte("for all good men")}
-	const count = 1000
-	for i := 0; i < count; i++ {
+
+	// Fill the buffer with enough to decode
+	testing.AllocsPerRun(N, func() {
 		err := enc.Encode(bench)
 		if err != nil {
 			t.Fatal("encode:", err)
 		}
-	}
+	})
+
 	dec := NewDecoder(&buf)
-	memstats := new(runtime.MemStats)
-	runtime.ReadMemStats(memstats)
-	mallocs := 0 - memstats.Mallocs
-	for i := 0; i < count; i++ {
+	allocs := testing.AllocsPerRun(N, func() {
 		*bench = Bench{}
 		err := dec.Decode(&bench)
 		if err != nil {
 			t.Fatal("decode:", err)
 		}
-	}
-	runtime.ReadMemStats(memstats)
-	mallocs += memstats.Mallocs
-	fmt.Printf("mallocs per decode of type Bench: %d\n", mallocs/count)
+	})
+	fmt.Printf("mallocs per decode of type Bench: %v\n", allocs)
 }
diff --git a/src/pkg/fmt/fmt_test.go b/src/pkg/fmt/fmt_test.go
index 4158c6c..a49b339 100644
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -9,7 +9,6 @@
 	. "fmt"
 	"io"
 	"math"
-	"runtime" // for the malloc count test only
 	"strings"
 	"testing"
 	"time"
@@ -598,19 +597,10 @@
 var _ bytes.Buffer
 
 func TestCountMallocs(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
 	for _, mt := range mallocTest {
-		const N = 100
-		memstats := new(runtime.MemStats)
-		runtime.ReadMemStats(memstats)
-		mallocs := 0 - memstats.Mallocs
-		for i := 0; i < N; i++ {
-			mt.fn()
-		}
-		runtime.ReadMemStats(memstats)
-		mallocs += memstats.Mallocs
-		if mallocs/N > uint64(mt.count) {
-			t.Errorf("%s: expected %d mallocs, got %d", mt.desc, mt.count, mallocs/N)
+		mallocs := testing.AllocsPerRun(100, mt.fn)
+		if got, max := mallocs, float64(mt.count); got > max {
+			t.Errorf("%s: got %v allocs, want <=%v", mt.desc, got, max)
 		}
 	}
 }
diff --git a/src/pkg/net/http/header_test.go b/src/pkg/net/http/header_test.go
index 01bb4dc..2313b55 100644
--- a/src/pkg/net/http/header_test.go
+++ b/src/pkg/net/http/header_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"bytes"
-	"runtime"
 	"testing"
 	"time"
 )
@@ -175,38 +174,31 @@
 	}
 }
 
+var testHeader = Header{
+	"Content-Length": {"123"},
+	"Content-Type":   {"text/plain"},
+	"Date":           {"some date at some time Z"},
+	"Server":         {"Go http package"},
+}
+
+var buf bytes.Buffer
+
 func BenchmarkHeaderWriteSubset(b *testing.B) {
-	doHeaderWriteSubset(b.N, b)
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		buf.Reset()
+		testHeader.WriteSubset(&buf, nil)
+	}
 }
 
 func TestHeaderWriteSubsetMallocs(t *testing.T) {
-	doHeaderWriteSubset(100, t)
-}
-
-type errorfer interface {
-	Errorf(string, ...interface{})
-}
-
-func doHeaderWriteSubset(n int, t errorfer) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
-	h := Header(map[string][]string{
-		"Content-Length": {"123"},
-		"Content-Type":   {"text/plain"},
-		"Date":           {"some date at some time Z"},
-		"Server":         {"Go http package"},
-	})
-	var buf bytes.Buffer
-	var m0 runtime.MemStats
-	runtime.ReadMemStats(&m0)
-	for i := 0; i < n; i++ {
+	n := testing.AllocsPerRun(100, func() {
 		buf.Reset()
-		h.WriteSubset(&buf, nil)
-	}
-	var m1 runtime.MemStats
-	runtime.ReadMemStats(&m1)
-	if mallocs := m1.Mallocs - m0.Mallocs; n >= 100 && mallocs >= uint64(n) {
+		testHeader.WriteSubset(&buf, nil)
+	})
+	if n > 1 {
 		// TODO(bradfitz,rsc): once we can sort without allocating,
 		// make this an error.  See http://golang.org/issue/3761
-		// t.Errorf("did %d mallocs (>= %d iterations); should have avoided mallocs", mallocs, n)
+		// t.Errorf("got %v allocs, want <= %v", n, 1)
 	}
 }
diff --git a/src/pkg/net/rpc/server_test.go b/src/pkg/net/rpc/server_test.go
index 2c734a4..db7778d 100644
--- a/src/pkg/net/rpc/server_test.go
+++ b/src/pkg/net/rpc/server_test.go
@@ -445,8 +445,7 @@
 	return DialHTTP("tcp", httpServerAddr)
 }
 
-func countMallocs(dial func() (*Client, error), t *testing.T) uint64 {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+func countMallocs(dial func() (*Client, error), t *testing.T) float64 {
 	once.Do(startServer)
 	client, err := dial()
 	if err != nil {
@@ -454,11 +453,7 @@
 	}
 	args := &Args{7, 8}
 	reply := new(Reply)
-	memstats := new(runtime.MemStats)
-	runtime.ReadMemStats(memstats)
-	mallocs := 0 - memstats.Mallocs
-	const count = 100
-	for i := 0; i < count; i++ {
+	return testing.AllocsPerRun(100, func() {
 		err := client.Call("Arith.Add", args, reply)
 		if err != nil {
 			t.Errorf("Add: expected no error but got string %q", err.Error())
@@ -466,18 +461,15 @@
 		if reply.C != args.A+args.B {
 			t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
 		}
-	}
-	runtime.ReadMemStats(memstats)
-	mallocs += memstats.Mallocs
-	return mallocs / count
+	})
 }
 
 func TestCountMallocs(t *testing.T) {
-	fmt.Printf("mallocs per rpc round trip: %d\n", countMallocs(dialDirect, t))
+	fmt.Printf("mallocs per rpc round trip: %v\n", countMallocs(dialDirect, t))
 }
 
 func TestCountMallocsOverHTTP(t *testing.T) {
-	fmt.Printf("mallocs per HTTP rpc round trip: %d\n", countMallocs(dialHTTP, t))
+	fmt.Printf("mallocs per HTTP rpc round trip: %v\n", countMallocs(dialHTTP, t))
 }
 
 type writeCrasher struct {
diff --git a/src/pkg/path/filepath/path_test.go b/src/pkg/path/filepath/path_test.go
index bd251a4..84609c4 100644
--- a/src/pkg/path/filepath/path_test.go
+++ b/src/pkg/path/filepath/path_test.go
@@ -91,7 +91,6 @@
 }
 
 func TestClean(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
 	tests := cleantests
 	if runtime.GOOS == "windows" {
 		for i := range tests {
@@ -108,20 +107,12 @@
 		}
 	}
 
-	var ms runtime.MemStats
-	runtime.ReadMemStats(&ms)
-	allocs := -ms.Mallocs
-	const rounds = 100
-	for i := 0; i < rounds; i++ {
-		for _, test := range tests {
-			filepath.Clean(test.result)
+	for _, test := range tests {
+		allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
+		if allocs > 0 {
+			t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
 		}
 	}
-	runtime.ReadMemStats(&ms)
-	allocs += ms.Mallocs
-	if allocs >= rounds {
-		t.Errorf("Clean cleaned paths: %d allocations per test round, want zero", allocs/rounds)
-	}
 }
 
 const sep = filepath.Separator
diff --git a/src/pkg/path/path_test.go b/src/pkg/path/path_test.go
index 52cbb49..220ec1a 100644
--- a/src/pkg/path/path_test.go
+++ b/src/pkg/path/path_test.go
@@ -5,7 +5,6 @@
 package path
 
 import (
-	"runtime"
 	"testing"
 )
 
@@ -64,7 +63,6 @@
 }
 
 func TestClean(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
 	for _, test := range cleantests {
 		if s := Clean(test.path); s != test.result {
 			t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
@@ -74,20 +72,12 @@
 		}
 	}
 
-	var ms runtime.MemStats
-	runtime.ReadMemStats(&ms)
-	allocs := -ms.Mallocs
-	const rounds = 100
-	for i := 0; i < rounds; i++ {
-		for _, test := range cleantests {
-			Clean(test.result)
+	for _, test := range cleantests {
+		allocs := testing.AllocsPerRun(100, func() { Clean(test.result) })
+		if allocs > 0 {
+			t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
 		}
 	}
-	runtime.ReadMemStats(&ms)
-	allocs += ms.Mallocs
-	if allocs >= rounds {
-		t.Errorf("Clean cleaned paths: %d allocations per test round, want zero", allocs/rounds)
-	}
 }
 
 type SplitTest struct {
diff --git a/src/pkg/reflect/all_test.go b/src/pkg/reflect/all_test.go
index 8dd24de..6f006db 100644
--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -13,7 +13,6 @@
 	"math/rand"
 	"os"
 	. "reflect"
-	"runtime"
 	"sync"
 	"testing"
 	"time"
@@ -2012,20 +2011,13 @@
 }
 
 func noAlloc(t *testing.T, n int, f func(int)) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
-	// once to prime everything
-	f(-1)
-	memstats := new(runtime.MemStats)
-	runtime.ReadMemStats(memstats)
-	oldmallocs := memstats.Mallocs
-
-	for j := 0; j < n; j++ {
-		f(j)
-	}
-	runtime.ReadMemStats(memstats)
-	mallocs := memstats.Mallocs - oldmallocs
-	if mallocs > 0 {
-		t.Fatalf("%d mallocs after %d iterations", mallocs, n)
+	i := -1
+	allocs := testing.AllocsPerRun(n, func() {
+		f(i)
+		i++
+	})
+	if allocs > 0 {
+		t.Errorf("%d iterations: got %v mallocs, want 0", n, allocs)
 	}
 }
 
diff --git a/src/pkg/strconv/strconv_test.go b/src/pkg/strconv/strconv_test.go
index 6a99522..c3c5389 100644
--- a/src/pkg/strconv/strconv_test.go
+++ b/src/pkg/strconv/strconv_test.go
@@ -5,7 +5,6 @@
 package strconv_test
 
 import (
-	"runtime"
 	. "strconv"
 	"strings"
 	"testing"
@@ -44,19 +43,10 @@
 )
 
 func TestCountMallocs(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
 	for _, mt := range mallocTest {
-		const N = 100
-		memstats := new(runtime.MemStats)
-		runtime.ReadMemStats(memstats)
-		mallocs := 0 - memstats.Mallocs
-		for i := 0; i < N; i++ {
-			mt.fn()
-		}
-		runtime.ReadMemStats(memstats)
-		mallocs += memstats.Mallocs
-		if mallocs/N > uint64(mt.count) {
-			t.Errorf("%s: expected %d mallocs, got %d", mt.desc, mt.count, mallocs/N)
+		allocs := testing.AllocsPerRun(100, mt.fn)
+		if max := float64(mt.count); allocs > max {
+			t.Errorf("%s: %v allocs, want <=%v", mt.desc, allocs, max)
 		}
 	}
 }
diff --git a/src/pkg/testing/allocs.go b/src/pkg/testing/allocs.go
new file mode 100644
index 0000000..d142a33
--- /dev/null
+++ b/src/pkg/testing/allocs.go
@@ -0,0 +1,41 @@
+// Copyright 2013 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 testing
+
+import (
+	"runtime"
+)
+
+// AllocsPerRun returns the average number of allocations during calls to f.
+//
+// To compute the number of allocations, the function will first be run once as
+// a warm-up.  The average number of allocations over the specified number of
+// runs will then be measured and returned.
+//
+// AllocsPerRun sets GOMAXPROCS to 1 during its measurement and will restore
+// it before returning.
+func AllocsPerRun(runs int, f func()) (avg float64) {
+	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+
+	// Warm up the function
+	f()
+
+	// Measure the starting statistics
+	var memstats runtime.MemStats
+	runtime.ReadMemStats(&memstats)
+	mallocs := 0 - memstats.Mallocs
+
+	// Run the function the specified number of times
+	for i := 0; i < runs; i++ {
+		f()
+	}
+
+	// Read the final statistics
+	runtime.ReadMemStats(&memstats)
+	mallocs += memstats.Mallocs
+
+	// Average the mallocs over the runs (not counting the warm-up)
+	return float64(mallocs) / float64(runs)
+}
diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go
index a8953ae..04b0ade 100644
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -11,7 +11,6 @@
 	"fmt"
 	"math/big"
 	"math/rand"
-	"runtime"
 	"strconv"
 	"strings"
 	"testing"
@@ -1258,19 +1257,10 @@
 }
 
 func TestCountMallocs(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
 	for _, mt := range mallocTest {
-		const N = 100
-		memstats := new(runtime.MemStats)
-		runtime.ReadMemStats(memstats)
-		mallocs := 0 - memstats.Mallocs
-		for i := 0; i < N; i++ {
-			mt.fn()
-		}
-		runtime.ReadMemStats(memstats)
-		mallocs += memstats.Mallocs
-		if mallocs/N > uint64(mt.count) {
-			t.Errorf("%s: expected %d mallocs, got %d", mt.desc, mt.count, mallocs/N)
+		allocs := int(testing.AllocsPerRun(100, mt.fn))
+		if allocs > mt.count {
+			t.Errorf("%s: %d allocs, want %d", mt.desc, allocs, mt.count)
 		}
 	}
 }