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  }