github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/analysis/lint/testutil/util.go (about) 1 package testutil 2 3 import ( 4 "crypto/sha256" 5 "go/build" 6 "io" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 "testing" 12 13 "github.com/amarpal/go-tools/analysis/lint" 14 "github.com/amarpal/go-tools/config" 15 "github.com/amarpal/go-tools/go/buildid" 16 "github.com/amarpal/go-tools/lintcmd/cache" 17 "github.com/amarpal/go-tools/lintcmd/runner" 18 19 "golang.org/x/tools/go/analysis" 20 "golang.org/x/tools/go/packages" 21 ) 22 23 type Test struct { 24 Dir string 25 Version string 26 } 27 28 func computeSalt() ([]byte, error) { 29 p, err := os.Executable() 30 if err != nil { 31 return nil, err 32 } 33 34 if id, err := buildid.ReadFile(p); err == nil { 35 return []byte(id), nil 36 } else { 37 // For some reason we couldn't read the build id from the executable. 38 // Fall back to hashing the entire executable. 39 f, err := os.Open(p) 40 if err != nil { 41 return nil, err 42 } 43 defer f.Close() 44 h := sha256.New() 45 if _, err := io.Copy(h, f); err != nil { 46 return nil, err 47 } 48 return h.Sum(nil), nil 49 } 50 } 51 52 func defaultGoVersion() string { 53 tags := build.Default.ReleaseTags 54 v := tags[len(tags)-1][2:] 55 return v 56 } 57 58 var testVersionRegexp = regexp.MustCompile(`^.+_go1(\d+)$`) 59 60 func Run(t *testing.T, a *lint.Analyzer) { 61 dirs, err := filepath.Glob("testdata/src/example.com/*") 62 if err != nil { 63 t.Fatalf("couldn't enumerate test data: %s", err) 64 } 65 66 if len(dirs) == 0 { 67 t.Fatalf("found no tests") 68 } 69 70 tests := make([]Test, 0, len(dirs)) 71 for _, dir := range dirs { 72 // Work around Windows paths 73 dir = strings.ReplaceAll(dir, `\`, `/`) 74 t := Test{ 75 Dir: strings.TrimPrefix(dir, "testdata/src/"), 76 } 77 if sub := testVersionRegexp.FindStringSubmatch(dir); sub != nil { 78 t.Version = "1." + sub[1] 79 } 80 tests = append(tests, t) 81 } 82 83 dirsByVersion := map[string][]string{} 84 85 // Group tests by Go version so that we can run multiple tests at once, saving time and memory on type 86 // checking and export data parsing. 87 for _, tt := range tests { 88 dirsByVersion[tt.Version] = append(dirsByVersion[tt.Version], tt.Dir) 89 } 90 91 for v, dirs := range dirsByVersion { 92 actualVersion := v 93 if actualVersion == "" { 94 actualVersion = defaultGoVersion() 95 } 96 97 c, err := cache.Open(t.TempDir()) 98 if err != nil { 99 t.Fatal(err) 100 } 101 salt, err := computeSalt() 102 if err != nil { 103 t.Fatal(err) 104 } 105 c.SetSalt(salt) 106 r, err := runner.New(config.Config{}, c) 107 if err != nil { 108 t.Fatal(err) 109 } 110 r.GoVersion = actualVersion 111 r.TestMode = true 112 113 testdata, err := filepath.Abs("testdata") 114 if err != nil { 115 t.Fatal(err) 116 } 117 cfg := &packages.Config{ 118 Tests: true, 119 Env: append(os.Environ(), "GOPATH="+testdata, "GO111MODULE=off", "GOPROXY=off"), 120 } 121 if len(dirs) == 0 { 122 t.Fatal("no directories for version", v) 123 } 124 res, err := r.Run(cfg, []*analysis.Analyzer{a.Analyzer}, dirs) 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 // resultByPath maps from import path to results 130 resultByPath := map[string][]runner.Result{} 131 failed := false 132 for _, r := range res { 133 if r.Failed { 134 failed = true 135 if len(r.Errors) > 0 { 136 t.Fatalf("failed checking %s: %v", r.Package.PkgPath, r.Errors) 137 } 138 } 139 // r.Package.PkgPath is not unique. The same path can refer to a package and a package plus its 140 // (non-external) tests. 141 resultByPath[r.Package.PkgPath] = append(resultByPath[r.Package.PkgPath], r) 142 } 143 144 if failed { 145 t.Fatal("failed processing package, but got no errors") 146 } 147 148 for _, tt := range tests { 149 if tt.Version != v { 150 continue 151 } 152 any := false 153 for _, suffix := range []string{"", ".test", "_test"} { 154 dir := tt.Dir + suffix 155 rr, ok := resultByPath[dir] 156 if !ok { 157 continue 158 } 159 any = true 160 // Remove this result. We later check that there remain no tests we haven't checked. 161 delete(resultByPath, dir) 162 163 for _, r := range rr { 164 data, err := r.Load() 165 if err != nil { 166 t.Fatal(err) 167 } 168 tdata, err := r.LoadTest() 169 if err != nil { 170 t.Fatal(err) 171 } 172 173 relevantDiags := data.Diagnostics 174 var relevantFacts []runner.TestFact 175 for _, fact := range tdata.Facts { 176 if fact.Analyzer != a.Analyzer.Name { 177 continue 178 } 179 relevantFacts = append(relevantFacts, fact) 180 } 181 182 Check(t, testdata, tdata.Files, relevantDiags, relevantFacts) 183 CheckSuggestedFixes(t, relevantDiags) 184 } 185 } 186 if !any { 187 t.Errorf("no result for directory %s", tt.Dir) 188 } 189 } 190 for key, rr := range resultByPath { 191 for _, r := range rr { 192 data, err := r.Load() 193 if err != nil { 194 t.Fatal(err) 195 } 196 if len(data.Diagnostics) != 0 { 197 t.Errorf("unexpected diagnostics in package %s", key) 198 } 199 } 200 } 201 } 202 }