Source file src/cmd/api/api_test.go

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

View as plain text