mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
6.3 KiB
235 lines
6.3 KiB
package cgo
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/scanner"
|
|
"go/token"
|
|
"go/types"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Pass -update to go test to update the output of the test files.
|
|
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
|
|
|
|
// normalizeResult normalizes Go source code that comes out of tests across
|
|
// platforms and Go versions.
|
|
func normalizeResult(t *testing.T, result string) string {
|
|
result = strings.ReplaceAll(result, "\r\n", "\n")
|
|
|
|
// This changed to 'undefined:', in Go 1.20.
|
|
result = strings.ReplaceAll(result, ": undeclared name:", ": undefined:")
|
|
// Go 1.20 added a bit more detail
|
|
result = regexp.MustCompile(`(unknown field z in struct literal).*`).ReplaceAllString(result, "$1")
|
|
|
|
return result
|
|
}
|
|
|
|
func TestCGo(t *testing.T) {
|
|
var cflags = []string{"--target=armv6m-unknown-unknown-eabi"}
|
|
|
|
for _, name := range []string{
|
|
"basic",
|
|
"errors",
|
|
"types",
|
|
"symbols",
|
|
"flags",
|
|
"const",
|
|
} {
|
|
name := name // avoid a race condition
|
|
t.Run(name, func(t *testing.T) {
|
|
// Read the AST in memory.
|
|
path := filepath.Join("testdata", name+".go")
|
|
fset := token.NewFileSet()
|
|
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Fatal("could not parse Go source file:", err)
|
|
}
|
|
|
|
// Process the AST with CGo.
|
|
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
|
|
|
|
// Check the AST for type errors.
|
|
var typecheckErrors []error
|
|
config := types.Config{
|
|
Error: func(err error) {
|
|
typecheckErrors = append(typecheckErrors, err)
|
|
},
|
|
Importer: simpleImporter{},
|
|
Sizes: types.SizesFor("gccgo", "arm"),
|
|
}
|
|
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
|
|
if err != nil && len(typecheckErrors) == 0 {
|
|
// Only report errors when no type errors are found (an
|
|
// unexpected condition).
|
|
t.Error(err)
|
|
}
|
|
|
|
// Store the (formatted) output in a buffer. Format it, so it
|
|
// becomes easier to read (and will hopefully change less with CGo
|
|
// changes).
|
|
buf := &bytes.Buffer{}
|
|
if len(cgoErrors) != 0 {
|
|
buf.WriteString("// CGo errors:\n")
|
|
for _, err := range cgoErrors {
|
|
buf.WriteString(formatDiagnostic(err))
|
|
}
|
|
buf.WriteString("\n")
|
|
}
|
|
if len(typecheckErrors) != 0 {
|
|
buf.WriteString("// Type checking errors after CGo processing:\n")
|
|
for _, err := range typecheckErrors {
|
|
buf.WriteString(formatDiagnostic(err))
|
|
}
|
|
buf.WriteString("\n")
|
|
}
|
|
err = format.Node(buf, fset, cgoFiles[0])
|
|
if err != nil {
|
|
t.Errorf("could not write out CGo AST: %v", err)
|
|
}
|
|
actual := normalizeResult(t, buf.String())
|
|
|
|
// Read the file with the expected output, to compare against.
|
|
outfile := filepath.Join("testdata", name+".out.go")
|
|
expectedBytes, err := os.ReadFile(outfile)
|
|
if err != nil {
|
|
t.Fatalf("could not read expected output: %v", err)
|
|
}
|
|
expected := strings.ReplaceAll(string(expectedBytes), "\r\n", "\n")
|
|
|
|
// Check whether the output is as expected.
|
|
if expected != actual {
|
|
// It is not. Test failed.
|
|
if *flagUpdate {
|
|
// Update the file with the expected data.
|
|
err := os.WriteFile(outfile, []byte(actual), 0666)
|
|
if err != nil {
|
|
t.Error("could not write updated output file:", err)
|
|
}
|
|
return
|
|
}
|
|
t.Errorf("output did not match:\n%s", string(actual))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_cgoPackage_isEquivalentAST(t *testing.T) {
|
|
fieldA := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "a"}}
|
|
fieldB := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "b"}}
|
|
listOfFieldA := &ast.FieldList{List: []*ast.Field{fieldA}}
|
|
listOfFieldB := &ast.FieldList{List: []*ast.Field{fieldB}}
|
|
funcDeclA := &ast.FuncDecl{Name: &ast.Ident{Name: "a"}, Type: &ast.FuncType{Params: &ast.FieldList{}, Results: listOfFieldA}}
|
|
funcDeclB := &ast.FuncDecl{Name: &ast.Ident{Name: "b"}, Type: &ast.FuncType{Params: &ast.FieldList{}, Results: listOfFieldB}}
|
|
funcDeclNoResults := &ast.FuncDecl{Name: &ast.Ident{Name: "C"}, Type: &ast.FuncType{Params: &ast.FieldList{}}}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
a, b ast.Node
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "both nil",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "not same type",
|
|
a: fieldA,
|
|
b: &ast.FuncDecl{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Field same",
|
|
a: fieldA,
|
|
b: fieldA,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Field different",
|
|
a: fieldA,
|
|
b: fieldB,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "FuncDecl Type Results nil",
|
|
a: funcDeclNoResults,
|
|
b: funcDeclNoResults,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "FuncDecl Type Results same",
|
|
a: funcDeclA,
|
|
b: funcDeclA,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "FuncDecl Type Results different",
|
|
a: funcDeclA,
|
|
b: funcDeclB,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "FuncDecl Type Results a nil",
|
|
a: funcDeclNoResults,
|
|
b: funcDeclB,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "FuncDecl Type Results b nil",
|
|
a: funcDeclA,
|
|
b: funcDeclNoResults,
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
p := &cgoPackage{}
|
|
if got := p.isEquivalentAST(tc.a, tc.b); tc.expected != got {
|
|
t.Errorf("expected %v, got %v", tc.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// simpleImporter implements the types.Importer interface, but only allows
|
|
// importing the unsafe package.
|
|
type simpleImporter struct {
|
|
}
|
|
|
|
// Import implements the Importer interface. For testing usage only: it only
|
|
// supports importing the unsafe package.
|
|
func (i simpleImporter) Import(path string) (*types.Package, error) {
|
|
switch path {
|
|
case "unsafe":
|
|
return types.Unsafe, nil
|
|
default:
|
|
return nil, fmt.Errorf("importer not implemented for package %s", path)
|
|
}
|
|
}
|
|
|
|
// formatDiagnostic formats the error message to be an indented comment. It
|
|
// also fixes Windows path name issues (backward slashes).
|
|
func formatDiagnostic(err error) string {
|
|
var msg string
|
|
switch err := err.(type) {
|
|
case scanner.Error:
|
|
msg = err.Pos.String() + ": " + err.Msg
|
|
default:
|
|
msg = err.Error()
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
// Fix Windows path slashes.
|
|
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
|
|
}
|
|
return "// " + msg + "\n"
|
|
}
|
|
|