From 76a6a2631ff5efecc76b904553e692886b0e0b76 Mon Sep 17 00:00:00 2001
From: Anmol Sethi <hi@nhooyr.io>
Date: Sun, 22 Sep 2019 12:21:46 -0500
Subject: [PATCH] Improve wasm test coverage

---
 assert_test.go                     | 29 ++++++++++++++++++--
 ci/wasm.sh                         | 19 +++++++------
 go.mod                             |  1 -
 internal/wsecho/cmd/wsecho/main.go | 21 ---------------
 internal/wsecho/wsecho.go          | 22 ++-------------
 internal/wsjs/wsjs.go              |  2 +-
 internal/wsjstest/main.go          | 43 ++++++++++++++++++++++++++++++
 netconn_js.go                      | 17 ------------
 netconn_normal.go                  | 12 ---------
 websocket.go                       |  4 +++
 websocket_js.go                    | 22 ++++++++++-----
 websocket_js_test.go               | 20 +++++++++++---
 websocket_test.go                  | 21 ---------------
 13 files changed, 120 insertions(+), 113 deletions(-)
 delete mode 100644 internal/wsecho/cmd/wsecho/main.go
 create mode 100644 internal/wsjstest/main.go
 delete mode 100644 netconn_js.go
 delete mode 100644 netconn_normal.go

diff --git a/assert_test.go b/assert_test.go
index 2f05337..cddae99 100644
--- a/assert_test.go
+++ b/assert_test.go
@@ -2,7 +2,9 @@ package websocket_test
 
 import (
 	"context"
+	"encoding/hex"
 	"fmt"
+	"math/rand"
 	"reflect"
 
 	"github.com/google/go-cmp/cmp"
@@ -91,9 +93,32 @@ func assertJSONRead(ctx context.Context, c *websocket.Conn, exp interface{}) err
 }
 
 func randBytes(n int) []byte {
-	return make([]byte, n)
+	b := make([]byte, n)
+	rand.Read(b)
+	return b
 }
 
 func randString(n int) string {
-	return string(randBytes(n))
+	return hex.EncodeToString(randBytes(n))[:n]
+}
+
+func assertEcho(ctx context.Context, c *websocket.Conn, typ websocket.MessageType, n int) error {
+	p := randBytes(n)
+	err := c.Write(ctx, typ, p)
+	if err != nil {
+		return err
+	}
+	typ2, p2, err := c.Read(ctx)
+	if err != nil {
+		return err
+	}
+	err = assertEqualf(typ, typ2, "unexpected data type")
+	if err != nil {
+		return err
+	}
+	return assertEqualf(p, p2, "unexpected payload")
+}
+
+func assertSubprotocol(c *websocket.Conn, exp string) error {
+	return assertEqualf(exp, c.Subprotocol(), "unexpected subprotocol")
 }
diff --git a/ci/wasm.sh b/ci/wasm.sh
index eb4a0cf..0290f18 100755
--- a/ci/wasm.sh
+++ b/ci/wasm.sh
@@ -9,19 +9,22 @@ GOOS=js GOARCH=wasm go vet ./...
 go install golang.org/x/lint/golint
 GOOS=js GOARCH=wasm golint -set_exit_status ./...
 
-wsEchoOut="$(mktemp -d)/stdout"
-mkfifo "$wsEchoOut"
-go install ./internal/wsecho/cmd/wsecho
-wsecho > "$wsEchoOut" &
+wsjstestOut="$(mktemp -d)/stdout"
+mkfifo "$wsjstestOut"
+go install ./internal/wsjstest
+timeout 30s wsjstest > "$wsjstestOut" &
+wsjstestPID=$!
 
-WS_ECHO_SERVER_URL="$(timeout 10s head -n 1 "$wsEchoOut")" || true
+WS_ECHO_SERVER_URL="$(timeout 10s head -n 1 "$wsjstestOut")" || true
 if [[ -z $WS_ECHO_SERVER_URL ]]; then
-  echo "./internal/wsecho/cmd/wsecho failed to start in 10s"
+  echo "./internal/wsjstest failed to start in 10s"
   exit 1
 fi
 
 go install github.com/agnivade/wasmbrowsertest
 GOOS=js GOARCH=wasm go test -exec=wasmbrowsertest ./... -args "$WS_ECHO_SERVER_URL"
 
