@@ -12,10 +12,13 @@ import (
1212 "io"
1313 "io/ioutil"
1414 "math/rand"
15+ "net"
1516 "net/http"
1617 "net/http/cookiejar"
1718 "net/http/httptest"
1819 "net/url"
20+ "os"
21+ "os/exec"
1922 "reflect"
2023 "strconv"
2124 "strings"
@@ -1962,3 +1965,369 @@ func assertReadMessage(ctx context.Context, c *websocket.Conn, typ websocket.Mes
19621965 }
19631966 return assertEqualf (p , actP , "unexpected frame %v payload" , actTyp )
19641967}
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