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 }