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 }