|
8 | 8 | "fmt" |
9 | 9 | "reflect" |
10 | 10 | "sync" |
| 11 | + "sync/atomic" |
11 | 12 | "testing" |
12 | 13 |
|
13 | 14 | "github.com/google/go-cmp/cmp" |
@@ -120,38 +121,51 @@ func TestHasExtensionNoAlloc(t *testing.T) { |
120 | 121 | Corecursive: &testpb.TestAllExtensions{}, |
121 | 122 | }) |
122 | 123 |
|
| 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 |
123 | 147 | b, err := proto.Marshal(mEager) |
124 | 148 | if err != nil { |
125 | 149 | t.Fatal(err) |
126 | 150 | } |
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 | + } |
130 | 157 | } |
131 | | - |
132 | 158 | for _, tc := range []struct { |
133 | 159 | name string |
134 | | - m proto.Message |
| 160 | + m []*testpb.TestAllExtensions |
135 | 161 | }{ |
136 | | - {name: "Nil", m: nil}, |
137 | | - {name: "Eager", m: mEager}, |
138 | 162 | {name: "Lazy", m: mLazy}, |
139 | 163 | } { |
140 | 164 | 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) |
155 | 169 | }) |
156 | 170 | if avg != 0 { |
157 | 171 | t.Errorf("proto.HasExtension should not allocate, but allocated %.2fx per run", avg) |
|
0 commit comments