[tor-commits] [snowflake/master] websocketconn tests.

dcf at torproject.org dcf at torproject.org
Tue Feb 4 22:54:15 UTC 2020


commit 5708a1d57b53d6d586d0e98be9a0b5a964e7c6c3
Author: David Fifield <david at bamsoftware.com>
Date:   Mon Feb 3 12:06:08 2020 -0700

    websocketconn tests.
    
    https://bugs.torproject.org/33144
---
 common/websocketconn/websocketconn_test.go | 235 +++++++++++++++++++++++++++++
 1 file changed, 235 insertions(+)

diff --git a/common/websocketconn/websocketconn_test.go b/common/websocketconn/websocketconn_test.go
new file mode 100644
index 0000000..ad6f100
--- /dev/null
+++ b/common/websocketconn/websocketconn_test.go
@@ -0,0 +1,235 @@
+package websocketconn
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/gorilla/websocket"
+)
+
+// Returns a (server, client) pair of websocketconn.Conns.
+func connPair() (*Conn, *Conn, error) {
+	// Will be assigned inside server.Handler.
+	var serverConn *Conn
+
+	// Start up a web server to receive the request.
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return nil, nil, err
+	}
+	defer ln.Close()
+	errCh := make(chan error)
+	server := http.Server{
+		Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+			upgrader := websocket.Upgrader{
+				CheckOrigin: func(*http.Request) bool { return true },
+			}
+			ws, err := upgrader.Upgrade(rw, req, nil)
+			if err != nil {
+				errCh <- err
+				return
+			}
+			serverConn = New(ws)
+			close(errCh)
+		}),
+	}
+	defer server.Close()
+	go func() {
+		err := server.Serve(ln)
+		if err != nil && err != http.ErrServerClosed {
+			errCh <- err
+		}
+	}()
+
+	// Make a request to the web server.
+	urlStr := (&url.URL{Scheme: "ws", Host: ln.Addr().String()}).String()
+	ws, _, err := (&websocket.Dialer{}).Dial(urlStr, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+	clientConn := New(ws)
+
+	// The server is finished when errCh is written to or closed.
+	err = <-errCh
+	if err != nil {
+		return nil, nil, err
+	}
+	return serverConn, clientConn, nil
+}
+
+// Test that you can write in chunks and read the result concatenated.
+func TestWrite(t *testing.T) {
+	tests := [][][]byte{
+		{},
+		{[]byte("foo")},
+		{[]byte("foo"), []byte("bar")},
+		{{}, []byte("foo"), {}, {}, []byte("bar")},
+	}
+
+	for _, test := range tests {
+		s, c, err := connPair()
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		// This is a little awkward because we need to read to and write
+		// from both ends of the Conn, and we need to do it in separate
+		// goroutines because otherwise a Write may block waiting for
+		// someone to Read it. Here we set up a loop in a separate
+		// goroutine, reading from the Conn s and writing to the dataCh
+		// and errCh channels, whose ultimate effect in the select loop
+		// below is like
+		//   data, err := ioutil.ReadAll(s)
+		dataCh := make(chan []byte)
+		errCh := make(chan error)
+		go func() {
+			for {
+				var buf [1024]byte
+				n, err := s.Read(buf[:])
+				if err != nil {
+					errCh <- err
+					return
+				}
+				p := make([]byte, n)
+				copy(p, buf[:])
+				dataCh <- p
+			}
+		}()
+
+		// Write the data to the client side of the Conn, one chunk at a
+		// time.
+		for i, chunk := range test {
+			n, err := c.Write(chunk)
+			if err != nil || n != len(chunk) {
+				t.Fatalf("%+q Write chunk %d: got (%d, %v), expected (%d, %v)",
+					test, i, n, err, len(chunk), nil)
+			}
+		}
+		// We cannot immediately c.Close here, because that closes the
+		// connection right away, without waiting for buffered data to
+		// be sent.
+
+		// Pull data and err from the server goroutine above.
+		var data []byte
+		err = nil
+	loop:
+		for {
+			select {
+			case p := <-dataCh:
+				data = append(data, p...)
+			case err = <-errCh:
+				break loop
+			case <-time.After(100 * time.Millisecond):
+				break loop
+			}
+		}
+		s.Close()
+		c.Close()
+
+		// Now data and err contain the result of reading everything
+		// from s.
+		expected := bytes.Join(test, []byte{})
+		if err != nil || !bytes.Equal(data, expected) {
+			t.Fatalf("%+q ReadAll: got (%+q, %v), expected (%+q, %v)",
+				test, data, err, expected, nil)
+		}
+	}
+}
+
+// Test that multiple goroutines may call Read on a Conn simultaneously. Run
+// this with
+//   go test -race
+func TestConcurrentRead(t *testing.T) {
+	s, c, err := connPair()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer s.Close()
+
+	// Set up multiple threads reading from the same conn.
+	errCh := make(chan error, 2)
+	var wg sync.WaitGroup
+	wg.Add(2)
+	for i := 0; i < 2; i++ {
+		go func() {
+			defer wg.Done()
+			_, err := io.Copy(ioutil.Discard, s)
+			if err != nil {
+				errCh <- err
+			}
+		}()
+	}
+
+	// Write a bunch of data to the other end.
+	for i := 0; i < 2000; i++ {
+		_, err := fmt.Fprintf(c, "%d", i)
+		if err != nil {
+			c.Close()
+			t.Fatalf("Write: %v", err)
+		}
+	}
+	c.Close()
+
+	wg.Wait()
+	close(errCh)
+
+	err = <-errCh
+	if err != nil {
+		t.Fatalf("Read: %v", err)
+	}
+}
+
+// Test that multiple goroutines may call Write on a Conn simultaneously. Run
+// this with
+//   go test -race
+func TestConcurrentWrite(t *testing.T) {
+	s, c, err := connPair()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Set up multiple threads writing to the same conn.
+	errCh := make(chan error, 3)
+	var wg sync.WaitGroup
+	wg.Add(2)
+	for i := 0; i < 2; i++ {
+		go func() {
+			defer wg.Done()
+			for j := 0; j < 1000; j++ {
+				_, err := fmt.Fprintf(s, "%d", j)
+				if err != nil {
+					errCh <- err
+					break
+				}
+			}
+		}()
+	}
+	go func() {
+		wg.Wait()
+		err := s.Close()
+		if err != nil {
+			errCh <- err
+		}
+		close(errCh)
+	}()
+
+	// Read from the other end.
+	_, err = io.Copy(ioutil.Discard, c)
+	c.Close()
+	if err != nil {
+		t.Fatalf("Read: %v", err)
+	}
+
+	err = <-errCh
+	if err != nil {
+		t.Fatalf("Write: %v", err)
+	}
+}





More information about the tor-commits mailing list