From 95f509b109d4936bec9b6020cb34fbb4bd5cba16 Mon Sep 17 00:00:00 2001 From: Brad Peabody Date: Sat, 23 May 2020 05:12:01 -0700 Subject: [PATCH] wasm test suite (#1116) * wasm: add test suite using headlless chrome --- .circleci/config.yml | 9 ++ Makefile | 3 + go.mod | 2 + go.sum | 15 +++ tests/wasm/chan_test.go | 34 ++++++ tests/wasm/event_test.go | 47 ++++++++ tests/wasm/fmt_test.go | 43 ++++++++ tests/wasm/fmtprint_test.go | 39 +++++++ tests/wasm/setup_test.go | 189 ++++++++++++++++++++++++++++++++ tests/wasm/testdata/chan.go | 16 +++ tests/wasm/testdata/event.go | 31 ++++++ tests/wasm/testdata/fmt.go | 8 ++ tests/wasm/testdata/fmtprint.go | 11 ++ 13 files changed, 447 insertions(+) create mode 100644 tests/wasm/chan_test.go create mode 100644 tests/wasm/event_test.go create mode 100644 tests/wasm/fmt_test.go create mode 100644 tests/wasm/fmtprint_test.go create mode 100644 tests/wasm/setup_test.go create mode 100644 tests/wasm/testdata/chan.go create mode 100644 tests/wasm/testdata/event.go create mode 100644 tests/wasm/testdata/fmt.go create mode 100644 tests/wasm/testdata/fmtprint.go diff --git a/.circleci/config.yml b/.circleci/config.yml index e311b6a1..060d6650 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,6 +37,13 @@ commands: sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node rm node-v10.15.1-linux-x64.tar.xz + install-chrome: + steps: + - run: + name: "Install Chrome" + command: | + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt install ./google-chrome-stable_current_amd64.deb llvm-source-linux: steps: - restore_cache: @@ -71,6 +78,7 @@ commands: - apt-dependencies: llvm: "<>" - install-node + - install-chrome - restore_cache: keys: - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} @@ -88,6 +96,7 @@ commands: - run: go test -v -tags=llvm<> ./cgo ./compileopts ./interp ./transform . - run: make gen-device -j4 - run: make smoketest + - run: make wasmtest - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: diff --git a/Makefile b/Makefile index fcdc72f7..82307e12 100644 --- a/Makefile +++ b/Makefile @@ -310,6 +310,9 @@ endif $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main +wasmtest: + $(GO) test ./tests/wasm + build/release: tinygo gen-device wasi-libc @mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/lib/clang/include diff --git a/go.mod b/go.mod index a2cbe4a7..a35767d3 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.11 require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 + github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac + github.com/chromedp/chromedp v0.5.3 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 go.bug.st/serial v1.0.0 diff --git a/go.sum b/go.sum index 09327350..60fdd4db 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,25 @@ github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4= +github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= +github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg= +github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= +github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs= +github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo= github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -27,6 +41,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids= golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/tests/wasm/chan_test.go b/tests/wasm/chan_test.go new file mode 100644 index 00000000..1cd08e66 --- /dev/null +++ b/tests/wasm/chan_test.go @@ -0,0 +1,34 @@ +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestChan(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/chan.wasm -target wasm testdata/chan.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=chan.wasm"), + waitLog(`1 +2 +4 +3 +true`), + ) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/event_test.go b/tests/wasm/event_test.go new file mode 100644 index 00000000..d2b8340c --- /dev/null +++ b/tests/wasm/event_test.go @@ -0,0 +1,47 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestEvent(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/event.wasm -target wasm testdata/event.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1, log2 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=event.wasm"), + chromedp.WaitVisible("#log"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`1 +4`), + chromedp.Click("#testbtn"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log2), + waitLog(`1 +4 +2 +3 +true`), + ) + t.Logf("log1: %s", log1) + t.Logf("log2: %s", log2) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/fmt_test.go b/tests/wasm/fmt_test.go new file mode 100644 index 00000000..8b4fe8c7 --- /dev/null +++ b/tests/wasm/fmt_test.go @@ -0,0 +1,43 @@ +// +build go1.14 + +package wasm + +// NOTE: this should work in go1.13 but panics with: +// panic: syscall/js: call of Value.Get on string +// which is coming from here: https://github.com/golang/go/blob/release-branch.go1.13/src/syscall/js/js.go#L252 +// But I'm not sure how import "fmt" results in this. +// To reproduce, install Go 1.13.x and change the build tag above +// to go1.13 and run this test. + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestFmt(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/fmt.wasm -target wasm testdata/fmt.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=fmt.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`did not panic`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/fmtprint_test.go b/tests/wasm/fmtprint_test.go new file mode 100644 index 00000000..ebd1ffa9 --- /dev/null +++ b/tests/wasm/fmtprint_test.go @@ -0,0 +1,39 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestFmtprint(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/fmtprint.wasm -target wasm testdata/fmtprint.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=fmtprint.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`test from fmtprint 1 +test from fmtprint 2 +test from fmtprint 3 +test from fmtprint 4`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go new file mode 100644 index 00000000..7d5ffa67 --- /dev/null +++ b/tests/wasm/setup_test.go @@ -0,0 +1,189 @@ +package wasm + +import ( + "context" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/chromedp/cdproto/cdp" + "github.com/chromedp/chromedp" +) + +var addr = flag.String("addr", ":8826", "Host:port to listen on for wasm test server") + +var wasmTmpDir string // set in TestMain to a temp directory for build output + +func TestMain(m *testing.M) { + flag.Parse() + + os.Exit(func() int { + + var err error + wasmTmpDir, err = ioutil.TempDir("", "wasm_test") + if err != nil { + log.Fatalf("unable to create temp dir: %v", err) + } + defer os.RemoveAll(wasmTmpDir) // cleanup even on panic and before os.Exit + + startServer(wasmTmpDir) + + return m.Run() + }()) + +} + +func run(cmdline string) error { + args := strings.Fields(cmdline) + return runargs(args...) +} + +func runargs(args ...string) error { + cmd := exec.Command(args[0], args[1:]...) + b, err := cmd.CombinedOutput() + log.Printf("Command: %s; err=%v; full output:\n%s", strings.Join(args, " "), err, b) + if err != nil { + return err + } + return nil +} + +func chromectx(timeout time.Duration) (context.Context, context.CancelFunc) { + + var ctx context.Context + + // looks for locally installed Chrome + ctx, _ = chromedp.NewContext(context.Background()) + + ctx, cancel := context.WithTimeout(ctx, timeout) + + return ctx, cancel +} + +func startServer(tmpDir string) { + + fsh := http.FileServer(http.Dir(tmpDir)) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + if r.URL.Path == "/wasm_exec.js" { + http.ServeFile(w, r, "../../targets/wasm_exec.js") + return + } + + if r.URL.Path == "/run" { + fmt.Fprintf(w, ` + + +Test + + + +
+

+
+
+
+
+`, r.FormValue("file"))
+			return
+		}
+
+		fsh.ServeHTTP(w, r)
+	})
+
+	log.Printf("Starting server at %q for dir: %s", *addr, tmpDir)
+	go func() {
+		log.Fatal(http.ListenAndServe(*addr, h))
+	}()
+
+}
+
+func waitLog(logText string) chromedp.QueryAction {
+	return waitInnerTextTrimEq("#log", logText)
+}
+
+// waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific string after whitespace trimming.
+func waitInnerTextTrimEq(sel, innerText string) chromedp.QueryAction {
+
+	return chromedp.Query(sel, func(s *chromedp.Selector) {
+
+		chromedp.WaitFunc(func(ctx context.Context, cur *cdp.Frame, ids ...cdp.NodeID) ([]*cdp.Node, error) {
+
+			nodes := make([]*cdp.Node, len(ids))
+			cur.RLock()
+			for i, id := range ids {
+				nodes[i] = cur.Nodes[id]
+				if nodes[i] == nil {
+					cur.RUnlock()
+					// not yet ready
+					return nil, nil
+				}
+			}
+			cur.RUnlock()
+
+			var ret string
+			err := chromedp.EvaluateAsDevTools("document.querySelector('"+sel+"').innerText", &ret).Do(ctx)
+			if err != nil {
+				return nodes, err
+			}
+			if strings.TrimSpace(ret) != innerText {
+				// log.Printf("found text: %s", ret)
+				return nodes, errors.New("unexpected value: " + ret)
+			}
+
+			// log.Printf("NodeValue: %#v", nodes[0])
+
+			// return nil, errors.New("not ready yet")
+			return nodes, nil
+		})(s)
+
+	})
+
+}
diff --git a/tests/wasm/testdata/chan.go b/tests/wasm/testdata/chan.go
new file mode 100644
index 00000000..8f10d1f8
--- /dev/null
+++ b/tests/wasm/testdata/chan.go
@@ -0,0 +1,16 @@
+package main
+
+func main() {
+
+	ch := make(chan bool, 1)
+	println("1")
+	go func() {
+		println("2")
+		ch <- true
+		println("3")
+	}()
+	println("4")
+	v := <-ch
+	println(v)
+
+}
diff --git a/tests/wasm/testdata/event.go b/tests/wasm/testdata/event.go
new file mode 100644
index 00000000..4153774f
--- /dev/null
+++ b/tests/wasm/testdata/event.go
@@ -0,0 +1,31 @@
+package main
+
+import "syscall/js"
+
+func main() {
+
+	ch := make(chan bool, 1)
+
+	println("1")
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#main").
+		Set("innerHTML", ``)
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("addEventListener", "click",
+			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+				println("2")
+				ch <- true
+				println("3")
+				return nil
+			}))
+
+	println("4")
+	v := <-ch
+	println(v)
+
+}
diff --git a/tests/wasm/testdata/fmt.go b/tests/wasm/testdata/fmt.go
new file mode 100644
index 00000000..b51c564c
--- /dev/null
+++ b/tests/wasm/testdata/fmt.go
@@ -0,0 +1,8 @@
+package main
+
+import "fmt"
+
+func main() {
+	var _ fmt.Stringer
+	println("did not panic")
+}
diff --git a/tests/wasm/testdata/fmtprint.go b/tests/wasm/testdata/fmtprint.go
new file mode 100644
index 00000000..1bad3361
--- /dev/null
+++ b/tests/wasm/testdata/fmtprint.go
@@ -0,0 +1,11 @@
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("test from fmtprint 1")
+	fmt.Print("test from fmtprint 2\n")
+	fmt.Print("test from fmtp")
+	fmt.Print("rint 3\n")
+	fmt.Printf("test from fmtprint %d\n", 4)
+}