1
2
3
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
56
57
58
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 }
175
View as plain text