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