package main import ( "bytes" "os" "path/filepath" "reflect" "strings" "testing" ) const minimalSocketFilter = `__attribute__((section("socket"), used)) int main() { return 0; }` // Test against the minimum supported version of clang to avoid regressions. const ( clangBin = "clang-9" ) func TestCompile(t *testing.T) { dir := mustWriteTempFile(t, "test.c", minimalSocketFilter) var dep bytes.Buffer err := compile(compileArgs{ cc: clangBin, dir: dir, source: filepath.Join(dir, "test.c"), dest: filepath.Join(dir, "test.o"), dep: &dep, }) if err != nil { t.Fatal("Can't compile:", err) } stat, err := os.Stat(filepath.Join(dir, "test.o")) if err != nil { t.Fatal("Can't stat output:", err) } if stat.Size() == 0 { t.Error("Compilation creates an empty file") } if dep.Len() == 0 { t.Error("Compilation doesn't generate depinfo") } if _, err := parseDependencies(dir, &dep); err != nil { t.Error("Can't parse dependencies:", err) } } func TestReproducibleCompile(t *testing.T) { dir := mustWriteTempFile(t, "test.c", minimalSocketFilter) err := compile(compileArgs{ cc: clangBin, dir: dir, source: filepath.Join(dir, "test.c"), dest: filepath.Join(dir, "a.o"), }) if err != nil { t.Fatal("Can't compile:", err) } err = compile(compileArgs{ cc: clangBin, dir: dir, source: filepath.Join(dir, "test.c"), dest: filepath.Join(dir, "b.o"), }) if err != nil { t.Fatal("Can't compile:", err) } aBytes, err := os.ReadFile(filepath.Join(dir, "a.o")) if err != nil { t.Fatal(err) } bBytes, err := os.ReadFile(filepath.Join(dir, "b.o")) if err != nil { t.Fatal(err) } if !bytes.Equal(aBytes, bBytes) { t.Error("Compiling the same file twice doesn't give the same result") } } func TestTriggerMissingTarget(t *testing.T) { dir := mustWriteTempFile(t, "test.c", `_Pragma(__BPF_TARGET_MISSING);`) err := compile(compileArgs{ cc: clangBin, dir: dir, source: filepath.Join(dir, "test.c"), dest: filepath.Join(dir, "a.o"), }) if err == nil { t.Fatal("No error when compiling __BPF_TARGET_MISSING") } } func TestParseDependencies(t *testing.T) { const input = `main.go: /foo/bar baz frob: /gobble \ gubble nothing: ` have, err := parseDependencies("/foo", strings.NewReader(input)) if err != nil { t.Fatal("Can't parse dependencies:", err) } want := []dependency{ {"/foo/main.go", []string{"/foo/bar", "/foo/baz"}}, {"/foo/frob", []string{"/gobble", "/foo/gubble"}}, {"/foo/nothing", nil}, } if !reflect.DeepEqual(have, want) { t.Logf("Have: %#v", have) t.Logf("Want: %#v", want) t.Error("Result doesn't match") } output, err := adjustDependencies("/foo", want) if err != nil { t.Error("Can't adjust dependencies") } const wantOutput = `main.go: \ bar \ baz frob: \ ../gobble \ gubble nothing: ` if have := string(output); have != wantOutput { t.Logf("Have:\n%s", have) t.Logf("Want:\n%s", wantOutput) t.Error("Output doesn't match") } } func mustWriteTempFile(t *testing.T, name, contents string) string { t.Helper() tmp, err := os.MkdirTemp("", "bpf2go") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(tmp) }) tmpFile := filepath.Join(tmp, name) if err := os.WriteFile(tmpFile, []byte(contents), 0660); err != nil { t.Fatal(err) } return tmp }