1
15
16 package golang
17
18 import (
19 "os"
20 "path/filepath"
21 "testing"
22
23 "github.com/google/go-cmp/cmp"
24 )
25
26 func TestOtherFileInfo(t *testing.T) {
27 dir := "."
28 for _, tc := range []struct {
29 desc, name, source string
30 wantTags *buildTags
31 }{
32 {
33 "empty file",
34 "foo.c",
35 "",
36 nil,
37 },
38 {
39 "tags file",
40 "foo.c",
41 `// +build foo bar
42 // +build baz,!ignore
43
44 `,
45 &buildTags{
46 expr: mustParseBuildTag(t, "(foo || bar) && (baz && !ignore)"),
47 rawTags: []string{"foo", "bar", "baz", "ignore"},
48 },
49 },
50 } {
51 t.Run(tc.desc, func(t *testing.T) {
52 if err := os.WriteFile(tc.name, []byte(tc.source), 0o600); err != nil {
53 t.Fatal(err)
54 }
55 defer os.Remove(tc.name)
56
57 got := otherFileInfo(filepath.Join(dir, tc.name))
58
59
60
61 if diff := cmp.Diff(tc.wantTags, got.tags, fileInfoCmpOption); diff != "" {
62 t.Errorf("(-want, +got): %s", diff)
63 }
64 })
65 }
66 }
67
68 func TestFileNameInfo(t *testing.T) {
69 for _, tc := range []struct {
70 desc, name string
71 want fileInfo
72 }{
73 {
74 "simple go file",
75 "simple.go",
76 fileInfo{
77 ext: goExt,
78 },
79 },
80 {
81 "simple go test",
82 "foo_test.go",
83 fileInfo{
84 ext: goExt,
85 isTest: true,
86 },
87 },
88 {
89 "test source",
90 "test.go",
91 fileInfo{
92 ext: goExt,
93 isTest: false,
94 },
95 },
96 {
97 "_test source",
98 "_test.go",
99 fileInfo{
100 ext: unknownExt,
101 },
102 },
103 {
104 "source with goos",
105 "foo_linux.go",
106 fileInfo{
107 ext: goExt,
108 goos: "linux",
109 },
110 },
111 {
112 "source with goarch",
113 "foo_amd64.go",
114 fileInfo{
115 ext: goExt,
116 goarch: "amd64",
117 },
118 },
119 {
120 "source with goos then goarch",
121 "foo_linux_amd64.go",
122 fileInfo{
123 ext: goExt,
124 goos: "linux",
125 goarch: "amd64",
126 },
127 },
128 {
129 "source with goarch then goos",
130 "foo_amd64_linux.go",
131 fileInfo{
132 ext: goExt,
133 goos: "linux",
134 },
135 },
136 {
137 "test with goos and goarch",
138 "foo_linux_amd64_test.go",
139 fileInfo{
140 ext: goExt,
141 goos: "linux",
142 goarch: "amd64",
143 isTest: true,
144 },
145 },
146 {
147 "test then goos",
148 "foo_test_linux.go",
149 fileInfo{
150 ext: goExt,
151 goos: "linux",
152 },
153 },
154 {
155 "goos source",
156 "linux.go",
157 fileInfo{
158 ext: goExt,
159 goos: "",
160 },
161 },
162 {
163 "goarch source",
164 "amd64.go",
165 fileInfo{
166 ext: goExt,
167 goarch: "",
168 },
169 },
170 {
171 "goos test",
172 "linux_test.go",
173 fileInfo{
174 ext: goExt,
175 goos: "",
176 isTest: true,
177 },
178 },
179 {
180 "c file",
181 "foo_test.cxx",
182 fileInfo{
183 ext: cExt,
184 isTest: false,
185 },
186 },
187 {
188 "c os test file",
189 "foo_linux_test.c",
190 fileInfo{
191 ext: cExt,
192 isTest: false,
193 goos: "linux",
194 },
195 },
196 {
197 "h file",
198 "foo_linux.h",
199 fileInfo{
200 ext: hExt,
201 goos: "linux",
202 },
203 },
204 {
205 "go asm file",
206 "foo_amd64.s",
207 fileInfo{
208 ext: sExt,
209 goarch: "amd64",
210 },
211 },
212 {
213 "c asm file",
214 "foo.S",
215 fileInfo{
216 ext: csExt,
217 },
218 },
219 {
220 "unsupported file",
221 "foo.m",
222 fileInfo{
223 ext: cExt,
224 },
225 },
226 {
227 "ignored test file",
228 "foo_test.py",
229 fileInfo{
230 isTest: false,
231 },
232 },
233 {
234 "ignored xtest file",
235 "foo_xtest.py",
236 fileInfo{
237 isTest: false,
238 },
239 },
240 {
241 "ignored file",
242 "foo.txt",
243 fileInfo{
244 ext: unknownExt,
245 },
246 },
247 {
248 "hidden file",
249 ".foo.go",
250 fileInfo{
251 ext: unknownExt,
252 },
253 },
254 } {
255 t.Run(tc.desc, func(t *testing.T) {
256 tc.want.name = tc.name
257 tc.want.path = filepath.Join("dir", tc.name)
258 got := fileNameInfo(tc.want.path)
259 if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
260 t.Errorf("(-want, +got): %s", diff)
261 }
262 })
263 }
264 }
265
266 func TestReadTags(t *testing.T) {
267 for _, tc := range []struct {
268 desc, source string
269 want *buildTags
270 }{
271 {
272 "empty file",
273 "",
274 nil,
275 },
276 {
277 "single comment without blank line",
278 "// +build foo\npackage main",
279 nil,
280 },
281 {
282 "multiple comments without blank link",
283 `// +build foo
284
285 // +build bar
286 package main
287
288 `,
289 &buildTags{
290 expr: mustParseBuildTag(t, "foo"),
291 rawTags: []string{"foo"},
292 },
293 },
294 {
295 "single comment",
296 "// +build foo\n\n",
297 &buildTags{
298 expr: mustParseBuildTag(t, "foo"),
299 rawTags: []string{"foo"},
300 },
301 },
302 {
303 "multiple comments",
304 `// +build foo
305 // +build bar
306
307 package main`,
308 &buildTags{
309 expr: mustParseBuildTag(t, "foo && bar"),
310 rawTags: []string{"foo", "bar"},
311 },
312 },
313 {
314 "multiple comments with blank",
315 `// +build foo
316
317 // +build bar
318
319 package main`,
320 &buildTags{
321 expr: mustParseBuildTag(t, "foo && bar"),
322 rawTags: []string{"foo", "bar"},
323 },
324 },
325 {
326 "Basic go:build",
327 `//go:build foo && bar
328
329 package main`,
330 &buildTags{
331 expr: mustParseBuildTag(t, "foo && bar"),
332 rawTags: []string{"foo", "bar"},
333 },
334 },
335 {
336 "Both go:build and +build",
337 `//go:build foo && bar
338 // +build foo,bar
339
340 package main`,
341 &buildTags{
342 expr: mustParseBuildTag(t, "foo && bar"),
343 rawTags: []string{"foo", "bar"},
344 },
345 },
346 {
347 "comment with space",
348 " // +build foo bar \n\n",
349 &buildTags{
350 expr: mustParseBuildTag(t, "foo || bar"),
351 rawTags: []string{"foo", "bar"},
352 },
353 },
354 {
355 "slash star comment",
356 "/* +build foo */\n\n",
357 nil,
358 },
359 } {
360 t.Run(tc.desc, func(t *testing.T) {
361 f, err := os.CreateTemp(".", "TestReadTags")
362 if err != nil {
363 t.Fatal(err)
364 }
365 path := f.Name()
366 defer os.Remove(path)
367
368 if _, err := f.WriteString(tc.source); err != nil {
369 t.Fatal(err)
370 }
371
372 if got, err := readTags(path); err != nil {
373 t.Fatal(err)
374 } else if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
375 t.Errorf("(-want, +got): %s", diff)
376 }
377 })
378 }
379 }
380
381 func TestCheckConstraints(t *testing.T) {
382 dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestCheckConstraints")
383 if err != nil {
384 t.Fatal(err)
385 }
386 defer os.RemoveAll(dir)
387 for _, tc := range []struct {
388 desc string
389 genericTags map[string]bool
390 os, arch, filename, content string
391 want bool
392 }{
393 {
394 desc: "unconstrained",
395 want: true,
396 }, {
397 desc: "goos satisfied",
398 filename: "foo_linux.go",
399 os: "linux",
400 want: true,
401 }, {
402 desc: "goos unsatisfied",
403 filename: "foo_linux.go",
404 os: "darwin",
405 want: false,
406 }, {
407 desc: "goarch satisfied",
408 filename: "foo_amd64.go",
409 arch: "amd64",
410 want: true,
411 }, {
412 desc: "goarch unsatisfied",
413 filename: "foo_amd64.go",
414 arch: "arm",
415 want: false,
416 }, {
417 desc: "goos goarch satisfied",
418 filename: "foo_linux_amd64.go",
419 os: "linux",
420 arch: "amd64",
421 want: true,
422 }, {
423 desc: "goos goarch unsatisfied",
424 filename: "foo_linux_amd64.go",
425 os: "darwin",
426 arch: "amd64",
427 want: false,
428 }, {
429 desc: "unix filename on darwin",
430 filename: "foo_unix.go",
431 os: "darwin",
432 want: true,
433 }, {
434 desc: "unix filename on windows",
435 filename: "foo_unix.go",
436 os: "windows",
437 want: true,
438 }, {
439 desc: "non-unix tag on linux",
440 filename: "foo_bar.go",
441 os: "darwin",
442 content: "//go:build !unix\n\npackage foo",
443 want: false,
444 }, {
445 desc: "non-unix tag on windows",
446 filename: "foo_bar.go",
447 os: "windows",
448 content: "//go:build !unix\n\npackage foo",
449 want: true,
450 }, {
451 desc: "unix tag on windows",
452 filename: "foo_bar.go",
453 os: "windows",
454 content: "//go:build unix\n\npackage foo",
455 want: false,
456 }, {
457 desc: "unix tag on linux",
458 filename: "foo_bar.go",
459 os: "linux",
460 content: "//go:build unix\n\npackage foo",
461 want: true,
462 }, {
463 desc: "goos unsatisfied tags satisfied",
464 filename: "foo_linux.go",
465 content: "// +build foo\n\npackage foo",
466 want: false,
467 }, {
468 desc: "tags all satisfied",
469 genericTags: map[string]bool{"a": true, "b": true},
470 content: "// +build a,b\n\npackage foo",
471 want: true,
472 }, {
473 desc: "tags some satisfied",
474 genericTags: map[string]bool{"a": true},
475 content: "// +build a,b\n\npackage foo",
476 want: false,
477 }, {
478 desc: "tag unsatisfied negated",
479 content: "// +build !a\n\npackage foo",
480 want: true,
481 }, {
482 desc: "tag satisfied negated",
483 genericTags: map[string]bool{"a": true},
484 content: "// +build !a\n\npackage foo",
485 want: false,
486 }, {
487 desc: "tag double negative",
488 content: "// +build !!a\n\npackage foo",
489 want: false,
490 }, {
491 desc: "tag group and satisfied",
492 genericTags: map[string]bool{"foo": true, "bar": true},
493 content: "// +build foo,bar\n\npackage foo",
494 want: true,
495 }, {
496 desc: "tag group and unsatisfied",
497 genericTags: map[string]bool{"foo": true},
498 content: "// +build foo,bar\n\npackage foo",
499 want: false,
500 }, {
501 desc: "tag line or satisfied",
502 genericTags: map[string]bool{"foo": true},
503 content: "// +build foo bar\n\npackage foo",
504 want: true,
505 }, {
506 desc: "tag line or unsatisfied",
507 genericTags: map[string]bool{"foo": true},
508 content: "// +build !foo bar\n\npackage foo",
509 want: false,
510 }, {
511 desc: "tag lines and satisfied",
512 genericTags: map[string]bool{"foo": true, "bar": true},
513 content: `
514 // +build foo
515 // +build bar
516
517 package foo`,
518 want: true,
519 }, {
520 desc: "tag lines and unsatisfied",
521 genericTags: map[string]bool{"foo": true},
522 content: `
523 // +build foo
524 // +build bar
525
526 package foo`,
527 want: false,
528 }, {
529 desc: "cgo tags satisfied",
530 os: "linux",
531 genericTags: map[string]bool{"foo": true},
532 content: `
533 // +build foo
534
535 package foo
536
537 /*
538 #cgo linux CFLAGS: -Ilinux
539 */
540 import "C"
541 `,
542 want: true,
543 }, {
544 desc: "cgo tags unsatisfied",
545 os: "linux",
546 content: `
547 package foo
548
549 /*
550 #cgo !linux CFLAGS: -Inotlinux
551 */
552 import "C"
553 `,
554 want: false,
555 }, {
556 desc: "release tags",
557 content: "// +build go1.7,go1.8,go1.9,go1.91,go2.0\n\npackage foo",
558 want: true,
559 }, {
560 desc: "release tag negated",
561 content: "// +build !go1.8\n\npackage foo",
562 want: true,
563 }, {
564 desc: "cgo tag",
565 content: "// +build cgo",
566 want: true,
567 }, {
568 desc: "cgo tag negated",
569 content: "// +build !cgo",
570 want: true,
571 }, {
572 desc: "race msan tags",
573 content: "// +build msan race",
574 want: true,
575 }, {
576 desc: "race msan tags negated",
577 content: "//+ build !msan,!race",
578 want: true,
579 },
580 } {
581 t.Run(tc.desc, func(t *testing.T) {
582 c, _, _ := testConfig(t)
583 gc := getGoConfig(c)
584 gc.genericTags = tc.genericTags
585 if gc.genericTags == nil {
586 gc.genericTags = map[string]bool{"gc": true}
587 }
588 filename := tc.filename
589 if filename == "" {
590 filename = tc.desc + ".go"
591 }
592 content := []byte(tc.content)
593 if len(content) == 0 {
594 content = []byte(`package foo`)
595 }
596
597 path := filepath.Join(dir, filename)
598 if err := os.WriteFile(path, content, 0o666); err != nil {
599 t.Fatal(err)
600 }
601
602 fi := goFileInfo(path, "")
603 var cgoTags *cgoTagsAndOpts
604 if len(fi.copts) > 0 {
605 cgoTags = fi.copts[0]
606 }
607
608 got := checkConstraints(c, tc.os, tc.arch, fi.goos, fi.goarch, fi.tags, cgoTags)
609 if diff := cmp.Diff(tc.want, got); diff != "" {
610 t.Errorf("(-want, +got): %s", diff)
611 }
612 })
613 }
614 }
615
616 func TestIsOSArchSpecific(t *testing.T) {
617 for _, tc := range []struct {
618 desc string
619 filename, content string
620
621 expectOSSpecific bool
622 expectArchSpecific bool
623 }{
624 {
625 desc: "normal",
626 filename: "foo.go",
627 content: "package foo",
628 expectOSSpecific: false,
629 expectArchSpecific: false,
630 },
631 {
632 desc: "unix directive",
633 filename: "foo.go",
634 content: "//go:build unix\n\npackage foo",
635 expectOSSpecific: true,
636 expectArchSpecific: false,
637 },
638 {
639 desc: "exclude-unix directive",
640 filename: "foo.go",
641 content: "//go:build !unix\n\npackage foo",
642 expectOSSpecific: true,
643 expectArchSpecific: false,
644 },
645 {
646 desc: "arch directive",
647 filename: "foo.go",
648 content: "//go:build arm64\n\npackage foo",
649 expectOSSpecific: false,
650 expectArchSpecific: true,
651 },
652 {
653 desc: "exclude-arch directive",
654 filename: "foo.go",
655 content: "//go:build !arm64\n\npackage foo",
656 expectOSSpecific: false,
657 expectArchSpecific: true,
658 },
659 {
660 desc: "os directive",
661 filename: "foo.go",
662 content: "//go:build linux\n\npackage foo",
663 expectOSSpecific: true,
664 expectArchSpecific: false,
665 },
666 {
667 desc: "exclude-os directive",
668 filename: "foo.go",
669 content: "//go:build !linux\n\npackage foo",
670 expectOSSpecific: true,
671 expectArchSpecific: false,
672 },
673 {
674 desc: "os and arch directive",
675 filename: "foo.go",
676 content: "//go:build linux && amd64\n\npackage foo",
677 expectOSSpecific: true,
678 expectArchSpecific: true,
679 },
680 {
681 desc: "unix and arch directive",
682 filename: "foo.go",
683 content: "//go:build unix && amd64\n\npackage foo",
684 expectOSSpecific: true,
685 expectArchSpecific: true,
686 },
687 } {
688 t.Run(tc.desc, func(t *testing.T) {
689 tmpDir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestIsOSSpecific_*")
690 if err != nil {
691 t.Fatal(err)
692 }
693 t.Cleanup(func() {
694 os.RemoveAll(tmpDir)
695 })
696
697 path := filepath.Join(tmpDir, tc.filename)
698 if err := os.WriteFile(path, []byte(tc.content), 0o666); err != nil {
699 t.Fatal(err)
700 }
701 fi := goFileInfo(path, "")
702 var cgoTags *cgoTagsAndOpts
703 if len(fi.copts) > 0 {
704 cgoTags = fi.copts[0]
705 }
706
707 gotOSSpecific, gotArchSpecific := isOSArchSpecific(fi, cgoTags)
708 if diff := cmp.Diff(tc.expectOSSpecific, gotOSSpecific); diff != "" {
709 t.Errorf("(-want, +got): %s", diff)
710 }
711 if diff := cmp.Diff(tc.expectArchSpecific, gotArchSpecific); diff != "" {
712 t.Errorf("(-want, +got): %s", diff)
713 }
714 })
715 }
716 }
717
View as plain text