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  }