Skip to content

Commit 304228d

Browse files
authored
test: make sure conns are really closed after handshake (#64)
Slightly more thorough tests to complement #63.
1 parent 1c1c8fa commit 304228d

File tree

1 file changed

+45
-1
lines changed

1 file changed

+45
-1
lines changed

websocket_test.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net/http"
1414
"net/http/httptest"
1515
"os"
16+
"strings"
1617
"sync"
1718
"sync/atomic"
1819
"testing"
@@ -579,8 +580,17 @@ func TestProtocolErrors(t *testing.T) {
579580
opts = newOpts(t)
580581
}
581582
conn := setupRawConn(t, opts)
583+
584+
// write erronous or invalid frames to the connection, which should
585+
// cause the server to start the closing handshake
582586
mustWriteFrames(t, conn, !tc.unmasked, tc.frames)
587+
588+
// read expected close frame
583589
mustReadCloseFrame(t, conn, tc.wantCloseCode, tc.wantCloseReason)
590+
591+
// server closes connection immediately on protocol errors,
592+
// without waiting for client to reply
593+
assertConnClosed(t, conn)
584594
})
585595
}
586596
}
@@ -664,13 +674,15 @@ func TestCloseHandshake(t *testing.T) {
664674
go func() {
665675
defer wg.Done()
666676
assert.NilError(t, server.Close())
677+
assertConnClosed(t, serverConn)
667678
}()
668679
// client finishes closing handshake
669680
wg.Add(1)
670681
go func() {
671682
defer wg.Done()
672683
mustReadCloseFrame(t, clientConn, websocket.StatusNormalClosure, nil)
673684
mustWriteFrame(t, clientConn, true, websocket.NewCloseFrame(websocket.StatusNormalClosure, ""))
685+
assertConnClosed(t, clientConn)
674686
}()
675687
wg.Wait()
676688
})
@@ -691,6 +703,7 @@ func TestCloseHandshake(t *testing.T) {
691703
defer wg.Done()
692704
mustWriteFrame(t, clientConn, true, websocket.NewCloseFrame(websocket.StatusNormalClosure, ""))
693705
mustReadCloseFrame(t, clientConn, websocket.StatusNormalClosure, nil)
706+
assertConnClosed(t, serverConn)
694707
}()
695708
// server replies to close frame automatically during a ReadMessage
696709
// call, which returns io.EOF when the connection is closed.
@@ -700,6 +713,7 @@ func TestCloseHandshake(t *testing.T) {
700713
msg, err := server.ReadMessage(t.Context())
701714
assert.Equal(t, msg, nil, "expected nil message")
702715
assert.Error(t, err, io.EOF)
716+
assertConnClosed(t, clientConn)
703717
}()
704718
wg.Wait()
705719
})
@@ -726,12 +740,14 @@ func TestCloseHandshake(t *testing.T) {
726740
elapsed := time.Since(start)
727741
assert.Error(t, err, errors.New("websocket: close: read failed while waiting for reply: error reading frame header: read pipe: i/o timeout"))
728742
assert.Equal(t, elapsed > closeTimeout, true, "close should have waited for timeout")
743+
assertConnClosed(t, serverConn)
729744
}()
730745
// client gets the closing handshake message but ignores it
731746
wg.Add(1)
732747
go func() {
733748
defer wg.Done()
734749
mustReadCloseFrame(t, clientConn, websocket.StatusNormalClosure, nil)
750+
assertConnClosed(t, clientConn)
735751
}()
736752
wg.Wait()
737753
})
@@ -757,6 +773,7 @@ func TestCloseHandshake(t *testing.T) {
757773
defer wg.Done()
758774
err := server.Close()
759775
assert.NilError(t, err)
776+
assertConnClosed(t, serverConn)
760777
}()
761778
// client receives initial closing handshake but sends additional data
762779
// before closing its end.
@@ -769,6 +786,7 @@ func TestCloseHandshake(t *testing.T) {
769786
websocket.NewFrame(websocket.OpcodeText, true, []byte("ignore me")),
770787
websocket.NewCloseFrame(websocket.StatusNormalClosure, ""),
771788
})
789+
assertConnClosed(t, clientConn)
772790
}()
773791
wg.Wait()
774792
})
@@ -812,6 +830,7 @@ func TestCloseHandshake(t *testing.T) {
812830
// client starts closing handshake
813831
mustWriteFrame(t, conn, true, websocket.NewCloseFrame(websocket.StatusNormalClosure, ""))
814832
wg.Wait()
833+
assertConnClosed(t, conn)
815834
})
816835

817836
t.Run("server fails to initiate close", func(t *testing.T) {
@@ -821,7 +840,7 @@ func TestCloseHandshake(t *testing.T) {
821840

822841
var wg sync.WaitGroup
823842
wg.Add(1)
824-
setupRawConnWithHandler(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
843+
conn := setupRawConnWithHandler(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
825844
defer wg.Done()
826845

827846
// Manually re-implement websocket.Accept in order to wrap the
@@ -848,6 +867,7 @@ func TestCloseHandshake(t *testing.T) {
848867
assert.Error(t, ws.Close(), writeErr)
849868
}))
850869
wg.Wait()
870+
assertConnClosed(t, conn)
851871
})
852872
}
853873

@@ -1085,6 +1105,30 @@ func mustReadCloseFrame(t *testing.T, r io.Reader, wantCode websocket.StatusCode
10851105
func assertConnClosed(t testing.TB, conn net.Conn) {
10861106
t.Helper()
10871107
_, err := conn.Read(make([]byte, 1))
1108+
// testing this is super annoying, because we get at least four kinds of
1109+
// errors at various points in our test suite:
1110+
//
1111+
// - a *net.OpError with a non-deterministic strings containing random IP
1112+
// addresses and the substring "connection reset by peer"
1113+
// - an error string "connection reset by peer" (from net.Pipe conns)
1114+
// - a concrete io.EOF value
1115+
// - a concrete net.ErrClosed value
1116+
//
1117+
// The latter two are easy to test, the first one requires this ugly
1118+
// substring check.
1119+
assert.Equal(t, err != nil, true, "expected non-nil error when reading from closed connection")
1120+
1121+
// first check ugly substring matches
1122+
for _, substring := range []string{
1123+
"read/write on closed pipe",
1124+
"connection reset by peer",
1125+
} {
1126+
if strings.Contains(err.Error(), substring) {
1127+
return
1128+
}
1129+
}
1130+
1131+
// finally check for concrete errors
10881132
assert.Error(t, err, io.EOF, net.ErrClosed)
10891133
}
10901134

0 commit comments

Comments
 (0)