golang.org/x/tools@v0.21.0/internal/apidiff/apidiff_test.go (about)

     1  // Copyright 2019 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 apidiff
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"go/types"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  	"golang.org/x/tools/go/packages"
    19  	"golang.org/x/tools/internal/testenv"
    20  )
    21  
    22  func TestChanges(t *testing.T) {
    23  	dir, err := os.MkdirTemp("", "apidiff_test")
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	dir = filepath.Join(dir, "go")
    28  	wanti, wantc := splitIntoPackages(t, dir)
    29  	defer os.RemoveAll(dir)
    30  	sort.Strings(wanti)
    31  	sort.Strings(wantc)
    32  
    33  	oldpkg, err := load(t, "apidiff/old", dir)
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  	newpkg, err := load(t, "apidiff/new", dir)
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  
    42  	report := Changes(oldpkg.Types, newpkg.Types)
    43  
    44  	got := report.messages(false)
    45  	if diff := cmp.Diff(wanti, got); diff != "" {
    46  		t.Errorf("incompatibles (-want +got):\n%s", diff)
    47  	}
    48  	got = report.messages(true)
    49  	if diff := cmp.Diff(wantc, got); diff != "" {
    50  		t.Errorf("compatibles (-want +got):\n%s", diff)
    51  	}
    52  }
    53  
    54  func splitIntoPackages(t *testing.T, dir string) (incompatibles, compatibles []string) {
    55  	// Read the input file line by line.
    56  	// Write a line into the old or new package,
    57  	// dependent on comments.
    58  	// Also collect expected messages.
    59  	f, err := os.Open("testdata/tests.go")
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	defer f.Close()
    64  
    65  	if err := os.MkdirAll(filepath.Join(dir, "src", "apidiff"), 0700); err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	if err := os.WriteFile(filepath.Join(dir, "src", "apidiff", "go.mod"), []byte("module apidiff\n"), 0666); err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	oldd := filepath.Join(dir, "src/apidiff/old")
    73  	newd := filepath.Join(dir, "src/apidiff/new")
    74  	if err := os.MkdirAll(oldd, 0700); err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	if err := os.Mkdir(newd, 0700); err != nil && !os.IsExist(err) {
    78  		t.Fatal(err)
    79  	}
    80  
    81  	oldf, err := os.Create(filepath.Join(oldd, "old.go"))
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	newf, err := os.Create(filepath.Join(newd, "new.go"))
    86  	if err != nil {
    87  		t.Fatal(err)
    88  	}
    89  
    90  	wl := func(f *os.File, line string) {
    91  		if _, err := fmt.Fprintln(f, line); err != nil {
    92  			t.Fatal(err)
    93  		}
    94  	}
    95  	writeBoth := func(line string) { wl(oldf, line); wl(newf, line) }
    96  	writeln := writeBoth
    97  	s := bufio.NewScanner(f)
    98  	for s.Scan() {
    99  		line := s.Text()
   100  		tl := strings.TrimSpace(line)
   101  		switch {
   102  		case tl == "// old":
   103  			writeln = func(line string) { wl(oldf, line) }
   104  		case tl == "// new":
   105  			writeln = func(line string) { wl(newf, line) }
   106  		case tl == "// both":
   107  			writeln = writeBoth
   108  		case strings.HasPrefix(tl, "// i "):
   109  			incompatibles = append(incompatibles, strings.TrimSpace(tl[4:]))
   110  		case strings.HasPrefix(tl, "// c "):
   111  			compatibles = append(compatibles, strings.TrimSpace(tl[4:]))
   112  		default:
   113  			writeln(line)
   114  		}
   115  	}
   116  	if s.Err() != nil {
   117  		t.Fatal(s.Err())
   118  	}
   119  	return
   120  }
   121  
   122  func load(t *testing.T, importPath, goPath string) (*packages.Package, error) {
   123  	testenv.NeedsGoPackages(t)
   124  
   125  	cfg := &packages.Config{
   126  		Mode: packages.LoadTypes,
   127  	}
   128  	if goPath != "" {
   129  		cfg.Env = append(os.Environ(), "GOPATH="+goPath)
   130  		cfg.Dir = filepath.Join(goPath, "src", filepath.FromSlash(importPath))
   131  	}
   132  	pkgs, err := packages.Load(cfg, importPath)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	if len(pkgs[0].Errors) > 0 {
   137  		return nil, pkgs[0].Errors[0]
   138  	}
   139  	return pkgs[0], nil
   140  }
   141  
   142  func TestExportedFields(t *testing.T) {
   143  	pkg, err := load(t, "golang.org/x/tools/internal/apidiff/testdata/exported_fields", "")
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  	typeof := func(name string) types.Type {
   148  		return pkg.Types.Scope().Lookup(name).Type()
   149  	}
   150  
   151  	s := typeof("S")
   152  	su := s.(*types.Named).Underlying().(*types.Struct)
   153  
   154  	ef := exportedSelectableFields(su)
   155  	wants := []struct {
   156  		name string
   157  		typ  types.Type
   158  	}{
   159  		{"A1", typeof("A1")},
   160  		{"D", types.Typ[types.Bool]},
   161  		{"E", types.Typ[types.Int]},
   162  		{"F", typeof("F")},
   163  		{"S", types.NewPointer(s)},
   164  	}
   165  
   166  	if got, want := len(ef), len(wants); got != want {
   167  		t.Errorf("got %d fields, want %d\n%+v", got, want, ef)
   168  	}
   169  	for _, w := range wants {
   170  		if got := ef[w.name]; got != nil && !types.Identical(got.Type(), w.typ) {
   171  			t.Errorf("%s: got %v, want %v", w.name, got.Type(), w.typ)
   172  		}
   173  	}
   174  }