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  	"sort"
    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, _ := 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  
    98  func 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  
   179  func 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  
   199  func 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  
   212  var 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  
   226  func 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  
   249  func 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  
   270  func 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  
   288  func 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  
   307  func TestCheck(t *testing.T) {
   308  	if !*flagCheck {
   309  		t.Skip("-check not specified")
   310  	}
   311  	testenv.MustHaveGoBuild(t)
   312  	Check(t)
   313  }
   314  

View as plain text