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  }