Skip to content

Commit 4a45645

Browse files
committed
proto: un-flake TestHasExtensionNoAlloc
Before this change, this test had a flakiness rate of 3 out of 1000 runs (on Google’s internal testing infrastructure). After this change, the test is not flaky, even when running with 50_000 runs. testing.AllocsPerRun is meant to be robust against individual allocations from background goroutines, but not in the way we called the function. Change-Id: If99573734fb2de5d7128b5e61c2019867ccbb9b2 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/716520 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Christian Höppner <hoeppi@google.com>
1 parent d65e1d4 commit 4a45645

File tree

1 file changed

+35
-21
lines changed

1 file changed

+35
-21
lines changed

proto/extension_test.go

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"reflect"
1010
"sync"
11+
"sync/atomic"
1112
"testing"
1213

1314
"github.com/google/go-cmp/cmp"
@@ -120,38 +121,51 @@ func TestHasExtensionNoAlloc(t *testing.T) {
120121
Corecursive: &testpb.TestAllExtensions{},
121122
})
122123

124+
// Number of runs that testing.AllocsPerRun will do.
125+
const runs = 100
126+
127+
for _, tc := range []struct {
128+
name string
129+
m proto.Message
130+
}{
131+
{name: "Nil", m: nil},
132+
{name: "Eager", m: mEager},
133+
} {
134+
t.Run(tc.name, func(t *testing.T) {
135+
avg := testing.AllocsPerRun(runs, func() {
136+
proto.HasExtension(tc.m, testpb.E_OptionalNestedMessage)
137+
})
138+
if avg != 0 {
139+
t.Errorf("proto.HasExtension should not allocate, but allocated %.2fx per run", avg)
140+
}
141+
})
142+
}
143+
144+
// Lazy initialization only happens once, so we need to allocate enough
145+
// copies for all testing.AllocsPerRun callbacks.
146+
const copies = runs + 1 // + 1 because testing.AllocsPerRun does a warmup call
123147
b, err := proto.Marshal(mEager)
124148
if err != nil {
125149
t.Fatal(err)
126150
}
127-
mLazy := &testpb.TestAllExtensions{}
128-
if err := proto.Unmarshal(b, mLazy); err != nil {
129-
t.Fatal(err)
151+
mLazy := make([]*testpb.TestAllExtensions, copies)
152+
for i := range copies {
153+
mLazy[i] = new(testpb.TestAllExtensions)
154+
if err := proto.Unmarshal(b, mLazy[i]); err != nil {
155+
t.Fatal(err)
156+
}
130157
}
131-
132158
for _, tc := range []struct {
133159
name string
134-
m proto.Message
160+
m []*testpb.TestAllExtensions
135161
}{
136-
{name: "Nil", m: nil},
137-
{name: "Eager", m: mEager},
138162
{name: "Lazy", m: mLazy},
139163
} {
140164
t.Run(tc.name, func(t *testing.T) {
141-
// Testing for allocations can be done with `testing.AllocsPerRun`, but it
142-
// has some snags that complicate its use for us:
143-
// - It performs a warmup invocation before starting the measurement. We
144-
// want to skip this because lazy initialization only happens once.
145-
// - Despite returning a float64, the returned value is an integer, so <1
146-
// allocations per operation are returned as 0. Therefore, pass runs =
147-
// 1.
148-
warmup := true
149-
avg := testing.AllocsPerRun(1, func() {
150-
if warmup {
151-
warmup = false
152-
return
153-
}
154-
proto.HasExtension(tc.m, testpb.E_OptionalNestedMessage)
165+
var idx atomic.Int64
166+
avg := testing.AllocsPerRun(runs, func() {
167+
i := int(idx.Add(1))
168+
proto.HasExtension(tc.m[i-1], testpb.E_OptionalNestedMessage)
155169
})
156170
if avg != 0 {
157171
t.Errorf("proto.HasExtension should not allocate, but allocated %.2fx per run", avg)

0 commit comments

Comments
 (0)