golang.org/x/tools@v0.21.0/internal/refactor/inline/everything_test.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package inline_test
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/parser"
    12  	"go/types"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  	"testing"
    18  
    19  	"golang.org/x/tools/go/packages"
    20  	"golang.org/x/tools/go/types/typeutil"
    21  	"golang.org/x/tools/internal/diff"
    22  	"golang.org/x/tools/internal/refactor/inline"
    23  	"golang.org/x/tools/internal/testenv"
    24  )
    25  
    26  var packagesFlag = flag.String("packages", "", "set of packages for TestEverything")
    27  
    28  // TestEverything invokes the inliner on every single call site in a
    29  // given package. and checks that it produces either a reasonable
    30  // error, or output that parses and type-checks.
    31  //
    32  // It does nothing during ordinary testing, but may be used to find
    33  // inlining bugs in large corpora.
    34  //
    35  // Use this command to inline everything in golang.org/x/tools:
    36  //
    37  // $ go test ./internal/refactor/inline/ -run=Everything -packages=../../../
    38  //
    39  // And these commands to inline everything in the kubernetes repository:
    40  //
    41  // $ go test -c -o /tmp/everything ./internal/refactor/inline/
    42  // $ (cd kubernetes && /tmp/everything -test.run=Everything -packages=./...)
    43  //
    44  // TODO(adonovan):
    45  //   - report counters (number of attempts, failed AnalyzeCallee, failed
    46  //     Inline, etc.)
    47  //   - Make a pretty log of the entire output so that we can peruse it
    48  //     for opportunities for systematic improvement.
    49  func TestEverything(t *testing.T) {
    50  	testenv.NeedsGoPackages(t)
    51  	if testing.Short() {
    52  		t.Skipf("skipping slow test in -short mode")
    53  	}
    54  	if *packagesFlag == "" {
    55  		return
    56  	}
    57  
    58  	// Load this package plus dependencies from typed syntax.
    59  	cfg := &packages.Config{
    60  		Mode: packages.LoadAllSyntax,
    61  		Env: append(os.Environ(),
    62  			"GO111MODULES=on",
    63  			"GOPATH=",
    64  			"GOWORK=off",
    65  			"GOPROXY=off"),
    66  	}
    67  	pkgs, err := packages.Load(cfg, *packagesFlag)
    68  	if err != nil {
    69  		t.Errorf("Load: %v", err)
    70  	}
    71  	// Report parse/type errors.
    72  	// Also, build transitive dependency mapping.
    73  	deps := make(map[string]*packages.Package) // key is PkgPath
    74  	packages.Visit(pkgs, nil, func(pkg *packages.Package) {
    75  		deps[pkg.Types.Path()] = pkg
    76  		for _, err := range pkg.Errors {
    77  			t.Fatal(err)
    78  		}
    79  	})
    80  
    81  	// Memoize repeated calls for same file.
    82  	fileContent := make(map[string][]byte)
    83  	readFile := func(filename string) ([]byte, error) {
    84  		content, ok := fileContent[filename]
    85  		if !ok {
    86  			var err error
    87  			content, err = os.ReadFile(filename)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  			fileContent[filename] = content
    92  		}
    93  		return content, nil
    94  	}
    95  
    96  	for _, callerPkg := range pkgs {
    97  		// Find all static function calls in the package.
    98  		for _, callerFile := range callerPkg.Syntax {
    99  			noMutCheck := checkNoMutation(callerFile)
   100  			ast.Inspect(callerFile, func(n ast.Node) bool {
   101  				call, ok := n.(*ast.CallExpr)
   102  				if !ok {
   103  					return true
   104  				}
   105  				fn := typeutil.StaticCallee(callerPkg.TypesInfo, call)
   106  				if fn == nil {
   107  					return true
   108  				}
   109  
   110  				// Prepare caller info.
   111  				callPosn := callerPkg.Fset.PositionFor(call.Lparen, false)
   112  				callerContent, err := readFile(callPosn.Filename)
   113  				if err != nil {
   114  					t.Fatal(err)
   115  				}
   116  				caller := &inline.Caller{
   117  					Fset:    callerPkg.Fset,
   118  					Types:   callerPkg.Types,
   119  					Info:    callerPkg.TypesInfo,
   120  					File:    callerFile,
   121  					Call:    call,
   122  					Content: callerContent,
   123  				}
   124  
   125  				// Analyze callee.
   126  				calleePkg, ok := deps[fn.Pkg().Path()]
   127  				if !ok {
   128  					t.Fatalf("missing package for callee %v", fn)
   129  				}
   130  				calleePosn := callerPkg.Fset.PositionFor(fn.Pos(), false)
   131  				calleeDecl, err := findFuncByPosition(calleePkg, calleePosn)
   132  				if err != nil {
   133  					t.Fatal(err)
   134  				}
   135  				calleeContent, err := readFile(calleePosn.Filename)
   136  				if err != nil {
   137  					t.Fatal(err)
   138  				}
   139  
   140  				// Create a subtest for each inlining operation.
   141  				name := fmt.Sprintf("%s@%v", fn.Name(), filepath.Base(callPosn.String()))
   142  				t.Run(name, func(t *testing.T) {
   143  					// TODO(adonovan): add a panic handler.
   144  
   145  					t.Logf("callee declared at %v",
   146  						filepath.Base(calleePosn.String()))
   147  
   148  					t.Logf("run this command to reproduce locally:\n$ gopls fix -a -d %s:#%d refactor.inline",
   149  						callPosn.Filename, callPosn.Offset)
   150  
   151  					callee, err := inline.AnalyzeCallee(
   152  						t.Logf,
   153  						calleePkg.Fset,
   154  						calleePkg.Types,
   155  						calleePkg.TypesInfo,
   156  						calleeDecl,
   157  						calleeContent)
   158  					if err != nil {
   159  						// Ignore the expected kinds of errors.
   160  						for _, ignore := range []string{
   161  							"has no body",
   162  							"type parameters are not yet",
   163  							"line directives",
   164  							"cgo-generated",
   165  						} {
   166  							if strings.Contains(err.Error(), ignore) {
   167  								return
   168  							}
   169  						}
   170  						t.Fatalf("AnalyzeCallee: %v", err)
   171  					}
   172  					if err := checkTranscode(callee); err != nil {
   173  						t.Fatal(err)
   174  					}
   175  
   176  					res, err := inline.Inline(caller, callee, &inline.Options{
   177  						Logf: t.Logf,
   178  					})
   179  					if err != nil {
   180  						// Write error to a log, but this ok.
   181  						t.Log(err)
   182  						return
   183  					}
   184  					got := res.Content
   185  
   186  					// Print the diff.
   187  					t.Logf("Got diff:\n%s",
   188  						diff.Unified("old", "new", string(callerContent), string(res.Content)))
   189  
   190  					// Parse and type-check the transformed source.
   191  					f, err := parser.ParseFile(caller.Fset, callPosn.Filename, got, parser.SkipObjectResolution)
   192  					if err != nil {
   193  						t.Fatalf("transformed source does not parse: %v", err)
   194  					}
   195  					// Splice into original file list.
   196  					syntax := append([]*ast.File(nil), callerPkg.Syntax...)
   197  					for i := range callerPkg.Syntax {
   198  						if syntax[i] == callerFile {
   199  							syntax[i] = f
   200  							break
   201  						}
   202  					}
   203  
   204  					var typeErrors []string
   205  					conf := &types.Config{
   206  						Error: func(err error) {
   207  							typeErrors = append(typeErrors, err.Error())
   208  						},
   209  						Importer: importerFunc(func(importPath string) (*types.Package, error) {
   210  							// Note: deps is properly keyed by package path,
   211  							// not import path, but we can't assume
   212  							// Package.Imports[importPath] exists in the
   213  							// case of newly added imports of indirect
   214  							// dependencies. Seems not to matter to this test.
   215  							dep, ok := deps[importPath]
   216  							if ok {
   217  								return dep.Types, nil
   218  							}
   219  							return nil, fmt.Errorf("missing package: %q", importPath)
   220  						}),
   221  					}
   222  					if _, err := conf.Check("p", caller.Fset, syntax, nil); err != nil {
   223  						t.Fatalf("transformed package has type errors:\n\n%s\n\nTransformed file:\n\n%s",
   224  							strings.Join(typeErrors, "\n"),
   225  							got)
   226  					}
   227  				})
   228  				return true
   229  			})
   230  			noMutCheck()
   231  		}
   232  	}
   233  	log.Printf("Analyzed %d packages", len(pkgs))
   234  }
   235  
   236  type importerFunc func(path string) (*types.Package, error)
   237  
   238  func (f importerFunc) Import(path string) (*types.Package, error) {
   239  	return f(path)
   240  }