github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/linkname_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"go/ast"
     5  	"go/importer"
     6  	"go/parser"
     7  	"go/token"
     8  	"go/types"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/google/go-cmp/cmp/cmpopts"
    14  	"github.com/gopherjs/gopherjs/compiler/internal/symbol"
    15  )
    16  
    17  func parseSource(t *testing.T, src string) (*ast.File, *token.FileSet) {
    18  	t.Helper()
    19  
    20  	const filename = "<src>"
    21  	fset := token.NewFileSet()
    22  
    23  	file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
    24  	if err != nil {
    25  		t.Log(src)
    26  		t.Fatalf("Failed to parse source code: %s", err)
    27  	}
    28  	return file, fset
    29  }
    30  
    31  func makePackage(t *testing.T, src string) *types.Package {
    32  	t.Helper()
    33  
    34  	file, fset := parseSource(t, src)
    35  	conf := types.Config{Importer: importer.Default()}
    36  	pkg, err := conf.Check(file.Name.Name, fset, []*ast.File{file}, nil)
    37  	if err != nil {
    38  		t.Log(src)
    39  		t.Fatalf("Failed to type check source code: %s", err)
    40  	}
    41  
    42  	return pkg
    43  }
    44  
    45  func TestParseGoLinknames(t *testing.T) {
    46  	tests := []struct {
    47  		desc           string
    48  		src            string
    49  		wantError      string
    50  		wantDirectives []GoLinkname
    51  	}{
    52  		{
    53  			desc: "no directives",
    54  			src: `package testcase
    55  			
    56  			// This comment doesn't start with go:linkname
    57  			func a() {}
    58  			// go:linkname directive must have no space between the slash and the directive.
    59  			func b() {}
    60  			// An example in the middle of a comment is also not a directive: //go:linkname foo bar.baz
    61  			func c() {}
    62  			`,
    63  			wantDirectives: []GoLinkname{},
    64  		}, {
    65  			desc: "normal use case",
    66  			src: `package testcase
    67  
    68  			import _ "unsafe"
    69  			
    70  			//go:linkname a other/package.testcase_a
    71  			func a()
    72  			`,
    73  			wantDirectives: []GoLinkname{
    74  				{
    75  					Reference:      symbol.Name{PkgPath: "testcase", Name: "a"},
    76  					Implementation: symbol.Name{PkgPath: "other/package", Name: "testcase_a"},
    77  				},
    78  			},
    79  		}, {
    80  			desc: "multiple directives in one comment group",
    81  			src: `package testcase
    82  			import _ "unsafe"
    83  
    84  			// The following functions are implemented elsewhere:
    85  			//go:linkname a other/package.a
    86  			//go:linkname b other/package.b
    87  
    88  			func a()
    89  			func b()
    90  			`,
    91  			wantDirectives: []GoLinkname{
    92  				{
    93  					Reference:      symbol.Name{PkgPath: "testcase", Name: "a"},
    94  					Implementation: symbol.Name{PkgPath: "other/package", Name: "a"},
    95  				}, {
    96  					Reference:      symbol.Name{PkgPath: "testcase", Name: "b"},
    97  					Implementation: symbol.Name{PkgPath: "other/package", Name: "b"},
    98  				},
    99  			},
   100  		}, {
   101  			desc: "unsafe not imported",
   102  			src: `package testcase
   103  			
   104  			//go:linkname a other/package.a
   105  			func a()
   106  			`,
   107  			wantError: `import "unsafe"`,
   108  		}, {
   109  			desc: "gopherjs: both parameters are required",
   110  			src: `package testcase
   111  			
   112  			import _ "unsafe"
   113  
   114  			//go:linkname a
   115  			func a()
   116  			`,
   117  			wantError: "usage",
   118  		}, {
   119  			desc: "referenced function doesn't exist",
   120  			src: `package testcase
   121  			
   122  			import _ "unsafe"
   123  
   124  			//go:linkname b other/package.b
   125  			func a()
   126  			`,
   127  			wantError: `"b" is not found`,
   128  		}, {
   129  			desc: "gopherjs: referenced a variable, not a function",
   130  			src: `package testcase
   131  			
   132  			import _ "unsafe"
   133  
   134  			//go:linkname a other/package.a
   135  			var a string = "foo"
   136  			`,
   137  			wantError: `is only supported for functions`,
   138  		}, {
   139  			desc: "gopherjs: can not insert local implementation",
   140  			src: `package testcase
   141  			
   142  			import _ "unsafe"
   143  
   144  			//go:linkname a other/package.a
   145  			func a() { println("do a") }
   146  			`,
   147  			wantError: `can not insert local implementation`,
   148  		},
   149  	}
   150  
   151  	for _, test := range tests {
   152  		t.Run(test.desc, func(t *testing.T) {
   153  			file, fset := parseSource(t, test.src)
   154  			directives, err := parseGoLinknames(fset, "testcase", file)
   155  
   156  			if test.wantError != "" {
   157  				if err == nil {
   158  					t.Fatalf("ParseGoLinknames() returned no error, want: %s.", test.wantError)
   159  				} else if !strings.Contains(err.Error(), test.wantError) {
   160  					t.Fatalf("ParseGoLinknames() returned error: %s. Want an error containing %q.", err, test.wantError)
   161  				}
   162  				return
   163  			}
   164  
   165  			if err != nil {
   166  				t.Fatalf("ParseGoLinkanmes() returned error: %s. Want: no error.", err)
   167  			}
   168  
   169  			if diff := cmp.Diff(test.wantDirectives, directives, cmpopts.EquateEmpty()); diff != "" {
   170  				t.Fatalf("ParseGoLinknames() returned diff (-want,+got):\n%s", diff)
   171  			}
   172  		})
   173  	}
   174  }