github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/clangtool/tooltest/tooltest.go (about) 1 // Copyright 2025 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package tooltest 5 6 import ( 7 "encoding/json" 8 "flag" 9 "fmt" 10 "os" 11 "path/filepath" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 "github.com/google/syzkaller/pkg/clangtool" 16 "github.com/google/syzkaller/pkg/osutil" 17 "github.com/google/syzkaller/pkg/testutil" 18 ) 19 20 var ( 21 FlagBin = flag.String("bin", "", "path to the clang tool binary to use") 22 FlagUpdate = flag.Bool("update", false, "update golden files") 23 ) 24 25 func TestClangTool[Output any, OutputPtr clangtool.OutputDataPtr[Output]](t *testing.T) { 26 if *FlagBin == "" { 27 t.Skipf("clang tool path is not specified, run with -bin=clangtool flag") 28 } 29 ForEachTestFile(t, func(t *testing.T, cfg *clangtool.Config, file string) { 30 out, err := clangtool.Run[Output, OutputPtr](cfg) 31 if err != nil { 32 t.Fatal(err) 33 } 34 got, err := json.MarshalIndent(out, "", "\t") 35 if err != nil { 36 t.Fatal(err) 37 } 38 CompareGoldenData(t, file+".json", got) 39 }) 40 } 41 42 func LoadOutput[Output any, OutputPtr clangtool.OutputDataPtr[Output]](t *testing.T) OutputPtr { 43 out := OutputPtr(new(Output)) 44 forEachTestFile(t, func(t *testing.T, file string) { 45 tmp, err := osutil.ReadJSON[OutputPtr](file + ".json") 46 if err != nil { 47 t.Fatal(err) 48 } 49 out.Merge(tmp) 50 }) 51 if err := clangtool.Finalize(out, []string{"testdata"}); err != nil { 52 t.Fatal(err) 53 } 54 return out 55 } 56 57 func ForEachTestFile(t *testing.T, fn func(t *testing.T, cfg *clangtool.Config, file string)) { 58 forEachTestFile(t, func(t *testing.T, file string) { 59 t.Run(filepath.Base(file), func(t *testing.T) { 60 t.Parallel() 61 buildDir := t.TempDir() 62 commands := fmt.Sprintf(`[{ 63 "file": "%s", 64 "directory": "%s", 65 "command": "clang -c %s -DKBUILD_BASENAME=foo" 66 }]`, 67 file, buildDir, file) 68 dbFile := filepath.Join(buildDir, "compile_commands.json") 69 if err := os.WriteFile(dbFile, []byte(commands), 0600); err != nil { 70 t.Fatal(err) 71 } 72 cfg := &clangtool.Config{ 73 ToolBin: *FlagBin, 74 KernelSrc: osutil.Abs("testdata"), 75 KernelObj: buildDir, 76 CacheFile: filepath.Join(buildDir, filepath.Base(file)+".json"), 77 DebugTrace: &testutil.Writer{TB: t}, 78 } 79 fn(t, cfg, file) 80 }) 81 }) 82 } 83 84 func forEachTestFile(t *testing.T, fn func(t *testing.T, file string)) { 85 files, err := filepath.Glob(filepath.Join(osutil.Abs("testdata"), "*.c")) 86 if err != nil { 87 t.Fatal(err) 88 } 89 if len(files) == 0 { 90 t.Fatal("found no source files") 91 } 92 for _, file := range files { 93 fn(t, file) 94 } 95 } 96 97 func CompareGoldenFile(t *testing.T, goldenFile, gotFile string) { 98 got, err := os.ReadFile(gotFile) 99 if err != nil { 100 t.Fatal(err) 101 } 102 CompareGoldenData(t, goldenFile, got) 103 } 104 105 func CompareGoldenData(t *testing.T, goldenFile string, got []byte) { 106 if *FlagUpdate { 107 if err := os.WriteFile(goldenFile, got, 0644); err != nil { 108 t.Fatal(err) 109 } 110 } 111 want, err := os.ReadFile(goldenFile) 112 if err != nil { 113 t.Fatal(err) 114 } 115 if diff := cmp.Diff(want, got); diff != "" { 116 t.Fatal(diff) 117 } 118 }