1
15
16 package golang
17
18 import (
19 "go/build/constraint"
20 "os"
21 "path/filepath"
22 "strings"
23 "testing"
24
25 "github.com/google/go-cmp/cmp"
26 )
27
28 var (
29 fileInfoCmpOption = cmp.AllowUnexported(
30 fileInfo{},
31 fileEmbed{},
32 buildTags{},
33 cgoTagsAndOpts{},
34 )
35 )
36
37 func TestGoFileInfo(t *testing.T) {
38 for _, tc := range []struct {
39 desc, name, source string
40 want fileInfo
41 }{
42 {
43 "empty file",
44 "foo.go",
45 "package foo\n",
46 fileInfo{
47 packageName: "foo",
48 },
49 },
50 {
51 "xtest file",
52 "foo_test.go",
53 "package foo_test\n",
54 fileInfo{
55 packageName: "foo",
56 isTest: true,
57 },
58 },
59 {
60 "xtest suffix on non-test",
61 "foo_xtest.go",
62 "package foo_test\n",
63 fileInfo{
64 packageName: "foo_test",
65 isTest: false,
66 },
67 },
68 {
69 "single import",
70 "foo.go",
71 `package foo
72
73 import "github.com/foo/bar"
74 `,
75 fileInfo{
76 packageName: "foo",
77 imports: []string{"github.com/foo/bar"},
78 },
79 },
80 {
81 "multiple imports",
82 "foo.go",
83 `package foo
84
85 import (
86 "github.com/foo/bar"
87 x "github.com/local/project/y"
88 )
89 `,
90 fileInfo{
91 packageName: "foo",
92 imports: []string{"github.com/foo/bar", "github.com/local/project/y"},
93 },
94 },
95 {
96 "standard imports included",
97 "foo.go",
98 `package foo
99
100 import "fmt"
101 `,
102 fileInfo{
103 packageName: "foo",
104 imports: []string{"fmt"},
105 },
106 },
107 {
108 "cgo",
109 "foo.go",
110 `package foo
111
112 import "C"
113 `,
114 fileInfo{
115 packageName: "foo",
116 isCgo: true,
117 },
118 },
119 {
120 "build tags",
121 "foo.go",
122 `// +build linux darwin
123
124 // +build !ignore
125
126 package foo
127 `,
128 fileInfo{
129 packageName: "foo",
130 tags: &buildTags{
131 expr: mustParseBuildTag(t, "(linux || darwin) && !ignore"),
132 rawTags: []string{"linux", "darwin", "ignore"},
133 },
134 },
135 },
136 {
137 "build tags without blank line",
138 "route.go",
139 `// Copyright 2017
140
141 // +build darwin dragonfly freebsd netbsd openbsd
142
143 // Package route provides basic functions for the manipulation of
144 // packet routing facilities on BSD variants.
145 package route
146 `,
147 fileInfo{
148 packageName: "route",
149 tags: &buildTags{
150 expr: mustParseBuildTag(t, "darwin || dragonfly || freebsd || netbsd || openbsd"),
151 rawTags: []string{"darwin", "dragonfly", "freebsd", "netbsd", "openbsd"},
152 },
153 },
154 },
155 {
156 "embed",
157 "embed.go",
158 `package foo
159
160 import _ "embed"
161
162 //go:embed embed.go
163 var src string
164 `,
165 fileInfo{
166 packageName: "foo",
167 imports: []string{"embed"},
168 embeds: []fileEmbed{{path: "embed.go"}},
169 },
170 },
171 } {
172 t.Run(tc.desc, func(t *testing.T) {
173 dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestGoFileInfo")
174 if err != nil {
175 t.Fatal(err)
176 }
177 defer os.RemoveAll(dir)
178 path := filepath.Join(dir, tc.name)
179 if err := os.WriteFile(path, []byte(tc.source), 0o600); err != nil {
180 t.Fatal(err)
181 }
182
183 got := goFileInfo(path, "")
184
185 got = fileInfo{
186 packageName: got.packageName,
187 isTest: got.isTest,
188 imports: got.imports,
189 embeds: got.embeds,
190 isCgo: got.isCgo,
191 tags: got.tags,
192 }
193 for i := range got.embeds {
194 got.embeds[i] = fileEmbed{path: got.embeds[i].path}
195 }
196
197 if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
198 t.Errorf("(-want, +got): %s", diff)
199 }
200
201 })
202 }
203 }
204
205 func TestGoFileInfoFailure(t *testing.T) {
206 dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestGoFileInfoFailure")
207 if err != nil {
208 t.Fatal(err)
209 }
210 defer os.RemoveAll(dir)
211 name := "foo_linux_amd64.go"
212 path := filepath.Join(dir, name)
213 if err := os.WriteFile(path, []byte("pakcage foo"), 0o600); err != nil {
214 t.Fatal(err)
215 }
216
217 got := goFileInfo(path, "")
218 want := fileInfo{
219 path: path,
220 name: name,
221 ext: goExt,
222 goos: "linux",
223 goarch: "amd64",
224 }
225 if diff := cmp.Diff(want, got, fileInfoCmpOption); diff != "" {
226 t.Errorf("(-want, +got): %s", diff)
227 }
228
229 }
230
231 func TestCgo(t *testing.T) {
232 for _, tc := range []struct {
233 desc, source string
234 want fileInfo
235 }{
236 {
237 "not cgo",
238 "package foo\n",
239 fileInfo{isCgo: false},
240 },
241 {
242 "empty cgo",
243 `package foo
244
245 import "C"
246 `,
247 fileInfo{isCgo: true},
248 },
249 {
250 "simple flags",
251 `package foo
252
253 /*
254 #cgo CFLAGS: -O0
255 #cgo CPPFLAGS: -O1
256 #cgo CXXFLAGS: -O2
257 #cgo LDFLAGS: -O3 -O4
258 */
259 import "C"
260 `,
261 fileInfo{
262 isCgo: true,
263 cppopts: []*cgoTagsAndOpts{
264 {opts: "-O1"},
265 },
266 copts: []*cgoTagsAndOpts{
267 {opts: "-O0"},
268 },
269 cxxopts: []*cgoTagsAndOpts{
270 {opts: "-O2"},
271 },
272 clinkopts: []*cgoTagsAndOpts{
273 {opts: strings.Join([]string{"-O3", "-O4"}, optSeparator)},
274 },
275 },
276 },
277 {
278 "cflags with conditions",
279 `package foo
280
281 /*
282 #cgo foo bar,!baz CFLAGS: -O0
283 */
284 import "C"
285 `,
286 fileInfo{
287 isCgo: true,
288 copts: []*cgoTagsAndOpts{
289 {
290 buildTags: &buildTags{
291 expr: mustParseBuildTag(t, "foo || (bar && !baz)"),
292 rawTags: []string{"foo", "bar", "baz"},
293 },
294 opts: "-O0",
295 },
296 },
297 },
298 },
299 {
300 "slashslash comments",
301 `package foo
302
303 // #cgo CFLAGS: -O0
304 // #cgo CFLAGS: -O1
305 import "C"
306 `,
307 fileInfo{
308 isCgo: true,
309 copts: []*cgoTagsAndOpts{
310 {opts: "-O0"},
311 {opts: "-O1"},
312 },
313 },
314 },
315 {
316 "comment above single import group",
317 `package foo
318
319 /*
320 #cgo CFLAGS: -O0
321 */
322 import ("C")
323 `,
324 fileInfo{
325 isCgo: true,
326 copts: []*cgoTagsAndOpts{
327 {opts: "-O0"},
328 },
329 },
330 },
331 } {
332 t.Run(tc.desc, func(t *testing.T) {
333 dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestCgo")
334 if err != nil {
335 t.Fatal(err)
336 }
337 defer os.RemoveAll(dir)
338 name := "TestCgo.go"
339 path := filepath.Join(dir, name)
340 if err := os.WriteFile(path, []byte(tc.source), 0o600); err != nil {
341 t.Fatal(err)
342 }
343
344 got := goFileInfo(path, "")
345
346
347 got = fileInfo{
348 isCgo: got.isCgo,
349 copts: got.copts,
350 cppopts: got.cppopts,
351 cxxopts: got.cxxopts,
352 clinkopts: got.clinkopts,
353 }
354
355 if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
356 t.Errorf("(-want, +got): %s", diff)
357 }
358
359 })
360 }
361 }
362
363
364 var (
365 expandSrcDirPath = filepath.Join(string(filepath.Separator)+"projects", "src", "add")
366 )
367
368
369 var expandSrcDirTests = []struct {
370 input, expected string
371 }{
372 {"-L ${SRCDIR}/libs -ladd", "-L /projects/src/add/libs -ladd"},
373 {"${SRCDIR}/add_linux_386.a -pthread -lstdc++", "/projects/src/add/add_linux_386.a -pthread -lstdc++"},
374 {"Nothing to expand here!", "Nothing to expand here!"},
375 {"$", "$"},
376 {"$$", "$$"},
377 {"${", "${"},
378 {"$}", "$}"},
379 {"$FOO ${BAR}", "$FOO ${BAR}"},
380 {"Find me the $SRCDIRECTORY.", "Find me the $SRCDIRECTORY."},
381 {"$SRCDIR is missing braces", "$SRCDIR is missing braces"},
382 }
383
384
385 func TestExpandSrcDir(t *testing.T) {
386 for _, test := range expandSrcDirTests {
387 output, _ := expandSrcDir(test.input, expandSrcDirPath)
388 if output != test.expected {
389 t.Errorf("%q expands to %q with SRCDIR=%q when %q is expected", test.input, output, expandSrcDirPath, test.expected)
390 } else {
391 t.Logf("%q expands to %q with SRCDIR=%q", test.input, output, expandSrcDirPath)
392 }
393 }
394 }
395
396 var (
397 goPackageCmpOption = cmp.AllowUnexported(
398 goPackage{},
399 goTarget{},
400 protoTarget{},
401 platformStringsBuilder{},
402 platformStringInfo{},
403 )
404 )
405
406 func TestExpandSrcDirRepoRelative(t *testing.T) {
407 repo, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "repo")
408 if err != nil {
409 t.Fatal(err)
410 }
411 sub := filepath.Join(repo, "sub")
412 if err := os.Mkdir(sub, 0o755); err != nil {
413 t.Fatal(err)
414 }
415 goFile := filepath.Join(sub, "sub.go")
416 content := []byte(`package sub
417
418 /*
419 #cgo CFLAGS: -I${SRCDIR}/..
420 */
421 import "C"
422 `)
423 if err := os.WriteFile(goFile, content, 0o644); err != nil {
424 t.Fatal(err)
425 }
426 c, _, _ := testConfig(
427 t,
428 "-repo_root="+repo,
429 "-go_prefix=example.com/repo")
430 fi := goFileInfo(filepath.Join(sub, "sub.go"), "sub")
431 pkgs, _ := buildPackages(c, sub, "sub", false, nil, []fileInfo{fi})
432 got, ok := pkgs["sub"]
433 if !ok {
434 t.Fatal("did not build package 'sub'")
435 }
436 want := &goPackage{
437 name: "sub",
438 dir: sub,
439 rel: "sub",
440 library: goTarget{cgo: true},
441 }
442 want.library.sources.addGenericString("sub.go")
443 want.library.copts.addGenericString("-Isub/..")
444 if diff := cmp.Diff(want, got, goPackageCmpOption); diff != "" {
445 t.Errorf("(-want, +got): %s", diff)
446 }
447
448 }
449
450
451 func TestShellSafety(t *testing.T) {
452 tests := []struct {
453 input, srcdir, expected string
454 result bool
455 }{
456 {"-I${SRCDIR}/../include", "/projects/src/issue 11868", "-I/projects/src/issue 11868/../include", true},
457 {"-I${SRCDIR}", "wtf$@%", "-Iwtf$@%", true},
458 {"-X${SRCDIR}/1,${SRCDIR}/2", "/projects/src/issue 11868", "-X/projects/src/issue 11868/1,/projects/src/issue 11868/2", true},
459 {"-I/tmp -I/tmp", "/tmp2", "-I/tmp -I/tmp", false},
460 {"-I/tmp", "/tmp/[0]", "-I/tmp", true},
461 {"-I${SRCDIR}/dir", "/tmp/[0]", "-I/tmp/[0]/dir", false},
462 }
463 for _, test := range tests {
464 output, ok := expandSrcDir(test.input, test.srcdir)
465 if ok != test.result {
466 t.Errorf("Expected %t while %q expands to %q with SRCDIR=%q; got %t", test.result, test.input, output, test.srcdir, ok)
467 }
468 if output != test.expected {
469 t.Errorf("Expected %q while %q expands with SRCDIR=%q; got %q", test.expected, test.input, test.srcdir, output)
470 }
471 }
472 }
473
474 func mustParseBuildTag(t *testing.T, in string) constraint.Expr {
475 x, err := constraint.Parse("//go:build " + in)
476 if err != nil {
477 t.Fatalf("%s: %s", in, err)
478 }
479
480 return x
481 }
482
View as plain text