func misc(t *testing.T, filename, expectOutput string) { const ( maxTextSize = 65536 maxRODataSize = 4096 stackSize = 4096 dumpText = false ) data, err := ioutil.ReadFile(filename) if err != nil { t.Fatal(err) } wasmReadCloser := wast2wasm(data, false) defer wasmReadCloser.Close() wasm := bufio.NewReader(wasmReadCloser) p, err := runner.NewProgram(maxTextSize, maxRODataSize) if err != nil { t.Fatal(err) } defer p.Close() var m Module m.load(wasm, runner.Env, p.Text, p.ROData, p.RODataAddr(), nil) p.Seal() p.SetData(m.Data()) p.SetFunctionMap(m.FunctionMap()) p.SetCallMap(m.CallMap()) minMemorySize, maxMemorySize := m.MemoryLimits() if dumpText && testing.Verbose() { dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), nil) } var printBuf bytes.Buffer r, err := p.NewRunner(minMemorySize, maxMemorySize, stackSize) if err != nil { t.Fatal(err) } defer r.Close() _, err = r.Run(0, m.Signatures(), &printBuf) if err != nil { t.Fatal(err) } output := string(printBuf.Bytes()) t.Logf("print output:\n%s", output) if output != expectOutput { t.Fail() } }
func testModule(t *testing.T, data []byte, filename string, quiet bool) []byte { const ( maxTextSize = 0x100000 maxRODataSize = 0x100000 maxMemorySize = 0x100000 stackSize = 4096 // limit stacktrace length timeout = time.Second * 3 dumpExps = false dumpText = false dumpROData = false dumpGlobals = false dumpMemory = false ) module, data := sexp.ParsePanic(data) if module == nil { return nil } if name := module[0].(string); name != "module" { t.Logf("%s not supported", name) return data } module = append([]interface{}{ module[0], []interface{}{ "import", sexp.Quote("wag"), sexp.Quote("get_arg"), []interface{}{ "func", "$get_arg", []interface{}{"result", "i64"}, }, }, []interface{}{ "import", sexp.Quote("wag"), sexp.Quote("set_result"), []interface{}{ "func", "$set_result", []interface{}{"param", "i32"}, }, }, }, module[1:]...) var realStartName string exports := make(map[string]string) for i := 1; i < len(module); { item, ok := module[i].([]interface{}) if !ok { i++ continue } var itemName string switch x := item[0].(type) { case string: itemName = x case sexp.Quoted: itemName = x.String() } switch itemName { case "start": realStartName = item[1].(string) module = append(module[:i], module[i+1:]...) case "export": exports[item[1].(string)] = item[2].(string) module = append(module[:i], module[i+1:]...) case "func": if len(item) > 1 { if expo, ok := item[1].([]interface{}); ok && expo[0].(string) == "export" { item[1] = "$" + expo[1].(sexp.Quoted).String() } if s, ok := item[1].(string); ok && len(item) > 2 { if expo, ok := item[2].([]interface{}); ok && expo[0].(string) == "export" { exports[expo[1].(sexp.Quoted).String()] = s[1:] } } } i++ default: i++ } } testTypes := make(map[int]string) testFunc := []interface{}{ "func", "$test", []interface{}{ "call", "$set_result", []interface{}{"i32.const", "0xbadc0de"}, }, } if realStartName != "" { testFunc = append(testFunc, []interface{}{ "if", []interface{}{ "i64.eq", []interface{}{"call", "$get_arg"}, []interface{}{"i64.const", "0"}, }, []interface{}{ "block", []interface{}{"call", realStartName}, []interface{}{ "set_global", "$test_result", []interface{}{"i64.const", "777"}, }, []interface{}{"return"}, }, }) } var idCount int for { id := idCount assert, tail := sexp.ParsePanic(data) if assert == nil { data = tail break } testType := assert[0].(string) if testType == "module" { break } idCount++ data = tail var argCount int var exprType string for _, x := range assert[1:] { if expr, ok := x.([]interface{}); ok { argCount++ exprName := expr[0].(string) if strings.Contains(exprName, ".") { exprType = strings.SplitN(exprName, ".", 2)[0] break } } } if argCount > 1 && exprType == "" { t.Fatalf("can't figure out type of %s", sexp.Stringify(assert, true)) } invoke2call(exports, assert[1:]) var test []interface{} switch testType { case "assert_return": if argCount > 1 { var check interface{} switch exprType { case "f32", "f64": bitsType := strings.Replace(exprType, "f", "i", 1) check = []interface{}{ bitsType + ".eq", []interface{}{ bitsType + ".reinterpret/" + exprType, assert[1], }, []interface{}{ bitsType + ".reinterpret/" + exprType, assert[2], }, } default: check = []interface{}{exprType + ".eq", assert[1], assert[2]} } test = []interface{}{ "block", []interface{}{ "call", "$set_result", check, }, []interface{}{"return"}, } } else { test = append([]interface{}{"block"}, assert[1:]...) test = append(test, []interface{}{ "block", []interface{}{ "call", "$set_result", []interface{}{"i32.const", "1"}, }, []interface{}{"return"}, }) } case "assert_trap": test = []interface{}{ "block", assert[1], []interface{}{"return"}, } case "invoke": n := assert[1].(sexp.Quoted).String() name, found := exports[n] if !found { name = n } test = []interface{}{ "block", append([]interface{}{"call", "$" + name}, assert[2:]...), []interface{}{ "call", "$set_result", []interface{}{"i32.const", "-1"}, }, []interface{}{"return"}, } default: testType = "" } testTypes[id] = testType if test != nil { testFunc = append(testFunc, []interface{}{ "if", []interface{}{ "i64.eq", []interface{}{"call", "$get_arg"}, []interface{}{"i64.const", strconv.Itoa(id)}, }, test, }) } } module = append(module, testFunc) module = append(module, []interface{}{"start", "$test"}) if dumpExps { fmt.Println(sexp.Stringify(module, true)) } { wasmReadCloser := wast2wasm(sexp.Unparse(module), quiet) defer wasmReadCloser.Close() wasm := bufio.NewReader(wasmReadCloser) var timedout bool p, err := runner.NewProgram(maxTextSize, maxRODataSize) if err != nil { t.Fatal(err) } defer func() { if !timedout { p.Close() } }() var nameSection sections.NameSection m := Module{ UnknownSectionLoader: sections.UnknownLoaders{ "name": nameSection.Load, }.Load, } m.load(wasm, runner.Env, p.Text, p.ROData, p.RODataAddr(), nil) p.Seal() p.SetData(m.Data()) p.SetFunctionMap(m.FunctionMap()) p.SetCallMap(m.CallMap()) minMemorySize, maxMemorySize := m.MemoryLimits() if dumpText && testing.Verbose() { dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), &nameSection) } if dumpROData { buf := m.ROData() for i := 0; len(buf) > 0; i++ { if len(buf) > 4 { t.Logf("read-only data #%d*8: 0x%08x 0x%08x", i, binary.LittleEndian.Uint32(buf[:4]), binary.LittleEndian.Uint32(buf[4:8])) buf = buf[8:] } else { t.Logf("read-only data #%d*8: 0x%08x", i, binary.LittleEndian.Uint32(buf[:4])) buf = buf[4:] } } } if dumpGlobals { data, memoryOffset := m.Data() buf := data[:memoryOffset] if len(buf) == 0 { t.Log("no globals") } for i := 0; len(buf) > 0; i++ { t.Logf("global #%d: 0x%016x", i, binary.LittleEndian.Uint64(buf)) buf = buf[8:] } } if dumpMemory { data, memoryOffset := m.Data() t.Logf("memory: %#v", data[memoryOffset:]) } memGrowSize := maxMemorySize if maxMemorySize > 0 && memGrowSize > maxMemorySize { memGrowSize = maxMemorySize } r, err := p.NewRunner(minMemorySize, memGrowSize, stackSize) if err != nil { t.Fatal(err) } defer func() { if !timedout { r.Close() } }() if realStartName != "" { var printBuf bytes.Buffer result, err := r.Run(0, m.Signatures(), &printBuf) if printBuf.Len() > 0 { t.Logf("run: module %s: print:\n%s", filename, string(printBuf.Bytes())) } if err != nil { t.Fatal(err) } if result != 777 { t.Fatalf("0x%x", result) } t.Logf("run: module %s: start", filename) } for id := 0; id < idCount; id++ { testType := testTypes[id] if testType == "" { t.Logf("run: module %s: test #%d: not supported", filename, id) continue } var printBuf bytes.Buffer var result int32 var panicked interface{} done := make(chan struct{}) go func() { defer close(done) defer func() { panicked = recover() }() result, err = r.Run(int64(id), m.Signatures(), &printBuf) }() timer := time.NewTimer(timeout) select { case <-done: timer.Stop() case <-timer.C: timedout = true t.Fatalf("run: module %s: test #%d: timeout", filename, id) } if printBuf.Len() > 0 { t.Logf("run: module %s: test #%d: print output:\n%s", filename, id, string(printBuf.Bytes())) } if panicked != nil { t.Fatalf("run: module %s: test #%d: panic: %v", filename, id, panicked) } var stackBuf bytes.Buffer if err := r.WriteStacktraceTo(&stackBuf, m.FunctionSignatures(), &nameSection); err == nil { if stackBuf.Len() > 0 { t.Logf("run: module %s: test #%d: stacktrace:\n%s", filename, id, string(stackBuf.Bytes())) } } else { t.Errorf("run: module %s: test #%d: stacktrace error: %v", filename, id, err) } if err != nil { if _, ok := err.(traps.Id); ok { if testType == "assert_trap" { t.Logf("run: module %s: test #%d: pass", filename, id) } else { t.Errorf("run: module %s: test #%d: FAIL due to unexpected trap", filename, id) } } else { t.Fatal(err) } } else { if testType == "assert_return" { switch result { case 1: t.Logf("run: module %s: test #%d: pass", filename, id) case 0: t.Errorf("run: module %s: test #%d: FAIL", filename, id) default: t.Fatalf("run: module %s: test #%d: bad result: 0x%x", filename, id, result) } } else if testType == "invoke" { switch result { case -1: t.Logf("run: module %s: test #%d: invoke", filename, id) default: t.Fatalf("run: module %s: test #%d: bad result: 0x%x", filename, id, result) } } else { t.Errorf("run: module %s: test #%d: FAIL due to unexpected return (result: 0x%x)", filename, id, result) } } } } return data }
func TestExec(t *testing.T) { const ( maxTextSize = 65536 maxRODataSize = 4096 stackSize = 4096 dumpText = false ) data, err := ioutil.ReadFile("testdata/exec.wast") if err != nil { t.Fatal(err) } wasmReadCloser := wast2wasm(data, false) defer wasmReadCloser.Close() wasm := bufio.NewReader(wasmReadCloser) var m Module m.loadPreliminarySections(wasm, runner.Env) var codeBuf bytes.Buffer if ok, err := sections.CopyCodeSection(&codeBuf, wasm); err != nil { t.Fatal(err) } else if !ok { t.Fatal(ok) } // skip name section if err := sections.DiscardUnknownSections(wasm); err != nil { t.Fatal(err) } minMemorySize, maxMemorySize := m.MemoryLimits() p, err := runner.NewProgram(maxTextSize, maxRODataSize) if err != nil { t.Fatal(err) } defer p.Close() r, err := p.NewRunner(minMemorySize, maxMemorySize, stackSize) if err != nil { t.Fatal(err) } defer r.Close() var printBuf bytes.Buffer e, trigger := r.NewExecutor(m.Signatures(), &printBuf) m.loadDataSection(wasm) p.SetData(m.Data()) m.loadCodeSection(&codeBuf, p.Text, p.ROData, p.RODataAddr(), trigger) p.Seal() p.SetFunctionMap(m.FunctionMap()) p.SetCallMap(m.CallMap()) if _, err := e.Wait(); err != nil { t.Fatal(err) } if printBuf.Len() > 0 { t.Logf("print output:\n%s", string(printBuf.Bytes())) } if dumpText && testing.Verbose() { dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), nil) } }
func TestSnapshot(t *testing.T) { const ( maxTextSize = 65536 maxRODataSize = 4096 stackSize = 4096 dumpText = false ) data, err := ioutil.ReadFile("testdata/snapshot.wast") if err != nil { t.Fatal(err) } wasmReadCloser := wast2wasm(data, false) defer wasmReadCloser.Close() wasm := bufio.NewReader(wasmReadCloser) p, err := runner.NewProgram(maxTextSize, maxRODataSize) if err != nil { t.Fatal(err) } defer p.Close() var m Module m.load(wasm, runner.Env, p.Text, p.ROData, p.RODataAddr(), nil) p.Seal() p.SetData(m.Data()) p.SetFunctionMap(m.FunctionMap()) p.SetCallMap(m.CallMap()) minMemorySize, maxMemorySize := m.MemoryLimits() if dumpText && testing.Verbose() { dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), nil) } var printBuf bytes.Buffer r1, err := p.NewRunner(minMemorySize, maxMemorySize, stackSize) if err != nil { t.Fatal(err) } _, err = r1.Run(0, m.Signatures(), &printBuf) r1.Close() if printBuf.Len() > 0 { t.Logf("print output:\n%s", string(printBuf.Bytes())) } if len(r1.Snapshots) != 1 { t.Fatal(r1.Snapshots) } s := r1.Snapshots[0] t.Log("resuming") printBuf.Reset() r2, err := s.NewRunner(maxMemorySize, stackSize) if err != nil { t.Fatal(err) } _, err = r2.Run(0, m.Signatures(), &printBuf) r2.Close() if printBuf.Len() > 0 { t.Logf("print output:\n%s", string(printBuf.Bytes())) } if len(r2.Snapshots) != 0 { t.Fatal(r2.Snapshots) } }