@@ -12,10 +12,13 @@ import (
12
12
"io"
13
13
"io/ioutil"
14
14
"math/rand"
15
+ "net"
15
16
"net/http"
16
17
"net/http/cookiejar"
17
18
"net/http/httptest"
18
19
"net/url"
20
+ "os"
21
+ "os/exec"
19
22
"reflect"
20
23
"strconv"
21
24
"strings"
@@ -1962,3 +1965,369 @@ func assertReadMessage(ctx context.Context, c *websocket.Conn, typ websocket.Mes
1962
1965
}
1963
1966
return assertEqualf (p , actP , "unexpected frame %v payload" , actTyp )
1964
1967
}
1968
+
1969
+ func BenchmarkConn (b * testing.B ) {
1970
+ sizes := []int {
1971
+ 2 ,
1972
+ 16 ,
1973
+ 32 ,
1974
+ 512 ,
1975
+ 4096 ,
1976
+ 16384 ,
1977
+ }
1978
+
1979
+ b .Run ("write" , func (b * testing.B ) {
1980
+ for _ , size := range sizes {
1981
+ b .Run (strconv .Itoa (size ), func (b * testing.B ) {
1982
+ b .Run ("stream" , func (b * testing.B ) {
1983
+ benchConn (b , false , true , size )
1984
+ })
1985
+ b .Run ("buffer" , func (b * testing.B ) {
1986
+ benchConn (b , false , false , size )
1987
+ })
1988
+ })
1989
+ }
1990
+ })
1991
+
1992
+ b .Run ("echo" , func (b * testing.B ) {
1993
+ for _ , size := range sizes {
1994
+ b .Run (strconv .Itoa (size ), func (b * testing.B ) {
1995
+ benchConn (b , true , true , size )
1996
+ })
1997
+ }
1998
+ })
1999
+ }
2000
+
2001
+ func benchConn (b * testing.B , echo , stream bool , size int ) {
2002
+ s , closeFn := testServer (b , func (w http.ResponseWriter , r * http.Request ) error {
2003
+ c , err := websocket .Accept (w , r , nil )
2004
+ if err != nil {
2005
+ return err
2006
+ }
2007
+ if echo {
2008
+ wsecho .Loop (r .Context (), c )
2009
+ } else {
2010
+ discardLoop (r .Context (), c )
2011
+ }
2012
+ return nil
2013
+ }, false )
2014
+ defer closeFn ()
2015
+
2016
+ wsURL := strings .Replace (s .URL , "http" , "ws" , 1 )
2017
+
2018
+ ctx , cancel := context .WithTimeout (context .Background (), time .Minute * 5 )
2019
+ defer cancel ()
2020
+
2021
+ c , _ , err := websocket .Dial (ctx , wsURL , nil )
2022
+ if err != nil {
2023
+ b .Fatal (err )
2024
+ }
2025
+ defer c .Close (websocket .StatusInternalError , "" )
2026
+
2027
+ msg := []byte (strings .Repeat ("2" , size ))
2028
+ readBuf := make ([]byte , len (msg ))
2029
+ b .SetBytes (int64 (len (msg )))
2030
+ b .ReportAllocs ()
2031
+ b .ResetTimer ()
2032
+ for i := 0 ; i < b .N ; i ++ {
2033
+ if stream {
2034
+ w , err := c .Writer (ctx , websocket .MessageText )
2035
+ if err != nil {
2036
+ b .Fatal (err )
2037
+ }
2038
+
2039
+ _ , err = w .Write (msg )
2040
+ if err != nil {
2041
+ b .Fatal (err )
2042
+ }
2043
+
2044
+ err = w .Close ()
2045
+ if err != nil {
2046
+ b .Fatal (err )
2047
+ }
2048
+ } else {
2049
+ err = c .Write (ctx , websocket .MessageText , msg )
2050
+ if err != nil {
2051
+ b .Fatal (err )
2052
+ }
2053
+ }
2054
+
2055
+ if echo {
2056
+ _ , r , err := c .Reader (ctx )
2057
+ if err != nil {
2058
+ b .Fatal (err )
2059
+ }
2060
+
2061
+ _ , err = io .ReadFull (r , readBuf )
2062
+ if err != nil {
2063
+ b .Fatal (err )
2064
+ }
2065
+ }
2066
+ }
2067
+ b .StopTimer ()
2068
+
2069
+ c .Close (websocket .StatusNormalClosure , "" )
2070
+ }
2071
+
2072
+ func discardLoop (ctx context.Context , c * websocket.Conn ) {
2073
+ defer c .Close (websocket .StatusInternalError , "" )
2074
+
2075
+ ctx , cancel := context .WithTimeout (ctx , time .Minute )
2076
+ defer cancel ()
2077
+
2078
+ b := make ([]byte , 32768 )
2079
+ echo := func () error {
2080
+ _ , r , err := c .Reader (ctx )
2081
+ if err != nil {
2082
+ return err
2083
+ }
2084
+
2085
+ _ , err = io .CopyBuffer (ioutil .Discard , r , b )
2086
+ if err != nil {
2087
+ return err
2088
+ }
2089
+ return nil
2090
+ }
2091
+
2092
+ for {
2093
+ err := echo ()
2094
+ if err != nil {
2095
+ return
2096
+ }
2097
+ }
2098
+ }
2099
+
2100
+ func TestAutobahnPython (t * testing.T ) {
2101
+ // This test contains the old autobahn test suite tests that use the
2102
+ // python binary. The approach is clunky and slow so new tests
2103
+ // have been written in pure Go in websocket_test.go.
2104
+ // These have been kept for correctness purposes and are occasionally ran.
2105
+ if os .Getenv ("AUTOBAHN_PYTHON" ) == "" {
2106
+ t .Skip ("Set $AUTOBAHN_PYTHON to run tests against the python autobahn test suite" )
2107
+ }
2108
+
2109
+ t .Run ("server" , testServerAutobahnPython )
2110
+ t .Run ("client" , testClientAutobahnPython )
2111
+ }
2112
+
2113
+ // https://github.com/crossbario/autobahn-python/tree/master/wstest
2114
+ func testServerAutobahnPython (t * testing.T ) {
2115
+ t .Parallel ()
2116
+
2117
+ s := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
2118
+ c , err := websocket .Accept (w , r , & websocket.AcceptOptions {
2119
+ Subprotocols : []string {"echo" },
2120
+ })
2121
+ if err != nil {
2122
+ t .Logf ("server handshake failed: %+v" , err )
2123
+ return
2124
+ }
2125
+ wsecho .Loop (r .Context (), c )
2126
+ }))
2127
+ defer s .Close ()
2128
+
2129
+ spec := map [string ]interface {}{
2130
+ "outdir" : "ci/out/wstestServerReports" ,
2131
+ "servers" : []interface {}{
2132
+ map [string ]interface {}{
2133
+ "agent" : "main" ,
2134
+ "url" : strings .Replace (s .URL , "http" , "ws" , 1 ),
2135
+ },
2136
+ },
2137
+ "cases" : []string {"*" },
2138
+ // We skip the UTF-8 handling tests as there isn't any reason to reject invalid UTF-8, just
2139
+ // more performance overhead. 7.5.1 is the same.
2140
+ // 12.* and 13.* as we do not support compression.
2141
+ "exclude-cases" : []string {"6.*" , "7.5.1" , "12.*" , "13.*" },
2142
+ }
2143
+ specFile , err := ioutil .TempFile ("" , "websocketFuzzingClient.json" )
2144
+ if err != nil {
2145
+ t .Fatalf ("failed to create temp file for fuzzingclient.json: %v" , err )
2146
+ }
2147
+ defer specFile .Close ()
2148
+
2149
+ e := json .NewEncoder (specFile )
2150
+ e .SetIndent ("" , "\t " )
2151
+ err = e .Encode (spec )
2152
+ if err != nil {
2153
+ t .Fatalf ("failed to write spec: %v" , err )
2154
+ }
2155
+
2156
+ err = specFile .Close ()
2157
+ if err != nil {
2158
+ t .Fatalf ("failed to close file: %v" , err )
2159
+ }
2160
+
2161
+ ctx := context .Background ()
2162
+ ctx , cancel := context .WithTimeout (ctx , time .Minute * 10 )
2163
+ defer cancel ()
2164
+
2165
+ args := []string {"--mode" , "fuzzingclient" , "--spec" , specFile .Name ()}
2166
+ wstest := exec .CommandContext (ctx , "wstest" , args ... )
2167
+ out , err := wstest .CombinedOutput ()
2168
+ if err != nil {
2169
+ t .Fatalf ("failed to run wstest: %v\n out:\n %s" , err , out )
2170
+ }
2171
+
2172
+ checkWSTestIndex (t , "./ci/out/wstestServerReports/index.json" )
2173
+ }
2174
+
2175
+ func unusedListenAddr () (string , error ) {
2176
+ l , err := net .Listen ("tcp" , "localhost:0" )
2177
+ if err != nil {
2178
+ return "" , err
2179
+ }
2180
+ l .Close ()
2181
+ return l .Addr ().String (), nil
2182
+ }
2183
+
2184
+ // https://github.com/crossbario/autobahn-python/blob/master/wstest/testee_client_aio.py
2185
+ func testClientAutobahnPython (t * testing.T ) {
2186
+ t .Parallel ()
2187
+
2188
+ if os .Getenv ("AUTOBAHN_PYTHON" ) == "" {
2189
+ t .Skip ("Set $AUTOBAHN_PYTHON to test against the python autobahn test suite" )
2190
+ }
2191
+
2192
+ serverAddr , err := unusedListenAddr ()
2193
+ if err != nil {
2194
+ t .Fatalf ("failed to get unused listen addr for wstest: %v" , err )
2195
+ }
2196
+
2197
+ wsServerURL := "ws://" + serverAddr
2198
+
2199
+ spec := map [string ]interface {}{
2200
+ "url" : wsServerURL ,
2201
+ "outdir" : "ci/out/wstestClientReports" ,
2202
+ "cases" : []string {"*" },
2203
+ // See TestAutobahnServer for the reasons why we exclude these.
2204
+ "exclude-cases" : []string {"6.*" , "7.5.1" , "12.*" , "13.*" },
2205
+ }
2206
+ specFile , err := ioutil .TempFile ("" , "websocketFuzzingServer.json" )
2207
+ if err != nil {
2208
+ t .Fatalf ("failed to create temp file for fuzzingserver.json: %v" , err )
2209
+ }
2210
+ defer specFile .Close ()
2211
+
2212
+ e := json .NewEncoder (specFile )
2213
+ e .SetIndent ("" , "\t " )
2214
+ err = e .Encode (spec )
2215
+ if err != nil {
2216
+ t .Fatalf ("failed to write spec: %v" , err )
2217
+ }
2218
+
2219
+ err = specFile .Close ()
2220
+ if err != nil {
2221
+ t .Fatalf ("failed to close file: %v" , err )
2222
+ }
2223
+
2224
+ ctx := context .Background ()
2225
+ ctx , cancel := context .WithTimeout (ctx , time .Minute * 10 )
2226
+ defer cancel ()
2227
+
2228
+ args := []string {"--mode" , "fuzzingserver" , "--spec" , specFile .Name (),
2229
+ // Disables some server that runs as part of fuzzingserver mode.
2230
+ // See https://github.com/crossbario/autobahn-testsuite/blob/058db3a36b7c3a1edf68c282307c6b899ca4857f/autobahntestsuite/autobahntestsuite/wstest.py#L124
2231
+ "--webport=0" ,
2232
+ }
2233
+ wstest := exec .CommandContext (ctx , "wstest" , args ... )
2234
+ err = wstest .Start ()
2235
+ if err != nil {
2236
+ t .Fatal (err )
2237
+ }
2238
+ defer func () {
2239
+ err := wstest .Process .Kill ()
2240
+ if err != nil {
2241
+ t .Error (err )
2242
+ }
2243
+ }()
2244
+
2245
+ // Let it come up.
2246
+ time .Sleep (time .Second * 5 )
2247
+
2248
+ var cases int
2249
+ func () {
2250
+ c , _ , err := websocket .Dial (ctx , wsServerURL + "/getCaseCount" , nil )
2251
+ if err != nil {
2252
+ t .Fatal (err )
2253
+ }
2254
+ defer c .Close (websocket .StatusInternalError , "" )
2255
+
2256
+ _ , r , err := c .Reader (ctx )
2257
+ if err != nil {
2258
+ t .Fatal (err )
2259
+ }
2260
+ b , err := ioutil .ReadAll (r )
2261
+ if err != nil {
2262
+ t .Fatal (err )
2263
+ }
2264
+ cases , err = strconv .Atoi (string (b ))
2265
+ if err != nil {
2266
+ t .Fatal (err )
2267
+ }
2268
+
2269
+ c .Close (websocket .StatusNormalClosure , "" )
2270
+ }()
2271
+
2272
+ for i := 1 ; i <= cases ; i ++ {
2273
+ func () {
2274
+ ctx , cancel := context .WithTimeout (ctx , time .Second * 45 )
2275
+ defer cancel ()
2276
+
2277
+ c , _ , err := websocket .Dial (ctx , fmt .Sprintf (wsServerURL + "/runCase?case=%v&agent=main" , i ), nil )
2278
+ if err != nil {
2279
+ t .Fatal (err )
2280
+ }
2281
+ wsecho .Loop (ctx , c )
2282
+ }()
2283
+ }
2284
+
2285
+ c , _ , err := websocket .Dial (ctx , fmt .Sprintf (wsServerURL + "/updateReports?agent=main" ), nil )
2286
+ if err != nil {
2287
+ t .Fatal (err )
2288
+ }
2289
+ c .Close (websocket .StatusNormalClosure , "" )
2290
+
2291
+ checkWSTestIndex (t , "./ci/out/wstestClientReports/index.json" )
2292
+ }
2293
+
2294
+ func checkWSTestIndex (t * testing.T , path string ) {
2295
+ wstestOut , err := ioutil .ReadFile (path )
2296
+ if err != nil {
2297
+ t .Fatalf ("failed to read index.json: %v" , err )
2298
+ }
2299
+
2300
+ var indexJSON map [string ]map [string ]struct {
2301
+ Behavior string `json:"behavior"`
2302
+ BehaviorClose string `json:"behaviorClose"`
2303
+ }
2304
+ err = json .Unmarshal (wstestOut , & indexJSON )
2305
+ if err != nil {
2306
+ t .Fatalf ("failed to unmarshal index.json: %v" , err )
2307
+ }
2308
+
2309
+ var failed bool
2310
+ for _ , tests := range indexJSON {
2311
+ for test , result := range tests {
2312
+ switch result .Behavior {
2313
+ case "OK" , "NON-STRICT" , "INFORMATIONAL" :
2314
+ default :
2315
+ failed = true
2316
+ t .Errorf ("test %v failed" , test )
2317
+ }
2318
+ switch result .BehaviorClose {
2319
+ case "OK" , "INFORMATIONAL" :
2320
+ default :
2321
+ failed = true
2322
+ t .Errorf ("bad close behaviour for test %v" , test )
2323
+ }
2324
+ }
2325
+ }
2326
+
2327
+ if failed {
2328
+ path = strings .Replace (path , ".json" , ".html" , 1 )
2329
+ if os .Getenv ("CI" ) == "" {
2330
+ t .Errorf ("wstest found failure, see %q (output as an artifact in CI)" , path )
2331
+ }
2332
+ }
2333
+ }
0 commit comments