-kill %1
-wait -n || true
+if ! wait "$wsjstestPID"; then
+  echo "wsjstest exited unsuccessfully"
+  exit 1
+fi
diff --git a/go.mod b/go.mod
index ab46375..86a9403 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,6 @@ require (
 	golang.org/x/sys v0.0.0-20190919044723-0c1ff786ef13 // indirect
 	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
 	golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72
-	golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
 	gotest.tools/gotestsum v0.3.5
 	mvdan.cc/sh v2.6.4+incompatible
diff --git a/internal/wsecho/cmd/wsecho/main.go b/internal/wsecho/cmd/wsecho/main.go
deleted file mode 100644
index 9d9dc82..0000000
--- a/internal/wsecho/cmd/wsecho/main.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +build !js
-
-package main
-
-import (
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"runtime"
-	"strings"
-
-	"nhooyr.io/websocket/internal/wsecho"
-)
-
-func main() {
-	s := httptest.NewServer(http.HandlerFunc(wsecho.Serve))
-	wsURL := strings.Replace(s.URL, "http", "ws", 1)
-	fmt.Printf("%v\n", wsURL)
-
-	runtime.Goexit()
-}
diff --git a/internal/wsecho/wsecho.go b/internal/wsecho/wsecho.go
index 8f531f1..c408f07 100644
--- a/internal/wsecho/wsecho.go
+++ b/internal/wsecho/wsecho.go
@@ -5,33 +5,15 @@ package wsecho
 import (
 	"context"
 	"io"
-	"log"
-	"net/http"
 	"time"
 
 	"nhooyr.io/websocket"
 )
 
-// Serve provides a streaming WebSocket echo server
-// for use in tests.
-func Serve(w http.ResponseWriter, r *http.Request) {
-	c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
-		Subprotocols:       []string{"echo"},
-		InsecureSkipVerify: true,
-	})
-	if err != nil {
-		log.Printf("echo server: failed to accept: %+v", err)
-		return
-	}
-	defer c.Close(websocket.StatusInternalError, "")
-
-	Loop(r.Context(), c)
-}
-
 // Loop echos every msg received from c until an error
 // occurs or the context expires.
 // The read limit is set to 1 << 30.
-func Loop(ctx context.Context, c *websocket.Conn) {
+func Loop(ctx context.Context, c *websocket.Conn) error {
 	defer c.Close(websocket.StatusInternalError, "")
 
 	c.SetReadLimit(1 << 30)
@@ -67,7 +49,7 @@ func Loop(ctx context.Context, c *websocket.Conn) {
 	for {
 		err := echo()
 		if err != nil {
-			return
+			return err
 		}
 	}
 }
diff --git a/internal/wsjs/wsjs.go b/internal/wsjs/wsjs.go
index f83b766..68078cf 100644
--- a/internal/wsjs/wsjs.go
+++ b/internal/wsjs/wsjs.go
@@ -41,7 +41,7 @@ func New(url string, protocols []string) (c WebSocket, err error) {
 		v: js.Global().Get("WebSocket").New(url, jsProtocols),
 	}
 
-	c.setBinaryType("arrayBuffer")
+	c.setBinaryType("arraybuffer")
 
 	c.Extensions = c.v.Get("extensions").String()
 	c.Protocol = c.v.Get("protocol").String()
diff --git a/internal/wsjstest/main.go b/internal/wsjstest/main.go
new file mode 100644
index 0000000..a1ad1b0
--- /dev/null
+++ b/internal/wsjstest/main.go
@@ -0,0 +1,43 @@
+// +build !js
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"runtime"
+	"strings"
+
+	"nhooyr.io/websocket"
+	"nhooyr.io/websocket/internal/wsecho"
+)
+
+func main() {
+	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
+			Subprotocols:       []string{"echo"},
+			InsecureSkipVerify: true,
+		})
+		if err != nil {
+			log.Fatalf("echo server: failed to accept: %+v", err)
+		}
+		defer c.Close(websocket.StatusInternalError, "")
+
+		err = wsecho.Loop(r.Context(), c)
+
+		var ce websocket.CloseError
+		if !errors.As(err, &ce) || ce.Code != websocket.StatusNormalClosure {
+			log.Fatalf("unexpected loop error: %+v", err)
+		}
+
+		os.Exit(0)
+	}))
+	wsURL := strings.Replace(s.URL, "http", "ws", 1)
+	fmt.Printf("%v\n", wsURL)
+
+	runtime.Goexit()
+}
diff --git a/netconn_js.go b/netconn_js.go
deleted file mode 100644
index 5cd15d4..0000000
--- a/netconn_js.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// +build js
-
-package websocket
-
-import (
-	"bytes"
-	"context"
-	"io"
-)
-
-func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
-	typ, p, err := c.c.Read(ctx)
-	if err != nil {
-		return 0, nil, err
-	}
-	return typ, bytes.NewReader(p), nil
-}
diff --git a/netconn_normal.go b/netconn_normal.go
deleted file mode 100644
index 0db551d..0000000
--- a/netconn_normal.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// +build !js
-
-package websocket
-
-import (
-	"context"
-	"io"
-)
-
-func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
-	return c.c.Reader(c.readContext)
-}
diff --git a/websocket.go b/websocket.go
index 596d89f..bbadb9b 100644
--- a/websocket.go
+++ b/websocket.go
@@ -946,3 +946,7 @@ func (c *Conn) extractBufioWriterBuf(w io.Writer) {
 
 	c.bw.Reset(w)
 }
