1// Copyright 2011 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 5package main 6 7import ( 8 "flag" 9 "fmt" 10 "go/build" 11 "internal/testenv" 12 "os" 13 "path/filepath" 14 "sort" 15 "strings" 16 "sync" 17 "testing" 18) 19 20var flagCheck = flag.Bool("check", false, "run API checks") 21 22func TestMain(m *testing.M) { 23 flag.Parse() 24 for _, c := range contexts { 25 c.Compiler = build.Default.Compiler 26 } 27 build.Default.GOROOT = testenv.GOROOT(nil) 28 29 os.Exit(m.Run()) 30} 31 32var ( 33 updateGolden = flag.Bool("updategolden", false, "update golden files") 34) 35 36func TestGolden(t *testing.T) { 37 if *flagCheck { 38 // slow, not worth repeating in -check 39 t.Skip("skipping with -check set") 40 } 41 42 testenv.MustHaveGoBuild(t) 43 44 td, err := os.Open("testdata/src/pkg") 45 if err != nil { 46 t.Fatal(err) 47 } 48 fis, err := td.Readdir(0) 49 if err != nil { 50 t.Fatal(err) 51 } 52 for _, fi := range fis { 53 if !fi.IsDir() { 54 continue 55 } 56 57 // TODO(gri) remove extra pkg directory eventually 58 goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt") 59 w := NewWalker(nil, "testdata/src/pkg") 60 pkg, _ := w.import_(fi.Name()) 61 w.export(pkg) 62 63 if *updateGolden { 64 os.Remove(goldenFile) 65 f, err := os.Create(goldenFile) 66 if err != nil { 67 t.Fatal(err) 68 } 69 for _, feat := range w.Features() { 70 fmt.Fprintf(f, "%s\n", feat) 71 } 72 f.Close() 73 } 74 75 bs, err := os.ReadFile(goldenFile) 76 if err != nil { 77 t.Fatalf("opening golden.txt for package %q: %v", fi.Name(), err) 78 } 79 wanted := strings.Split(string(bs), "\n") 80 sort.Strings(wanted) 81 for _, feature := range wanted { 82 if feature == "" { 83 continue 84 } 85 _, ok := w.features[feature] 86 if !ok { 87 t.Errorf("package %s: missing feature %q", fi.Name(), feature) 88 } 89 delete(w.features, feature) 90 } 91 92 for _, feature := range w.Features() { 93 t.Errorf("package %s: extra feature not in golden file: %q", fi.Name(), feature) 94 } 95 } 96} 97 98func TestCompareAPI(t *testing.T) { 99 tests := []struct { 100 name string 101 features, required, exception []string 102 ok bool // want 103 out string // want 104 }{ 105 { 106 name: "equal", 107 features: []string{"A", "B", "C"}, 108 required: []string{"A", "B", "C"}, 109 ok: true, 110 out: "", 111 }, 112 { 113 name: "feature added", 114 features: []string{"A", "B", "C", "D", "E", "F"}, 115 required: []string{"B", "D"}, 116 ok: false, 117 out: "+A\n+C\n+E\n+F\n", 118 }, 119 { 120 name: "feature removed", 121 features: []string{"C", "A"}, 122 required: []string{"A", "B", "C"}, 123 ok: false, 124 out: "-B\n", 125 }, 126 { 127 name: "exception removal", 128 features: []string{"A", "C"}, 129 required: []string{"A", "B", "C"}, 130 exception: []string{"B"}, 131 ok: true, 132 out: "", 133 }, 134 135 // Test that a feature required on a subset of ports is implicitly satisfied 136 // by the same feature being implemented on all ports. That is, it shouldn't 137 // say "pkg syscall (darwin-amd64), type RawSockaddrInet6 struct" is missing. 138 // See https://go.dev/issue/4303. 139 { 140 name: "contexts reconverging after api/next/* update", 141 features: []string{ 142 "A", 143 "pkg syscall, type RawSockaddrInet6 struct", 144 }, 145 required: []string{ 146 "A", 147 "pkg syscall (darwin-amd64), type RawSockaddrInet6 struct", // api/go1.n.txt 148 "pkg syscall, type RawSockaddrInet6 struct", // api/next/n.txt 149 }, 150 ok: true, 151 out: "", 152 }, 153 { 154 name: "contexts reconverging before api/next/* update", 155 features: []string{ 156 "A", 157 "pkg syscall, type RawSockaddrInet6 struct", 158 }, 159 required: []string{ 160 "A", 161 "pkg syscall (darwin-amd64), type RawSockaddrInet6 struct", 162 }, 163 ok: false, 164 out: "+pkg syscall, type RawSockaddrInet6 struct\n", 165 }, 166 } 167 for _, tt := range tests { 168 buf := new(strings.Builder) 169 gotOK := compareAPI(buf, tt.features, tt.required, tt.exception) 170 if gotOK != tt.ok { 171 t.Errorf("%s: ok = %v; want %v", tt.name, gotOK, tt.ok) 172 } 173 if got := buf.String(); got != tt.out { 174 t.Errorf("%s: output differs\nGOT:\n%s\nWANT:\n%s", tt.name, got, tt.out) 175 } 176 } 177} 178 179func TestSkipInternal(t *testing.T) { 180 tests := []struct { 181 pkg string 182 want bool 183 }{ 184 {"net/http", true}, 185 {"net/http/internal-foo", true}, 186 {"net/http/internal", false}, 187 {"net/http/internal/bar", false}, 188 {"internal/foo", false}, 189 {"internal", false}, 190 } 191 for _, tt := range tests { 192 got := !internalPkg.MatchString(tt.pkg) 193 if got != tt.want { 194 t.Errorf("%s is internal = %v; want %v", tt.pkg, got, tt.want) 195 } 196 } 197} 198 199func BenchmarkAll(b *testing.B) { 200 for i := 0; i < b.N; i++ { 201 for _, context := range contexts { 202 w := NewWalker(context, filepath.Join(testenv.GOROOT(b), "src")) 203 for _, name := range w.stdPackages { 204 pkg, _ := w.import_(name) 205 w.export(pkg) 206 } 207 w.Features() 208 } 209 } 210} 211 212var warmupCache = sync.OnceFunc(func() { 213 // Warm up the import cache in parallel. 214 var wg sync.WaitGroup 215 for _, context := range contexts { 216 context := context 217 wg.Add(1) 218 go func() { 219 defer wg.Done() 220 _ = NewWalker(context, filepath.Join(testenv.GOROOT(nil), "src")) 221 }() 222 } 223 wg.Wait() 224}) 225 226func TestIssue21181(t *testing.T) { 227 if testing.Short() { 228 t.Skip("skipping with -short") 229 } 230 if *flagCheck { 231 // slow, not worth repeating in -check 232 t.Skip("skipping with -check set") 233 } 234 testenv.MustHaveGoBuild(t) 235 236 warmupCache() 237 238 for _, context := range contexts { 239 w := NewWalker(context, "testdata/src/issue21181") 240 pkg, err := w.import_("p") 241 if err != nil { 242 t.Fatalf("%s: (%s-%s) %s %v", err, context.GOOS, context.GOARCH, 243 pkg.Name(), w.imported) 244 } 245 w.export(pkg) 246 } 247} 248 249func TestIssue29837(t *testing.T) { 250 if testing.Short() { 251 t.Skip("skipping with -short") 252 } 253 if *flagCheck { 254 // slow, not worth repeating in -check 255 t.Skip("skipping with -check set") 256 } 257 testenv.MustHaveGoBuild(t) 258 259 warmupCache() 260 261 for _, context := range contexts { 262 w := NewWalker(context, "testdata/src/issue29837") 263 _, err := w.ImportFrom("p", "", 0) 264 if _, nogo := err.(*build.NoGoError); !nogo { 265 t.Errorf("expected *build.NoGoError, got %T", err) 266 } 267 } 268} 269 270func TestIssue41358(t *testing.T) { 271 if *flagCheck { 272 // slow, not worth repeating in -check 273 t.Skip("skipping with -check set") 274 } 275 testenv.MustHaveGoBuild(t) 276 context := new(build.Context) 277 *context = build.Default 278 context.Dir = filepath.Join(testenv.GOROOT(t), "src") 279 280 w := NewWalker(context, context.Dir) 281 for _, pkg := range w.stdPackages { 282 if strings.HasPrefix(pkg, "vendor/") || strings.HasPrefix(pkg, "golang.org/x/") { 283 t.Fatalf("stdPackages contains unexpected package %s", pkg) 284 } 285 } 286} 287 288func TestIssue64958(t *testing.T) { 289 defer func() { 290 if x := recover(); x != nil { 291 t.Errorf("expected no panic; recovered %v", x) 292 } 293 }() 294 295 testenv.MustHaveGoBuild(t) 296 297 for _, context := range contexts { 298 w := NewWalker(context, "testdata/src/issue64958") 299 pkg, err := w.importFrom("p", "", 0) 300 if err != nil { 301 t.Errorf("expected no error importing; got %T", err) 302 } 303 w.export(pkg) 304 } 305} 306 307func TestCheck(t *testing.T) { 308 if !*flagCheck { 309 t.Skip("-check not specified") 310 } 311 testenv.MustHaveGoBuild(t) 312 Check(t) 313} 314