github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/cmd/bpf2go/main_test.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "slices" 13 "strings" 14 "testing" 15 16 "github.com/go-quicktest/qt" 17 "github.com/google/go-cmp/cmp" 18 ) 19 20 func TestRun(t *testing.T) { 21 clangBin := clangBin(t) 22 dir := t.TempDir() 23 mustWriteFile(t, dir, "test.c", minimalSocketFilter) 24 25 modRoot, err := filepath.Abs("../..") 26 qt.Assert(t, qt.IsNil(err)) 27 28 if _, err := os.Stat(filepath.Join(modRoot, "go.mod")); os.IsNotExist(err) { 29 t.Fatal("No go.mod file in", modRoot) 30 } 31 32 modDir := t.TempDir() 33 execInModule := func(name string, args ...string) { 34 t.Helper() 35 36 cmd := exec.Command(name, args...) 37 cmd.Dir = modDir 38 if out, err := cmd.CombinedOutput(); err != nil { 39 if out := string(out); out != "" { 40 t.Log(out) 41 } 42 t.Fatalf("Can't execute %s: %v", name, args) 43 } 44 } 45 46 module := currentModule() 47 48 execInModule("go", "mod", "init", "bpf2go-test") 49 50 execInModule("go", "mod", "edit", 51 // Require the module. The version doesn't matter due to the replace 52 // below. 53 fmt.Sprintf("-require=%s@v0.0.0", module), 54 // Replace the module with the current version. 55 fmt.Sprintf("-replace=%s=%s", module, modRoot), 56 ) 57 58 goarches := []string{ 59 "amd64", // little-endian 60 "arm64", 61 "s390x", // big-endian 62 } 63 64 err = run(io.Discard, []string{ 65 "-go-package", "main", 66 "-output-dir", modDir, 67 "-cc", clangBin, 68 "-target", strings.Join(goarches, ","), 69 "bar", 70 filepath.Join(dir, "test.c"), 71 }) 72 73 if err != nil { 74 t.Fatal("Can't run:", err) 75 } 76 77 mustWriteFile(t, modDir, "main.go", 78 ` 79 package main 80 81 func main() { 82 var obj barObjects 83 println(obj.Main) 84 }`) 85 86 for _, arch := range goarches { 87 t.Run(arch, func(t *testing.T) { 88 goBuild := exec.Command("go", "build", "-mod=mod", "-o", "/dev/null") 89 goBuild.Dir = modDir 90 goBuild.Env = append(os.Environ(), 91 "GOOS=linux", 92 "GOARCH="+arch, 93 "GOPROXY=off", 94 "GOSUMDB=off", 95 ) 96 out, err := goBuild.CombinedOutput() 97 if err != nil { 98 if out := string(out); out != "" { 99 t.Log(out) 100 } 101 t.Error("Can't compile package:", err) 102 } 103 }) 104 } 105 } 106 107 func TestHelp(t *testing.T) { 108 var stdout bytes.Buffer 109 err := run(&stdout, []string{"-help"}) 110 if err != nil { 111 t.Fatal("Can't execute -help") 112 } 113 114 if stdout.Len() == 0 { 115 t.Error("-help doesn't write to stdout") 116 } 117 } 118 119 func TestErrorMentionsEnvVar(t *testing.T) { 120 err := run(io.Discard, nil) 121 qt.Assert(t, qt.StringContains(err.Error(), gopackageEnv), qt.Commentf("Error should include name of environment variable")) 122 } 123 124 func TestDisableStripping(t *testing.T) { 125 dir := t.TempDir() 126 mustWriteFile(t, dir, "test.c", minimalSocketFilter) 127 128 err := run(io.Discard, []string{ 129 "-go-package", "foo", 130 "-output-dir", dir, 131 "-cc", clangBin(t), 132 "-strip", "binary-that-certainly-doesnt-exist", 133 "-no-strip", 134 "bar", 135 filepath.Join(dir, "test.c"), 136 }) 137 138 if err != nil { 139 t.Fatal("Can't run with stripping disabled:", err) 140 } 141 } 142 143 func TestCollectTargets(t *testing.T) { 144 clangArches := make(map[string][]goarch) 145 linuxArchesLE := make(map[string][]goarch) 146 linuxArchesBE := make(map[string][]goarch) 147 for arch, archTarget := range targetByGoArch { 148 clangArches[archTarget.clang] = append(clangArches[archTarget.clang], arch) 149 if archTarget.clang == "bpfel" { 150 linuxArchesLE[archTarget.linux] = append(linuxArchesLE[archTarget.linux], arch) 151 continue 152 } 153 linuxArchesBE[archTarget.linux] = append(linuxArchesBE[archTarget.linux], arch) 154 } 155 for i := range clangArches { 156 slices.Sort(clangArches[i]) 157 } 158 for i := range linuxArchesLE { 159 slices.Sort(linuxArchesLE[i]) 160 } 161 for i := range linuxArchesBE { 162 slices.Sort(linuxArchesBE[i]) 163 } 164 165 nativeTarget := make(map[target][]goarch) 166 for arch, archTarget := range targetByGoArch { 167 if arch == goarch(runtime.GOARCH) { 168 if archTarget.clang == "bpfel" { 169 nativeTarget[archTarget] = linuxArchesLE[archTarget.linux] 170 } else { 171 nativeTarget[archTarget] = linuxArchesBE[archTarget.linux] 172 } 173 break 174 } 175 } 176 177 tests := []struct { 178 targets []string 179 want map[target][]goarch 180 }{ 181 { 182 []string{"bpf", "bpfel", "bpfeb"}, 183 map[target][]goarch{ 184 {"bpf", ""}: nil, 185 {"bpfel", ""}: clangArches["bpfel"], 186 {"bpfeb", ""}: clangArches["bpfeb"], 187 }, 188 }, 189 { 190 []string{"amd64", "386"}, 191 map[target][]goarch{ 192 {"bpfel", "x86"}: linuxArchesLE["x86"], 193 }, 194 }, 195 { 196 []string{"amd64", "ppc64"}, 197 map[target][]goarch{ 198 {"bpfeb", "powerpc"}: linuxArchesBE["powerpc"], 199 {"bpfel", "x86"}: linuxArchesLE["x86"], 200 }, 201 }, 202 { 203 []string{"native"}, 204 nativeTarget, 205 }, 206 } 207 208 for _, test := range tests { 209 name := strings.Join(test.targets, ",") 210 t.Run(name, func(t *testing.T) { 211 have, err := collectTargets(test.targets) 212 if err != nil { 213 t.Fatal(err) 214 } 215 216 if diff := cmp.Diff(test.want, have); diff != "" { 217 t.Errorf("Result mismatch (-want +got):\n%s", diff) 218 } 219 }) 220 } 221 } 222 223 func TestCollectTargetsErrors(t *testing.T) { 224 tests := []struct { 225 name string 226 target string 227 }{ 228 {"unknown", "frood"}, 229 {"no linux target", "mipsle"}, 230 } 231 232 for _, test := range tests { 233 t.Run(test.name, func(t *testing.T) { 234 _, err := collectTargets([]string{test.target}) 235 if err == nil { 236 t.Fatal("Function did not return an error") 237 } 238 t.Log("Error message:", err) 239 }) 240 } 241 } 242 243 func TestConvertGOARCH(t *testing.T) { 244 tmp := t.TempDir() 245 mustWriteFile(t, tmp, "test.c", 246 ` 247 #ifndef __TARGET_ARCH_x86 248 #error __TARGET_ARCH_x86 is not defined 249 #endif`, 250 ) 251 252 b2g := bpf2go{ 253 pkg: "test", 254 stdout: io.Discard, 255 identStem: "test", 256 cc: clangBin(t), 257 disableStripping: true, 258 sourceFile: tmp + "/test.c", 259 outputDir: tmp, 260 } 261 262 if err := b2g.convert(targetByGoArch["amd64"], nil); err != nil { 263 t.Fatal("Can't target GOARCH:", err) 264 } 265 } 266 267 func TestCTypes(t *testing.T) { 268 var ct cTypes 269 valid := []string{ 270 "abcdefghijklmnopqrstuvqxyABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_", 271 "y", 272 } 273 for _, value := range valid { 274 if err := ct.Set(value); err != nil { 275 t.Fatalf("Set returned an error for %q: %s", value, err) 276 } 277 } 278 qt.Assert(t, qt.ContentEquals(ct, valid)) 279 280 for _, value := range []string{ 281 "", 282 " ", 283 " frood", 284 "foo\nbar", 285 ".", 286 ",", 287 "+", 288 "-", 289 } { 290 ct = nil 291 if err := ct.Set(value); err == nil { 292 t.Fatalf("Set did not return an error for %q", value) 293 } 294 } 295 296 ct = nil 297 qt.Assert(t, qt.IsNil(ct.Set("foo"))) 298 qt.Assert(t, qt.IsNotNil(ct.Set("foo"))) 299 } 300 301 func TestParseArgs(t *testing.T) { 302 const ( 303 pkg = "eee" 304 outputDir = "." 305 csource = "testdata/minimal.c" 306 stem = "a" 307 ) 308 t.Run("makebase", func(t *testing.T) { 309 t.Setenv(gopackageEnv, pkg) 310 basePath, _ := filepath.Abs("barfoo") 311 args := []string{"-makebase", basePath, stem, csource} 312 b2g, err := newB2G(&bytes.Buffer{}, args) 313 qt.Assert(t, qt.IsNil(err)) 314 qt.Assert(t, qt.Equals(b2g.makeBase, basePath)) 315 }) 316 317 t.Run("makebase from env", func(t *testing.T) { 318 t.Setenv(gopackageEnv, pkg) 319 basePath, _ := filepath.Abs("barfoo") 320 args := []string{stem, csource} 321 t.Setenv("BPF2GO_MAKEBASE", basePath) 322 b2g, err := newB2G(&bytes.Buffer{}, args) 323 qt.Assert(t, qt.IsNil(err)) 324 qt.Assert(t, qt.Equals(b2g.makeBase, basePath)) 325 }) 326 327 t.Run("makebase flag overrides env", func(t *testing.T) { 328 t.Setenv(gopackageEnv, pkg) 329 basePathFlag, _ := filepath.Abs("barfoo") 330 basePathEnv, _ := filepath.Abs("foobar") 331 args := []string{"-makebase", basePathFlag, stem, csource} 332 t.Setenv("BPF2GO_MAKEBASE", basePathEnv) 333 b2g, err := newB2G(&bytes.Buffer{}, args) 334 qt.Assert(t, qt.IsNil(err)) 335 qt.Assert(t, qt.Equals(b2g.makeBase, basePathFlag)) 336 }) 337 338 t.Run("cc defaults to clang", func(t *testing.T) { 339 t.Setenv(gopackageEnv, pkg) 340 args := []string{stem, csource} 341 b2g, err := newB2G(&bytes.Buffer{}, args) 342 qt.Assert(t, qt.IsNil(err)) 343 qt.Assert(t, qt.Equals(b2g.cc, "clang")) 344 }) 345 346 t.Run("cc", func(t *testing.T) { 347 t.Setenv(gopackageEnv, pkg) 348 args := []string{"-cc", "barfoo", stem, csource} 349 b2g, err := newB2G(&bytes.Buffer{}, args) 350 qt.Assert(t, qt.IsNil(err)) 351 qt.Assert(t, qt.Equals(b2g.cc, "barfoo")) 352 }) 353 354 t.Run("cc from env", func(t *testing.T) { 355 t.Setenv(gopackageEnv, pkg) 356 args := []string{stem, csource} 357 t.Setenv("BPF2GO_CC", "barfoo") 358 b2g, err := newB2G(&bytes.Buffer{}, args) 359 qt.Assert(t, qt.IsNil(err)) 360 qt.Assert(t, qt.Equals(b2g.cc, "barfoo")) 361 }) 362 363 t.Run("cc flag overrides env", func(t *testing.T) { 364 t.Setenv(gopackageEnv, pkg) 365 args := []string{"-cc", "barfoo", stem, csource} 366 t.Setenv("BPF2GO_CC", "foobar") 367 b2g, err := newB2G(&bytes.Buffer{}, args) 368 qt.Assert(t, qt.IsNil(err)) 369 qt.Assert(t, qt.Equals(b2g.cc, "barfoo")) 370 }) 371 372 t.Run("strip defaults to llvm-strip", func(t *testing.T) { 373 t.Setenv(gopackageEnv, pkg) 374 args := []string{stem, csource} 375 b2g, err := newB2G(&bytes.Buffer{}, args) 376 qt.Assert(t, qt.IsNil(err)) 377 qt.Assert(t, qt.Equals(b2g.strip, "llvm-strip")) 378 }) 379 380 t.Run("strip", func(t *testing.T) { 381 t.Setenv(gopackageEnv, pkg) 382 args := []string{"-strip", "barfoo", stem, csource} 383 b2g, err := newB2G(&bytes.Buffer{}, args) 384 qt.Assert(t, qt.IsNil(err)) 385 qt.Assert(t, qt.Equals(b2g.strip, "barfoo")) 386 }) 387 388 t.Run("strip from env", func(t *testing.T) { 389 t.Setenv(gopackageEnv, pkg) 390 args := []string{stem, csource} 391 t.Setenv("BPF2GO_STRIP", "barfoo") 392 b2g, err := newB2G(&bytes.Buffer{}, args) 393 qt.Assert(t, qt.IsNil(err)) 394 qt.Assert(t, qt.Equals(b2g.strip, "barfoo")) 395 }) 396 397 t.Run("strip flag overrides env", func(t *testing.T) { 398 t.Setenv(gopackageEnv, pkg) 399 args := []string{"-strip", "barfoo", stem, csource} 400 t.Setenv("BPF2GO_STRIP", "foobar") 401 b2g, err := newB2G(&bytes.Buffer{}, args) 402 qt.Assert(t, qt.IsNil(err)) 403 qt.Assert(t, qt.Equals(b2g.strip, "barfoo")) 404 }) 405 406 t.Run("no strip defaults to false", func(t *testing.T) { 407 t.Setenv(gopackageEnv, pkg) 408 args := []string{stem, csource} 409 b2g, err := newB2G(&bytes.Buffer{}, args) 410 qt.Assert(t, qt.IsNil(err)) 411 qt.Assert(t, qt.IsFalse(b2g.disableStripping)) 412 }) 413 414 t.Run("no strip", func(t *testing.T) { 415 t.Setenv(gopackageEnv, pkg) 416 args := []string{"-no-strip", stem, csource} 417 b2g, err := newB2G(&bytes.Buffer{}, args) 418 qt.Assert(t, qt.IsNil(err)) 419 qt.Assert(t, qt.IsTrue(b2g.disableStripping)) 420 }) 421 422 t.Run("cflags flag", func(t *testing.T) { 423 t.Setenv(gopackageEnv, pkg) 424 args := []string{"-cflags", "x y z", stem, csource} 425 b2g, err := newB2G(&bytes.Buffer{}, args) 426 qt.Assert(t, qt.IsNil(err)) 427 qt.Assert(t, qt.DeepEquals(b2g.cFlags, []string{"x", "y", "z"})) 428 }) 429 430 t.Run("cflags multi flag", func(t *testing.T) { 431 t.Setenv(gopackageEnv, pkg) 432 args := []string{"-cflags", "x y z", "-cflags", "u v", stem, csource} 433 b2g, err := newB2G(&bytes.Buffer{}, args) 434 qt.Assert(t, qt.IsNil(err)) 435 qt.Assert(t, qt.DeepEquals(b2g.cFlags, []string{"u", "v"})) 436 }) 437 438 t.Run("cflags flag and args", func(t *testing.T) { 439 t.Setenv(gopackageEnv, pkg) 440 args := []string{"-cflags", "x y z", "stem", csource, "--", "u", "v"} 441 b2g, err := newB2G(&bytes.Buffer{}, args) 442 qt.Assert(t, qt.IsNil(err)) 443 qt.Assert(t, qt.DeepEquals(b2g.cFlags, []string{"x", "y", "z", "u", "v"})) 444 }) 445 446 t.Run("cflags from env", func(t *testing.T) { 447 t.Setenv(gopackageEnv, pkg) 448 args := []string{stem, csource} 449 t.Setenv("BPF2GO_CFLAGS", "x y z") 450 b2g, err := newB2G(&bytes.Buffer{}, args) 451 qt.Assert(t, qt.IsNil(err)) 452 qt.Assert(t, qt.DeepEquals(b2g.cFlags, []string{"x", "y", "z"})) 453 }) 454 455 t.Run("cflags flag overrides env", func(t *testing.T) { 456 t.Setenv(gopackageEnv, pkg) 457 args := []string{"-cflags", "u v", stem, csource} 458 t.Setenv("BPF2GO_CFLAGS", "x y z") 459 b2g, err := newB2G(&bytes.Buffer{}, args) 460 qt.Assert(t, qt.IsNil(err)) 461 qt.Assert(t, qt.DeepEquals(b2g.cFlags, []string{"u", "v"})) 462 }) 463 464 t.Run("go package overrides env", func(t *testing.T) { 465 t.Setenv(gopackageEnv, pkg) 466 args := []string{"-go-package", "aaa", stem, csource} 467 b2g, err := newB2G(&bytes.Buffer{}, args) 468 qt.Assert(t, qt.IsNil(err)) 469 qt.Assert(t, qt.Equals(b2g.pkg, "aaa")) 470 }) 471 472 t.Run("output dir", func(t *testing.T) { 473 t.Setenv(gopackageEnv, pkg) 474 args := []string{"-output-dir", outputDir, stem, csource} 475 b2g, err := newB2G(&bytes.Buffer{}, args) 476 qt.Assert(t, qt.IsNil(err)) 477 qt.Assert(t, qt.Equals(b2g.outputDir, outputDir)) 478 }) 479 } 480 481 func TestGoarches(t *testing.T) { 482 exe := goBin(t) 483 484 for goarch := range targetByGoArch { 485 t.Run(string(goarch), func(t *testing.T) { 486 goEnv := exec.Command(exe, "env") 487 goEnv.Env = []string{"GOROOT=/", "GOOS=linux", "GOARCH=" + string(goarch)} 488 output, err := goEnv.CombinedOutput() 489 qt.Assert(t, qt.IsNil(err), qt.Commentf("go output is:\n%s", string(output))) 490 }) 491 } 492 } 493 494 func TestClangTargets(t *testing.T) { 495 exe := goBin(t) 496 497 clangTargets := map[string]struct{}{} 498 for _, tgt := range targetByGoArch { 499 clangTargets[tgt.clang] = struct{}{} 500 } 501 502 for target := range clangTargets { 503 for _, env := range []string{"GOOS", "GOARCH"} { 504 env += "=" + target 505 t.Run(env, func(t *testing.T) { 506 goEnv := exec.Command(exe, "env") 507 goEnv.Env = []string{"GOROOT=/", env} 508 output, err := goEnv.CombinedOutput() 509 t.Log("go output is:", string(output)) 510 qt.Assert(t, qt.IsNotNil(err), qt.Commentf("No clang target should be a valid build constraint")) 511 }) 512 } 513 514 } 515 } 516 517 func clangBin(t *testing.T) string { 518 t.Helper() 519 520 if testing.Short() { 521 t.Skip("Not compiling with -short") 522 } 523 524 // Use a floating clang version for local development, but allow CI to run 525 // against oldest supported clang. 526 clang := "clang" 527 if minVersion := os.Getenv("CI_MIN_CLANG_VERSION"); minVersion != "" { 528 clang = fmt.Sprintf("clang-%s", minVersion) 529 } 530 531 t.Log("Testing against", clang) 532 return clang 533 } 534 535 func goBin(t *testing.T) string { 536 t.Helper() 537 538 exe, err := exec.LookPath("go") 539 if errors.Is(err, exec.ErrNotFound) { 540 t.Skip("go binary is not in PATH") 541 } 542 qt.Assert(t, qt.IsNil(err)) 543 544 return exe 545 }