golang.org/x/tools@v0.21.0/internal/apidiff/apidiff_test.go (about) 1 // Copyright 2019 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 apidiff 6 7 import ( 8 "bufio" 9 "fmt" 10 "go/types" 11 "os" 12 "path/filepath" 13 "sort" 14 "strings" 15 "testing" 16 17 "github.com/google/go-cmp/cmp" 18 "golang.org/x/tools/go/packages" 19 "golang.org/x/tools/internal/testenv" 20 ) 21 22 func TestChanges(t *testing.T) { 23 dir, err := os.MkdirTemp("", "apidiff_test") 24 if err != nil { 25 t.Fatal(err) 26 } 27 dir = filepath.Join(dir, "go") 28 wanti, wantc := splitIntoPackages(t, dir) 29 defer os.RemoveAll(dir) 30 sort.Strings(wanti) 31 sort.Strings(wantc) 32 33 oldpkg, err := load(t, "apidiff/old", dir) 34 if err != nil { 35 t.Fatal(err) 36 } 37 newpkg, err := load(t, "apidiff/new", dir) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 report := Changes(oldpkg.Types, newpkg.Types) 43 44 got := report.messages(false) 45 if diff := cmp.Diff(wanti, got); diff != "" { 46 t.Errorf("incompatibles (-want +got):\n%s", diff) 47 } 48 got = report.messages(true) 49 if diff := cmp.Diff(wantc, got); diff != "" { 50 t.Errorf("compatibles (-want +got):\n%s", diff) 51 } 52 } 53 54 func splitIntoPackages(t *testing.T, dir string) (incompatibles, compatibles []string) { 55 // Read the input file line by line. 56 // Write a line into the old or new package, 57 // dependent on comments. 58 // Also collect expected messages. 59 f, err := os.Open("testdata/tests.go") 60 if err != nil { 61 t.Fatal(err) 62 } 63 defer f.Close() 64 65 if err := os.MkdirAll(filepath.Join(dir, "src", "apidiff"), 0700); err != nil { 66 t.Fatal(err) 67 } 68 if err := os.WriteFile(filepath.Join(dir, "src", "apidiff", "go.mod"), []byte("module apidiff\n"), 0666); err != nil { 69 t.Fatal(err) 70 } 71 72 oldd := filepath.Join(dir, "src/apidiff/old") 73 newd := filepath.Join(dir, "src/apidiff/new") 74 if err := os.MkdirAll(oldd, 0700); err != nil { 75 t.Fatal(err) 76 } 77 if err := os.Mkdir(newd, 0700); err != nil && !os.IsExist(err) { 78 t.Fatal(err) 79 } 80 81 oldf, err := os.Create(filepath.Join(oldd, "old.go")) 82 if err != nil { 83 t.Fatal(err) 84 } 85 newf, err := os.Create(filepath.Join(newd, "new.go")) 86 if err != nil { 87 t.Fatal(err) 88 } 89 90 wl := func(f *os.File, line string) { 91 if _, err := fmt.Fprintln(f, line); err != nil { 92 t.Fatal(err) 93 } 94 } 95 writeBoth := func(line string) { wl(oldf, line); wl(newf, line) } 96 writeln := writeBoth 97 s := bufio.NewScanner(f) 98 for s.Scan() { 99 line := s.Text() 100 tl := strings.TrimSpace(line) 101 switch { 102 case tl == "// old": 103 writeln = func(line string) { wl(oldf, line) } 104 case tl == "// new": 105 writeln = func(line string) { wl(newf, line) } 106 case tl == "// both": 107 writeln = writeBoth 108 case strings.HasPrefix(tl, "// i "): 109 incompatibles = append(incompatibles, strings.TrimSpace(tl[4:])) 110 case strings.HasPrefix(tl, "// c "): 111 compatibles = append(compatibles, strings.TrimSpace(tl[4:])) 112 default: 113 writeln(line) 114 } 115 } 116 if s.Err() != nil { 117 t.Fatal(s.Err()) 118 } 119 return 120 } 121 122 func load(t *testing.T, importPath, goPath string) (*packages.Package, error) { 123 testenv.NeedsGoPackages(t) 124 125 cfg := &packages.Config{ 126 Mode: packages.LoadTypes, 127 } 128 if goPath != "" { 129 cfg.Env = append(os.Environ(), "GOPATH="+goPath) 130 cfg.Dir = filepath.Join(goPath, "src", filepath.FromSlash(importPath)) 131 } 132 pkgs, err := packages.Load(cfg, importPath) 133 if err != nil { 134 return nil, err 135 } 136 if len(pkgs[0].Errors) > 0 { 137 return nil, pkgs[0].Errors[0] 138 } 139 return pkgs[0], nil 140 } 141 142 func TestExportedFields(t *testing.T) { 143 pkg, err := load(t, "golang.org/x/tools/internal/apidiff/testdata/exported_fields", "") 144 if err != nil { 145 t.Fatal(err) 146 } 147 typeof := func(name string) types.Type { 148 return pkg.Types.Scope().Lookup(name).Type() 149 } 150 151 s := typeof("S") 152 su := s.(*types.Named).Underlying().(*types.Struct) 153 154 ef := exportedSelectableFields(su) 155 wants := []struct { 156 name string 157 typ types.Type 158 }{ 159 {"A1", typeof("A1")}, 160 {"D", types.Typ[types.Bool]}, 161 {"E", types.Typ[types.Int]}, 162 {"F", typeof("F")}, 163 {"S", types.NewPointer(s)}, 164 } 165 166 if got, want := len(ef), len(wants); got != want { 167 t.Errorf("got %d fields, want %d\n%+v", got, want, ef) 168 } 169 for _, w := range wants { 170 if got := ef[w.name]; got != nil && !types.Identical(got.Type(), w.typ) { 171 t.Errorf("%s: got %v, want %v", w.name, got.Type(), w.typ) 172 } 173 } 174 }