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  		wg.Add(1)
   236  		go func() {
   237  			defer wg.Done()
   238  			_ = NewWalker(context, filepath.Join(testenv.GOROOT(nil), "src"))
   239  		}()
   240  	}
   241  	wg.Wait()
   242  })
   243  
   244  func TestIssue21181(t *testing.T) {
   245  	if testing.Short() {
   246  		t.Skip("skipping with -short")
   247  	}
   248  	if *flagCheck {
   249  		// slow, not worth repeating in -check
   250  		t.Skip("skipping with -check set")
   251  	}
   252  	testenv.MustHaveGoBuild(t)
   253  
   254  	warmupCache()
   255  
   256  	for _, context := range contexts {
   257  		w := NewWalker(context, "testdata/src/issue21181")
   258  		pkg, err := w.import_("p")
   259  		if err != nil {
   260  			t.Fatalf("import %s (%s-%s): %v", "p", context.GOOS, context.GOARCH, err)
   261  		}
   262  		w.export(pkg)
   263  	}
   264  }
   265  
   266  func TestIssue29837(t *testing.T) {
   267  	if testing.Short() {
   268  		t.Skip("skipping with -short")
   269  	}
   270  	if *flagCheck {
   271  		// slow, not worth repeating in -check
   272  		t.Skip("skipping with -check set")
   273  	}
   274  	testenv.MustHaveGoBuild(t)
   275  
   276  	warmupCache()
   277  
   278  	for _, context := range contexts {
   279  		w := NewWalker(context, "testdata/src/issue29837")
   280  		_, err := w.ImportFrom("p", "", 0)
   281  		if _, nogo := err.(*build.NoGoError); !nogo {
   282  			t.Errorf("expected *build.NoGoError, got %T", err)
   283  		}
   284  	}
   285  }
   286  
   287  func TestIssue41358(t *testing.T) {
   288  	if *flagCheck {
   289  		// slow, not worth repeating in -check
   290  		t.Skip("skipping with -check set")
   291  	}
   292  	testenv.MustHaveGoBuild(t)
   293  	context := new(build.Context)
   294  	*context = build.Default
   295  	context.Dir = filepath.Join(testenv.GOROOT(t), "src")
   296  
   297  	w := NewWalker(context, context.Dir)
   298  	for _, pkg := range w.stdPackages {
   299  		if strings.HasPrefix(pkg, "vendor/") || strings.HasPrefix(pkg, "golang.org/x/") {
   300  			t.Fatalf("stdPackages contains unexpected package %s", pkg)
   301  		}
   302  	}
   303  }
   304  
   305  func TestIssue64958(t *testing.T) {
   306  	if testing.Short() {
   307  		t.Skip("skipping with -short")
   308  	}
   309  	if *flagCheck {
   310  		// slow, not worth repeating in -check
   311  		t.Skip("skipping with -check set")
   312  	}
   313  	testenv.MustHaveGoBuild(t)
   314  
   315  	defer func() {
   316  		if x := recover(); x != nil {
   317  			t.Errorf("expected no panic; recovered %v", x)
   318  		}
   319  	}()
   320  	for _, context := range contexts {
   321  		w := NewWalker(context, "testdata/src/issue64958")
   322  		pkg, err := w.importFrom("p", "", 0)
   323  		if err != nil {
   324  			t.Errorf("expected no error importing; got %T", err)
   325  		}
   326  		w.export(pkg)
   327  	}
   328  }
   329  
   330  func TestCheck(t *testing.T) {
   331  	if !*flagCheck {
   332  		t.Skip("-check not specified")
   333  	}
   334  	testenv.MustHaveGoBuild(t)
   335  	Check(t)
   336  }
   337  

View as plain text