1
2
3
4
5 package loopvar_test
6
7 import (
8 "internal/testenv"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "regexp"
13 "runtime"
14 "strings"
15 "testing"
16 )
17
18 type testcase struct {
19 lvFlag string
20 buildExpect string
21 expectRC int
22 files []string
23 }
24
25 var for_files = []string{
26 "for_esc_address.go",
27 "for_esc_closure.go",
28 "for_esc_minimal_closure.go",
29 "for_esc_method.go",
30 "for_complicated_esc_address.go",
31 }
32
33 var range_files = []string{
34 "range_esc_address.go",
35 "range_esc_closure.go",
36 "range_esc_minimal_closure.go",
37 "range_esc_method.go",
38 }
39
40 var cases = []testcase{
41 {"-1", "", 11, for_files[:1]},
42 {"0", "", 0, for_files[:1]},
43 {"1", "", 0, for_files[:1]},
44 {"2", "loop variable i now per-iteration,", 0, for_files},
45
46 {"-1", "", 11, range_files[:1]},
47 {"0", "", 0, range_files[:1]},
48 {"1", "", 0, range_files[:1]},
49 {"2", "loop variable i now per-iteration,", 0, range_files},
50
51 {"1", "", 0, []string{"for_nested.go"}},
52 }
53
54
55 func TestLoopVarGo1_21(t *testing.T) {
56 switch runtime.GOOS {
57 case "linux", "darwin":
58 default:
59 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
60 }
61 switch runtime.GOARCH {
62 case "amd64", "arm64":
63 default:
64 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
65 }
66
67 testenv.MustHaveGoBuild(t)
68 gocmd := testenv.GoToolPath(t)
69 tmpdir := t.TempDir()
70 output := filepath.Join(tmpdir, "foo.exe")
71
72 for i, tc := range cases {
73 for _, f := range tc.files {
74 source := f
75 cmd := testenv.Command(t, gocmd, "build", "-o", output, "-gcflags=-lang=go1.21 -d=loopvar="+tc.lvFlag, source)
76 cmd.Env = append(cmd.Env, "GOEXPERIMENT=loopvar", "HOME="+tmpdir)
77 cmd.Dir = "testdata"
78 t.Logf("File %s loopvar=%s expect '%s' exit code %d", f, tc.lvFlag, tc.buildExpect, tc.expectRC)
79 b, e := cmd.CombinedOutput()
80 if e != nil {
81 t.Error(e)
82 }
83 if tc.buildExpect != "" {
84 s := string(b)
85 if !strings.Contains(s, tc.buildExpect) {
86 t.Errorf("File %s test %d expected to match '%s' with \n-----\n%s\n-----", f, i, tc.buildExpect, s)
87 }
88 }
89
90 cmd = testenv.Command(t, output)
91 b, e = cmd.CombinedOutput()
92 if tc.expectRC != 0 {
93 if e == nil {
94 t.Errorf("Missing expected error, file %s, case %d", f, i)
95 } else if ee, ok := (e).(*exec.ExitError); !ok || ee.ExitCode() != tc.expectRC {
96 t.Error(e)
97 } else {
98
99 }
100 } else if e != nil {
101 t.Error(e)
102 }
103 }
104 }
105 }
106
107 func TestLoopVarInlinesGo1_21(t *testing.T) {
108 switch runtime.GOOS {
109 case "linux", "darwin":
110 default:
111 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
112 }
113 switch runtime.GOARCH {
114 case "amd64", "arm64":
115 default:
116 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
117 }
118
119 testenv.MustHaveGoBuild(t)
120 gocmd := testenv.GoToolPath(t)
121 tmpdir := t.TempDir()
122
123 root := "cmd/compile/internal/loopvar/testdata/inlines"
124
125 f := func(pkg string) string {
126
127
128
129 cmd := testenv.Command(t, gocmd, "run", "-gcflags="+root+"/...=-lang=go1.21", "-gcflags="+pkg+"=-d=loopvar=1", root)
130 cmd.Env = append(cmd.Env, "GOEXPERIMENT=noloopvar", "HOME="+tmpdir)
131 cmd.Dir = filepath.Join("testdata", "inlines")
132
133 b, e := cmd.CombinedOutput()
134 if e != nil {
135 t.Error(e)
136 }
137 return string(b)
138 }
139
140 a := f(root + "/a")
141 b := f(root + "/b")
142 c := f(root + "/c")
143 m := f(root)
144
145 t.Log(a)
146 t.Log(b)
147 t.Log(c)
148 t.Log(m)
149
150 if !strings.Contains(a, "f, af, bf, abf, cf sums = 100, 45, 100, 100, 100") {
151 t.Errorf("Did not see expected value of a")
152 }
153 if !strings.Contains(b, "f, af, bf, abf, cf sums = 100, 100, 45, 45, 100") {
154 t.Errorf("Did not see expected value of b")
155 }
156 if !strings.Contains(c, "f, af, bf, abf, cf sums = 100, 100, 100, 100, 45") {
157 t.Errorf("Did not see expected value of c")
158 }
159 if !strings.Contains(m, "f, af, bf, abf, cf sums = 45, 100, 100, 100, 100") {
160 t.Errorf("Did not see expected value of m")
161 }
162 }
163
164 func countMatches(s, re string) int {
165 slice := regexp.MustCompile(re).FindAllString(s, -1)
166 return len(slice)
167 }
168
169 func TestLoopVarHashes(t *testing.T) {
170
171 switch runtime.GOOS {
172 case "linux", "darwin":
173 default:
174 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
175 }
176 switch runtime.GOARCH {
177 case "amd64", "arm64":
178 default:
179 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
180 }
181
182 testenv.MustHaveGoBuild(t)
183 gocmd := testenv.GoToolPath(t)
184 tmpdir := t.TempDir()
185
186 root := "cmd/compile/internal/loopvar/testdata/inlines"
187
188 f := func(hash string) string {
189
190
191
192
193 cmd := testenv.Command(t, gocmd, "run", "-trimpath", root)
194 cmd.Env = append(cmd.Env, "GOCOMPILEDEBUG=loopvarhash="+hash, "HOME="+tmpdir)
195 cmd.Dir = filepath.Join("testdata", "inlines")
196
197 b, _ := cmd.CombinedOutput()
198
199 return string(b)
200 }
201
202 for _, arg := range []string{"v001100110110110010100100", "vx336ca4"} {
203 m := f(arg)
204 t.Log(m)
205
206 mCount := countMatches(m, "loopvarhash triggered cmd/compile/internal/loopvar/testdata/inlines/main.go:27:6: .* 001100110110110010100100")
207 otherCount := strings.Count(m, "loopvarhash")
208 if mCount < 1 {
209 t.Errorf("%s: did not see triggered main.go:27:6", arg)
210 }
211 if mCount != otherCount {
212 t.Errorf("%s: too many matches", arg)
213 }
214 mCount = countMatches(m, "cmd/compile/internal/loopvar/testdata/inlines/main.go:27:6: .* \\[bisect-match 0x7802e115b9336ca4\\]")
215 otherCount = strings.Count(m, "[bisect-match ")
216 if mCount < 1 {
217 t.Errorf("%s: did not see bisect-match for main.go:27:6", arg)
218 }
219 if mCount != otherCount {
220 t.Errorf("%s: too many matches", arg)
221 }
222
223
224 if !strings.Contains(m, ", 100, 100, 100, 100") {
225 t.Errorf("%s: did not see expected value of m run", arg)
226 }
227 }
228 }
229
230
231 func TestLoopVarVersionEnableFlag(t *testing.T) {
232 switch runtime.GOOS {
233 case "linux", "darwin":
234 default:
235 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
236 }
237 switch runtime.GOARCH {
238 case "amd64", "arm64":
239 default:
240 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
241 }
242
243 testenv.MustHaveGoBuild(t)
244 gocmd := testenv.GoToolPath(t)
245
246
247 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.22 -d=loopvar=3", "opt.go")
248 cmd.Dir = filepath.Join("testdata")
249
250 b, err := cmd.CombinedOutput()
251 m := string(b)
252
253 t.Log(m)
254
255 yCount := strings.Count(m, "opt.go:16:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt.go:29)")
256 nCount := strings.Count(m, "shared")
257
258 if yCount != 1 {
259 t.Errorf("yCount=%d != 1", yCount)
260 }
261 if nCount > 0 {
262 t.Errorf("nCount=%d > 0", nCount)
263 }
264 if err != nil {
265 t.Errorf("err=%v != nil", err)
266 }
267 }
268
269
270 func TestLoopVarVersionEnableGoBuild(t *testing.T) {
271 switch runtime.GOOS {
272 case "linux", "darwin":
273 default:
274 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
275 }
276 switch runtime.GOARCH {
277 case "amd64", "arm64":
278 default:
279 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
280 }
281
282 testenv.MustHaveGoBuild(t)
283 gocmd := testenv.GoToolPath(t)
284
285
286 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.21 -d=loopvar=3", "opt-122.go")
287 cmd.Dir = filepath.Join("testdata")
288
289 b, err := cmd.CombinedOutput()
290 m := string(b)
291
292 t.Log(m)
293
294 yCount := strings.Count(m, "opt-122.go:18:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt-122.go:31)")
295 nCount := strings.Count(m, "shared")
296
297 if yCount != 1 {
298 t.Errorf("yCount=%d != 1", yCount)
299 }
300 if nCount > 0 {
301 t.Errorf("nCount=%d > 0", nCount)
302 }
303 if err != nil {
304 t.Errorf("err=%v != nil", err)
305 }
306 }
307
308
309 func TestLoopVarVersionDisableFlag(t *testing.T) {
310 switch runtime.GOOS {
311 case "linux", "darwin":
312 default:
313 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
314 }
315 switch runtime.GOARCH {
316 case "amd64", "arm64":
317 default:
318 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
319 }
320
321 testenv.MustHaveGoBuild(t)
322 gocmd := testenv.GoToolPath(t)
323
324
325 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.21 -d=loopvar=3", "opt.go")
326 cmd.Dir = filepath.Join("testdata")
327
328 b, err := cmd.CombinedOutput()
329 m := string(b)
330
331 t.Log(m)
332
333 yCount := strings.Count(m, "opt.go:16:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt.go:29)")
334 nCount := strings.Count(m, "shared")
335
336 if yCount != 0 {
337 t.Errorf("yCount=%d != 0", yCount)
338 }
339 if nCount > 0 {
340 t.Errorf("nCount=%d > 0", nCount)
341 }
342 if err == nil {
343 t.Errorf("err=%v == nil", err)
344 }
345 }
346
347
348 func TestLoopVarVersionDisableGoBuild(t *testing.T) {
349 switch runtime.GOOS {
350 case "linux", "darwin":
351 default:
352 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
353 }
354 switch runtime.GOARCH {
355 case "amd64", "arm64":
356 default:
357 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
358 }
359
360 testenv.MustHaveGoBuild(t)
361 gocmd := testenv.GoToolPath(t)
362
363
364 cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.22 -d=loopvar=3", "opt-121.go")
365 cmd.Dir = filepath.Join("testdata")
366
367 b, err := cmd.CombinedOutput()
368 m := string(b)
369
370 t.Log(m)
371
372 yCount := strings.Count(m, "opt-121.go:18:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt-121.go:31)")
373 nCount := strings.Count(m, "shared")
374
375 if yCount != 0 {
376 t.Errorf("yCount=%d != 0", yCount)
377 }
378 if nCount > 0 {
379 t.Errorf("nCount=%d > 0", nCount)
380 }
381 if err == nil {
382 t.Errorf("err=%v == nil", err)
383 }
384 }
385
386
387
388
389
390 func TestLoopVarLineDirective(t *testing.T) {
391 switch runtime.GOOS {
392 case "linux", "darwin":
393 default:
394 t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS)
395 }
396 switch runtime.GOARCH {
397 case "amd64", "arm64":
398 default:
399 t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH)
400 }
401
402 testenv.MustHaveGoBuild(t)
403 gocmd := testenv.GoToolPath(t)
404 tmpdir := t.TempDir()
405 output := filepath.Join(tmpdir, "foo.exe")
406
407
408
409 gomodPath := filepath.Join(tmpdir, "go.mod")
410 if err := os.WriteFile(gomodPath, []byte("module test\n\ngo 1.21\n"), 0644); err != nil {
411 t.Fatal(err)
412 }
413
414
415 testFile := "range_esc_closure_linedir.go"
416 srcPath := filepath.Join("testdata", testFile)
417 dstPath := filepath.Join(tmpdir, testFile)
418 src, err := os.ReadFile(srcPath)
419 if err != nil {
420 t.Fatal(err)
421 }
422 if err := os.WriteFile(dstPath, src, 0644); err != nil {
423 t.Fatal(err)
424 }
425
426
427 cmd := testenv.Command(t, gocmd, "build", "-o", output, ".")
428 cmd.Dir = tmpdir
429 b, err := cmd.CombinedOutput()
430 if err != nil {
431 t.Logf("build output: %s", b)
432 t.Fatal(err)
433 }
434 t.Logf("build output: %s", b)
435
436 cmd = testenv.Command(t, output)
437 b, err = cmd.CombinedOutput()
438 t.Logf("run output: %s", b)
439
440 if err != nil {
441 t.Errorf("expected success (exit code 0), got: %v", err)
442 }
443 }
444
View as plain text