
This is needed for the type checker, otherwise it doesn't know which Go version it should use for type checking.
228 строки
6,2 КиБ
Go
228 строки
6,2 КиБ
Go
package cgo
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"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)
|
|
}
|
|
}
|
|
|
|
// formatDiagnostics formats the error message to be an indented comment. It
|
|
// also fixes Windows path name issues (backward slashes).
|
|
func formatDiagnostic(err error) string {
|
|
msg := err.Error()
|
|
if runtime.GOOS == "windows" {
|
|
// Fix Windows path slashes.
|
|
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
|
|
}
|
|
return "// " + msg + "\n"
|
|
}
|