land/vendor/github.com/go-interpreter/wagon/exec/exec_test.go

403 lines
8.3 KiB
Go

// Copyright 2017 The go-interpreter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package exec_test
import (
"encoding/json"
"fmt"
"math"
"math/big"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"testing"
"github.com/go-interpreter/wagon/exec"
"github.com/go-interpreter/wagon/validate"
"github.com/go-interpreter/wagon/wasm"
)
const (
nonSpecTestsDir = "./testdata"
specTestsDir = "./testdata/spec"
)
type testCase struct {
Function string `json:"function"`
Args []string `json:"args"`
Return string `json:"return"`
Trap string `json:"trap"`
RecoverPanic bool `json:"recoverpanic,omitempty"`
ErrorMsg string `json:"errormsg"`
}
type file struct {
FileName string `json:"file"`
Tests []testCase `json:"tests"`
}
var reValue = regexp.MustCompile(`(.+)\:(.+)`)
func parseFloat(str string, bitSize int) float64 {
hexFloat, err := regexp.MatchString("0x", str)
if err != nil {
panic(err)
}
isNanOrInf, err := regexp.MatchString("(nan|inf)", str)
if err != nil {
panic(err)
}
if isNanOrInf {
if strings.HasPrefix(str, "-") {
str = strings.TrimPrefix(str, "-")
if str == "inf" {
return math.Inf(-1)
} else if str == "nan" {
return math.NaN()
}
}
if str == "inf" {
return math.Inf(+1)
} else if str == "nan" {
return math.NaN()
}
}
if hexFloat {
if strings.HasPrefix(str, "-0x") {
str = strings.TrimPrefix(str, "-0x")
if str == "inf" {
return math.Inf(-1)
}
str = "-" + str
} else {
if str == "inf" {
return math.Inf(+1)
} else if str == "nan" {
return math.NaN()
}
str = strings.TrimPrefix(str, "0x")
}
f, _, err := big.ParseFloat(str, 16, big.MaxPrec, big.ToNearestEven)
if err != nil {
panic(err)
}
n, _ := f.Float64()
return n
}
n, err := strconv.ParseFloat(str, bitSize)
if err != nil {
panic(err)
}
return n
}
func TestParseFloat(t *testing.T) {
tests := []struct {
str string
want float64
}{
{"0xf32", 3890.0},
{"3.4", 3.4},
{"0.0", 0.0},
{"-123.0", -123.0},
{"0x1.fffffep+127", 340282346638528859811704183484516925440.0},
{"nan", math.NaN()},
{"inf", math.Inf(+1)},
{"-inf", math.Inf(-1)},
}
for _, test := range tests {
f := parseFloat(test.str, 64)
if f != test.want && !(math.IsNaN(test.want) && math.IsNaN(f)) {
t.Errorf("unexpected value returned by parseFloat: got=%f want=%f", f, test.want)
}
}
}
func parseInt(str string, bitSize int) uint64 {
isHex, _ := regexp.MatchString("0x", str)
base := 10
if isHex {
if strings.HasPrefix(str, "-") {
str = strings.TrimPrefix(str, "-0x")
str = "-" + str
} else {
str = strings.TrimPrefix(str, "0x")
}
base = 16
}
n, err := strconv.ParseUint(str, base, bitSize)
if err != nil {
// try parsing as an int
n2, err := strconv.ParseInt(str, base, bitSize)
if err != nil {
panic(err)
}
n = uint64(n2)
}
return n
}
func TestParseInt(t *testing.T) {
tests := []struct {
str string
want uint64
}{
{"45", 45},
{"0", 0},
{"0xABADCAFEDEAD1DEA", 0xABADCAFEDEAD1DEA},
{"-1", 18446744073709551615},
}
for _, test := range tests {
i := parseInt(test.str, 64)
if i != test.want {
t.Errorf("unexpected value returned by parseInt: got=%d want=%d", i, test.want)
}
}
}
func parseValue(str string) interface{} {
if str == "" {
return nil
}
matches := reValue.FindStringSubmatch(str)
if matches == nil {
panic("Invalid value expression: " + str)
}
switch matches[1] {
case "i32":
n := parseInt(matches[2], 32)
return uint32(n)
case "i64":
n := parseInt(matches[2], 64)
return n
case "f32":
return float32(parseFloat(matches[2], 32))
case "f64":
return parseFloat(matches[2], 64)
default:
panic("invalid value_type prefix " + matches[1])
}
}
func TestParseValue(t *testing.T) {
tests := []struct {
str string
want interface{}
}{
{"i64:45", uint64(45)},
{"f32:0.0", float32(0)},
{"f64:0x1.fffffep+127", float64(340282346638528859811704183484516925440.0)},
{"f64:inf", float64(math.Inf(+1))},
{"f32:inf", float32(math.Inf(+1))},
}
for _, test := range tests {
v := parseValue(test.str)
if !reflect.DeepEqual(test.want, v) {
t.Errorf("unexpected value returned by parseInt: got=%v want=%v", v, test.want)
}
}
}
func parseArgs(args []string) (arr []uint64) {
for _, str := range args {
v := parseValue(str)
var n uint64
switch v.(type) {
case uint64:
n = v.(uint64)
case uint32:
n = uint64(v.(uint32))
case float32:
n = uint64(math.Float32bits(v.(float32)))
case float64:
n = math.Float64bits(v.(float64))
default:
panic(fmt.Sprintf("invalid value type: %v(%v)", reflect.TypeOf(v), v))
}
arr = append(arr, n)
}
return arr
}
func fnString(fn string, args []string) string {
if len(args) == 0 {
return fn
}
return fmt.Sprintf("%s(%v)", fn, args)
}
func panics(fn func()) (panicked bool, msg string) {
defer func() {
r := recover()
panicked = r != nil
msg = fmt.Sprint(r)
}()
fn()
return
}
func runTest(fileName string, testCases []testCase, t testing.TB) {
file, err := os.Open(fileName)
if err != nil {
t.Fatal(err)
}
defer file.Close()
module, err := wasm.ReadModule(file, nil)
if err != nil {
t.Fatal(err)
}
if err = validate.VerifyModule(module); err != nil {
t.Fatalf("%s: %v", fileName, err)
}
vm, err := exec.NewVM(module)
if err != nil {
t.Fatalf("%s: %v", fileName, err)
}
b, ok := t.(*testing.B)
for _, testCase := range testCases {
var expected interface{}
index := module.Export.Entries[testCase.Function].Index
args := parseArgs(testCase.Args)
if testCase.Return != "" {
expected = parseValue(testCase.Return)
}
if testCase.Trap != "" {
// don't benchmark tests that involve trapping the VM
fn := func() {
_, err := vm.ExecCode(int64(index), args...)
if err != nil {
t.Fatalf("%s, %s: %v", fileName, testCase.Function, err)
}
}
if p, msg := panics(fn); p && msg != testCase.Trap {
t.Errorf("%s, %s: unexpected trap message: got=%s, want=%s", fileName, fnString(testCase.Function, testCase.Args), msg, testCase.Trap)
}
continue
}
vm.RecoverPanic = (testCase.RecoverPanic == true)
times := 1
if ok {
times = b.N
b.ResetTimer()
}
var res interface{}
var err error
for i := 0; i < times; i++ {
res, err = vm.ExecCode(int64(index), args...)
}
if ok {
b.StopTimer()
}
if err != nil && err.Error() != testCase.ErrorMsg {
t.Fatalf("%s, %s: %v", fileName, testCase.Function, err)
}
nanEq := false
if reflect.TypeOf(res) == reflect.TypeOf(expected) {
switch v := expected.(type) {
case float32:
nanEq = math.IsNaN(float64(v)) && math.IsNaN(float64(res.(float32)))
case float64:
nanEq = math.IsNaN(v) && math.IsNaN(res.(float64))
}
}
if nanEq {
continue
}
if !reflect.DeepEqual(res, expected) {
t.Fatalf("%s, %s (%d): unexpected return value: got=%v(%v), want=%v(%v) (%s)", fileName, fnString(testCase.Function, testCase.Args), index, reflect.TypeOf(res), res, reflect.TypeOf(expected), expected, testCase.Return)
}
}
}
func testModules(t *testing.T, dir string) {
files := []file{}
file, err := os.Open(filepath.Join(dir, "modules.json"))
if err != nil {
t.Fatal(err)
}
defer file.Close()
err = json.NewDecoder(file).Decode(&files)
if err != nil {
t.Fatal(err)
}
for _, file := range files {
fileName := filepath.Join(dir, file.FileName)
testCases := file.Tests
t.Run(fileName, func(t *testing.T) {
t.Parallel()
path, err := filepath.Abs(fileName)
if err != nil {
t.Fatal(err)
}
runTest(path, testCases, t)
})
}
}
func BenchmarkModules(b *testing.B) {
files := []file{}
file, err := os.Open(filepath.Join("testdata/spec", "modules.json"))
if err != nil {
b.Fatal(err)
}
defer file.Close()
err = json.NewDecoder(file).Decode(&files)
if err != nil {
b.Fatal(err)
}
for _, file := range files {
fileName := filepath.Join("testdata/spec", file.FileName)
testCases := file.Tests
b.Run(fileName, func(b *testing.B) {
path, err := filepath.Abs(fileName)
if err != nil {
b.Fatal(err)
}
runTest(path, testCases, b)
})
}
}
func TestNonSpec(t *testing.T) {
testModules(t, nonSpecTestsDir)
}
func TestSpec(t *testing.T) {
testModules(t, specTestsDir)
}