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