• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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