/cgo/cgo_test.go

https://github.com/tinygo-org/tinygo · Go · 145 lines · 104 code · 15 blank · 26 comment · 25 complexity · bc7534c2c0f71805ad7e4c291ddbaca8 MD5 · raw file

  1. package cgo
  2. import (
  3. "bytes"
  4. "flag"
  5. "fmt"
  6. "go/ast"
  7. "go/format"
  8. "go/parser"
  9. "go/token"
  10. "go/types"
  11. "io/ioutil"
  12. "path/filepath"
  13. "regexp"
  14. "runtime"
  15. "strings"
  16. "testing"
  17. )
  18. // Pass -update to go test to update the output of the test files.
  19. var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
  20. // normalizeResult normalizes Go source code that comes out of tests across
  21. // platforms and Go versions.
  22. func normalizeResult(result string) string {
  23. actual := strings.Replace(result, "\r\n", "\n", -1)
  24. // Make sure all functions are wrapped, even those that would otherwise be
  25. // single-line functions. This is necessary because Go 1.14 changed the way
  26. // such functions are wrapped and it's important to have consistent test
  27. // results.
  28. re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
  29. actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
  30. return actual
  31. }
  32. func TestCGo(t *testing.T) {
  33. var cflags = []string{"--target=armv6m-none-eabi"}
  34. for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
  35. name := name // avoid a race condition
  36. t.Run(name, func(t *testing.T) {
  37. // Read the AST in memory.
  38. path := filepath.Join("testdata", name+".go")
  39. fset := token.NewFileSet()
  40. f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
  41. if err != nil {
  42. t.Fatal("could not parse Go source file:", err)
  43. }
  44. // Process the AST with CGo.
  45. cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
  46. // Check the AST for type errors.
  47. var typecheckErrors []error
  48. config := types.Config{
  49. Error: func(err error) {
  50. typecheckErrors = append(typecheckErrors, err)
  51. },
  52. Importer: simpleImporter{},
  53. Sizes: types.SizesFor("gccgo", "arm"),
  54. }
  55. _, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
  56. if err != nil && len(typecheckErrors) == 0 {
  57. // Only report errors when no type errors are found (an
  58. // unexpected condition).
  59. t.Error(err)
  60. }
  61. // Store the (formatted) output in a buffer. Format it, so it
  62. // becomes easier to read (and will hopefully change less with CGo
  63. // changes).
  64. buf := &bytes.Buffer{}
  65. if len(cgoErrors) != 0 {
  66. buf.WriteString("// CGo errors:\n")
  67. for _, err := range cgoErrors {
  68. buf.WriteString(formatDiagnostic(err))
  69. }
  70. buf.WriteString("\n")
  71. }
  72. if len(typecheckErrors) != 0 {
  73. buf.WriteString("// Type checking errors after CGo processing:\n")
  74. for _, err := range typecheckErrors {
  75. buf.WriteString(formatDiagnostic(err))
  76. }
  77. buf.WriteString("\n")
  78. }
  79. err = format.Node(buf, fset, cgoAST)
  80. if err != nil {
  81. t.Errorf("could not write out CGo AST: %v", err)
  82. }
  83. actual := normalizeResult(string(buf.Bytes()))
  84. // Read the file with the expected output, to compare against.
  85. outfile := filepath.Join("testdata", name+".out.go")
  86. expectedBytes, err := ioutil.ReadFile(outfile)
  87. if err != nil {
  88. t.Fatalf("could not read expected output: %v", err)
  89. }
  90. expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
  91. // Check whether the output is as expected.
  92. if expected != actual {
  93. // It is not. Test failed.
  94. if *flagUpdate {
  95. // Update the file with the expected data.
  96. err := ioutil.WriteFile(outfile, []byte(actual), 0666)
  97. if err != nil {
  98. t.Error("could not write updated output file:", err)
  99. }
  100. return
  101. }
  102. t.Errorf("output did not match:\n%s", string(actual))
  103. }
  104. })
  105. }
  106. }
  107. // simpleImporter implements the types.Importer interface, but only allows
  108. // importing the unsafe package.
  109. type simpleImporter struct {
  110. }
  111. // Import implements the Importer interface. For testing usage only: it only
  112. // supports importing the unsafe package.
  113. func (i simpleImporter) Import(path string) (*types.Package, error) {
  114. switch path {
  115. case "unsafe":
  116. return types.Unsafe, nil
  117. default:
  118. return nil, fmt.Errorf("importer not implemented for package %s", path)
  119. }
  120. }
  121. // formatDiagnostics formats the error message to be an indented comment. It
  122. // also fixes Windows path name issues (backward slashes).
  123. func formatDiagnostic(err error) string {
  124. msg := err.Error()
  125. if runtime.GOOS == "windows" {
  126. // Fix Windows path slashes.
  127. msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
  128. }
  129. return "// " + msg + "\n"
  130. }