+
+func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
+	return c.c.Reader(c.readContext)
+}
diff --git a/websocket_js.go b/websocket_js.go
index 0782e04..14f198d 100644
--- a/websocket_js.go
+++ b/websocket_js.go
@@ -1,17 +1,17 @@
 package websocket // import "nhooyr.io/websocket"
 
 import (
+	"bytes"
 	"context"
 	"errors"
 	"fmt"
+	"io"
 	"net/http"
 	"reflect"
 	"runtime"
 	"sync"
 	"syscall/js"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/wsjs"
 )
 
@@ -35,9 +35,6 @@ func (c *Conn) close(err error) {
 
 		c.closeErr = fmt.Errorf("websocket closed: %w", err)
 		close(c.closed)
-
-		c.releaseOnClose()
-		c.releaseOnMessage()
 	})
 }
 
@@ -52,6 +49,9 @@ func (c *Conn) init() {
 		}
 
 		c.close(fmt.Errorf("received close frame: %w", cerr))
+
+		c.releaseOnClose()
+		c.releaseOnMessage()
 	})
 
 	c.releaseOnMessage = c.ws.OnMessage(func(e wsjs.MessageEvent) {
@@ -144,8 +144,8 @@ func (c *Conn) Close(code StatusCode, reason string) error {
 	}
 	c.close(err)
 
-	if !xerrors.Is(c.closeErr, err) {
-		return xerrors.Errorf("failed to close websocket: %w", err)
+	if !errors.Is(c.closeErr, err) {
+		return fmt.Errorf("failed to close websocket: %w", err)
 	}
 
 	return nil
@@ -207,3 +207,11 @@ func dial(ctx context.Context, url string, opts *DialOptions) (*Conn, *http.Resp
 	// Have to return a non nil response as the normal API does that.
 	return c, &http.Response{}, nil
 }
+
+func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
+	typ, p, err := c.c.Read(ctx)
+	if err != nil {
+		return 0, nil, err
+	}
+	return typ, bytes.NewReader(p), nil
+}
diff --git a/websocket_js_test.go b/websocket_js_test.go
index 9ced658..1142190 100644
--- a/websocket_js_test.go
+++ b/websocket_js_test.go
@@ -10,7 +10,7 @@ import (
 	"nhooyr.io/websocket"
 )
 
-func TestWebSocket(t *testing.T) {
+func TestConn(t *testing.T) {
 	t.Parallel()
 
 	wsEchoServerURL := flag.Arg(0)
@@ -18,18 +18,30 @@ func TestWebSocket(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
 	defer cancel()
 
-	c, resp, err := websocket.Dial(ctx, wsEchoServerURL, nil)
+	c, resp, err := websocket.Dial(ctx, wsEchoServerURL, &websocket.DialOptions{
+		Subprotocols: []string{"echo"},
+	})
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer c.Close(websocket.StatusInternalError, "")
 
+	assertSubprotocol(c, "echo")
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	err = assertEqualf(&http.Response{}, resp, "unexpected http response")
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	err = assertJSONEcho(ctx, c, 4096)
+	err = assertJSONEcho(ctx, c, 16)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = assertEcho(ctx, c, websocket.MessageBinary, 16)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -38,4 +50,6 @@ func TestWebSocket(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+
+	time.Sleep(time.Millisecond * 100)
 }
diff --git a/websocket_test.go b/websocket_test.go
index 36a5224..2fabba5 100644
--- a/websocket_test.go
+++ b/websocket_test.go
@@ -1858,23 +1858,6 @@ func assertCloseStatus(err error, code websocket.StatusCode) error {
 	return assertEqualf(code, cerr.Code, "unexpected status code")
 }
 
-func assertEcho(ctx context.Context, c *websocket.Conn, typ websocket.MessageType, n int) error {
-	p := randBytes(n)
-	err := c.Write(ctx, typ, p)
-	if err != nil {
-		return err
-	}
-	typ2, p2, err := c.Read(ctx)
-	if err != nil {
-		return err
-	}
-	err = assertEqualf(typ, typ2, "unexpected data type")
-	if err != nil {
-		return err
-	}
-	return assertEqualf(p, p2, "unexpected payload")
-}
-
 func assertProtobufRead(ctx context.Context, c *websocket.Conn, exp interface{}) error {
 	expType := reflect.TypeOf(exp)
 	actv := reflect.New(expType.Elem())
@@ -1887,10 +1870,6 @@ func assertProtobufRead(ctx context.Context, c *websocket.Conn, exp interface{})
 	return assertEqualf(exp, act, "unexpected protobuf")
 }
 
-func assertSubprotocol(c *websocket.Conn, exp string) error {
-	return assertEqualf(exp, c.Subprotocol(), "unexpected subprotocol")
-}
-
 func assertNetConnRead(r io.Reader, exp string) error {
 	act := make([]byte, len(exp))
 	_, err := r.Read(act)
-- 
GitLab