wa/internal/app/apptest/apptest.go

341 lines
7.8 KiB
Go

// 版权 @2023 凹语言 作者。保留所有权利。
package apptest
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"wa-lang.org/wa/internal/3rdparty/cli"
"wa-lang.org/wa/internal/app/appbase"
"wa-lang.org/wa/internal/backends/compiler_wat"
"wa-lang.org/wa/internal/config"
"wa-lang.org/wa/internal/loader"
"wa-lang.org/wa/internal/wabt"
"wa-lang.org/wa/internal/wazero"
"wa-lang.org/wa/waroot"
)
var CmdTest = &cli.Command{
Name: "test",
Usage: "test Wa packages",
Flags: []cli.Flag{
appbase.MakeFlag_target(),
appbase.MakeFlag_tags(),
&cli.StringFlag{
Name: "run",
Usage: "set run func file name pattern",
Value: "",
},
},
Action: func(c *cli.Context) error {
opt := appbase.BuildOptions(c)
var pkgpath = "."
var runPattern = c.String("run")
var appArgs []string
if c.Args().Len() > 0 {
pkgpath = c.Args().First()
}
if c.Args().Len() > 1 {
appArgs = c.Args().Slice()[1:]
}
RunTest(opt.Config(), pkgpath, runPattern, appArgs...)
return nil
},
}
func RunTest(cfg *config.Config, pkgpath, runPattern string, appArgs ...string) {
var pkgList = []string{pkgpath}
if pkgpath == "std" {
pkgList = waroot.GetStdPkgList()
}
for _, p := range pkgList {
runTest(cfg, p, runPattern, appArgs...)
}
}
func runTest(cfg *config.Config, pkgpath, runPattern string, appArgs ...string) {
cfg.UnitTest = true
prog, err := loader.LoadProgram(cfg, pkgpath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
startTime := time.Now()
mainPkg := prog.Pkgs[prog.Manifest.MainPkg]
wasmName := "unittest://" + pkgpath
wasmArgs := []string{}
if len(mainPkg.TestInfo.Files) == 0 {
fmt.Printf("? %s [no test files]\n", prog.Manifest.MainPkg)
return
}
// 生成 wat 文件(main 函数为空)
watOutput, err := compiler_wat.New().Compile(prog, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// wat 落盘(仅用于调试)
os.WriteFile("a.out.wat", []byte(watOutput), 0666)
// 编译为 wasm
wasmBytes, err := wabt.Wat2Wasm([]byte(watOutput))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
m, err := wazero.BuildModule(cfg, wasmName, wasmBytes, wasmArgs...)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer m.Close()
// 执行测试函数
var firstError error
for i := 0; i < len(mainPkg.TestInfo.Tests); i++ {
t := mainPkg.TestInfo.Tests[i]
if runPattern != "" {
matched, err := filepath.Match(runPattern, t.Name)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !matched {
continue
}
}
tFuncName := mainPkg.Pkg.Path() + "." + t.Name
tFuncName = strings.ReplaceAll(tFuncName, "/", "$")
_, stdout, stderr, err := m.RunFunc(tFuncName)
if t.OutputPanic {
stdout = fmtGotOutput(stdout)
expect, got := t.Output, string(stdout)
if exitCode, _ := wazero.AsExitError(err); exitCode == 0 {
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
fmt.Printf(" expect panic, got = nil\n")
if firstError == nil {
firstError = fmt.Errorf("expect(panic) = %q, got = %q", expect, "nil")
}
continue
}
if !strings.HasPrefix(got, "panic: "+expect) { // panic: ${expect} (pos)
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
fmt.Printf(" expect(panic) = %q, got = %q\n", expect, got)
if firstError == nil {
firstError = fmt.Errorf("expect(panic) = %q, got = %q", expect, got)
}
}
// 重新加载
{
m, err = wazero.BuildModule(cfg, wasmName, wasmBytes, wasmArgs...)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// 临时方案: defer 太多
defer m.Close()
}
continue
}
if err != nil {
if len(stdout) > 0 {
if s := sWithPrefix(string(stdout), " "); s != "" {
fmt.Println(s)
}
}
if len(stderr) > 0 {
if s := sWithPrefix(string(stderr), " "); s != "" {
fmt.Println(s)
}
} else {
if s := sWithPrefix(err.Error(), " "); s != "" {
fmt.Println(s)
}
}
os.Exit(1)
}
stdout = fmtGotOutput(stdout)
if t.Output != "" && t.Output == string(stdout) {
continue
}
if err != nil {
if firstError == nil {
firstError = err
}
if _, ok := wazero.AsExitError(err); ok {
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
if s := sWithPrefix(string(stdout), " "); s != "" {
fmt.Println(s)
}
if s := sWithPrefix(string(stderr), " "); s != "" {
fmt.Println(s)
}
} else {
fmt.Println(err)
}
}
if t.Output != "" {
if expect, got := t.Output, string(stdout); expect != got {
if firstError == nil {
firstError = fmt.Errorf("expect = %q, got = %q", expect, got)
}
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
fmt.Printf(" expect = %q, got = %q\n", expect, got)
}
}
}
for i := 0; i < len(mainPkg.TestInfo.Examples); i++ {
t := mainPkg.TestInfo.Examples[i]
if runPattern != "" {
matched, err := filepath.Match(runPattern, t.Name)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !matched {
continue
}
}
tFuncName := mainPkg.Pkg.Path() + "." + t.Name
tFuncName = strings.ReplaceAll(tFuncName, "/", "$")
_, stdout, stderr, err := m.RunFunc(tFuncName)
if t.OutputPanic {
stdout = fmtGotOutput(stdout)
expect, got := t.Output, string(stdout)
if exitCode, _ := wazero.AsExitError(err); exitCode == 0 {
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
fmt.Printf(" expect panic, got = nil\n")
if firstError == nil {
firstError = fmt.Errorf("expect panic, got = nil")
}
continue
}
if !strings.HasPrefix(got, "panic: "+expect) { // panic: ${expect} (pos)
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
fmt.Printf(" expect(panic) = %q, got = %q\n", expect, got)
if firstError == nil {
firstError = fmt.Errorf("expect(panic) = %q, got = %q", expect, got)
}
}
// 重新加载
{
m, err = wazero.BuildModule(cfg, wasmName, wasmBytes, wasmArgs...)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// 临时方案: defer 太多
defer m.Close()
}
continue
}
if err != nil {
if len(stdout) > 0 {
if s := sWithPrefix(string(stdout), " "); s != "" {
fmt.Println(s)
}
}
if len(stderr) > 0 {
if s := sWithPrefix(string(stderr), " "); s != "" {
fmt.Println(s)
}
}
os.Exit(1)
}
stdout = fmtGotOutput(stdout)
if t.Output != "" && t.Output == string(stdout) {
continue
}
if err != nil {
if firstError == nil {
firstError = err
}
if _, ok := wazero.AsExitError(err); ok {
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
if s := sWithPrefix(string(stdout), " "); s != "" {
fmt.Println(s)
}
if s := sWithPrefix(string(stderr), " "); s != "" {
fmt.Println(s)
}
} else {
fmt.Println(err)
}
}
if t.Output != "" {
if expect, got := t.Output, string(stdout); expect != got {
if firstError == nil {
firstError = fmt.Errorf("expect = %q, got = %q", expect, got)
}
fmt.Printf("---- %s.%s\n", prog.Manifest.MainPkg, t.Name)
fmt.Printf(" expect = %q, got = %q\n", expect, got)
}
}
}
if firstError != nil {
fmt.Printf("FAIL %s %v\n", prog.Manifest.MainPkg, time.Now().Sub(startTime).Round(time.Millisecond))
os.Exit(1)
}
fmt.Printf("ok %s %v\n", prog.Manifest.MainPkg, time.Now().Sub(startTime).Round(time.Millisecond))
return
}
func sWithPrefix(s, prefix string) string {
lines := strings.Split(strings.TrimSpace(s), "\n")
for i, line := range lines {
lines[i] = prefix + strings.TrimSpace(line)
}
return strings.Join(lines, "\n")
}
func fmtGotOutput(stdout []byte) []byte {
stdout = bytes.TrimSpace(stdout)
lines := bytes.Split(stdout, []byte("\n"))
for i, s := range lines {
lines[i] = bytes.TrimSpace(s)
}
return bytes.Join(lines, []byte("\n"))
}