Skip to content

Commit c5985d5

Browse files
committed
Update compliance test suite.
1 parent 7fdd932 commit c5985d5

12 files changed

+234
-128
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ build:
88
python setup.py build_ext --inplace
99

1010
style:
11-
ruff format src tests
12-
ruff check --fix src tests
11+
ruff format compliance src tests
12+
ruff check --fix compliance src tests
1313

1414
types:
1515
mypy --strict src

compliance/README.rst

+48-26
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,69 @@ Autobahn Testsuite
44
General information and installation instructions are available at
55
https://github.com/crossbario/autobahn-testsuite.
66

7-
To improve performance, you should compile the C extension first::
7+
Running the test suite
8+
----------------------
9+
10+
All commands below must be run from the root directory of the repository.
11+
12+
To get acceptable performance, compile the C extension first:
13+
14+
.. code-block:: console
815
916
$ python setup.py build_ext --inplace
1017
11-
Running the test suite
12-
----------------------
18+
Run each command in a different shell. Testing takes several minutes to complete
19+
— wstest is the bottleneck. When clients finish, stop servers with Ctrl-C.
20+
21+
You can exclude slow tests by modifying the configuration files as follows::
22+
23+
"exclude-cases": ["9.*", "12.*", "13.*"]
1324

14-
All commands below must be run from the directory containing this file.
25+
The test server and client applications shouldn't display any exceptions.
1526

16-
To test the server::
27+
To test the servers:
1728

18-
$ PYTHONPATH=.. python test_server.py
19-
$ wstest -m fuzzingclient
29+
.. code-block:: console
2030
21-
To test the client::
31+
$ PYTHONPATH=src python compliance/asyncio/server.py
32+
$ PYTHONPATH=src python compliance/sync/server.py
2233
23-
$ wstest -m fuzzingserver
24-
$ PYTHONPATH=.. python test_client.py
34+
$ docker run --interactive --tty --rm \
35+
--volume "${PWD}/compliance/config:/config" \
36+
--volume "${PWD}/compliance/reports:/reports" \
37+
--name fuzzingclient \
38+
crossbario/autobahn-testsuite \
39+
wstest --mode fuzzingclient --spec /config/fuzzingclient.json
2540
26-
Run the first command in a shell. Run the second command in another shell.
27-
It should take about ten minutes to complete — wstest is the bottleneck.
28-
Then kill the first one with Ctrl-C.
41+
$ open reports/servers/index.html
2942
30-
The test client or server shouldn't display any exceptions. The results are
31-
stored in reports/clients/index.html.
43+
To test the clients:
3244

33-
Note that the Autobahn software only supports Python 2, while ``websockets``
34-
only supports Python 3; you need two different environments.
45+
.. code-block:: console
46+
$ docker run --interactive --tty --rm \
47+
--volume "${PWD}/compliance/config:/config" \
48+
--volume "${PWD}/compliance/reports:/reports" \
49+
--publish 9001:9001 \
50+
--name fuzzingserver \
51+
crossbario/autobahn-testsuite \
52+
wstest --mode fuzzingserver --spec /config/fuzzingserver.json
53+
54+
$ PYTHONPATH=src python compliance/asyncio/client.py
55+
$ PYTHONPATH=src python compliance/sync/client.py
56+
57+
$ open reports/clients/index.html
3558
3659
Conformance notes
3760
-----------------
3861

3962
Some test cases are more strict than the RFC. Given the implementation of the
40-
library and the test echo client or server, ``websockets`` gets a "Non-Strict"
41-
in these cases.
42-
43-
In 3.2, 3.3, 4.1.3, 4.1.4, 4.2.3, 4.2.4, and 5.15 ``websockets`` notices the
44-
protocol error and closes the connection before it has had a chance to echo
45-
the previous frame.
63+
library and the test client and server applications, websockets passes with a
64+
"Non-Strict" result in these cases.
4665

47-
In 6.4.3 and 6.4.4, even though it uses an incremental decoder, ``websockets``
48-
doesn't notice the invalid utf-8 fast enough to get a "Strict" pass. These
49-
tests are more strict than the RFC.
66+
In 3.2, 3.3, 4.1.3, 4.1.4, 4.2.3, 4.2.4, and 5.15 websockets notices the
67+
protocol error and closes the connection at the library level before the
68+
application gets a chance to echo the previous frame.
5069

70+
In 6.4.3 and 6.4.4, even though it uses an incremental decoder, websockets
71+
doesn't notice the invalid utf-8 fast enough to get a "Strict" pass. These tests
72+
are more strict than the RFC.

compliance/asyncio/client.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import asyncio
2+
import json
3+
import logging
4+
5+
from websockets.asyncio.client import connect
6+
from websockets.exceptions import WebSocketException
7+
8+
9+
logging.basicConfig(level=logging.WARNING)
10+
11+
SERVER = "ws://127.0.0.1:9001"
12+
13+
14+
async def get_case_count():
15+
async with connect(f"{SERVER}/getCaseCount") as ws:
16+
return json.loads(await ws.recv())
17+
18+
19+
async def run_case(case):
20+
async with connect(
21+
f"{SERVER}/runCase?case={case}",
22+
user_agent_header="websockets.asyncio",
23+
max_size=2**25,
24+
) as ws:
25+
async for msg in ws:
26+
await ws.send(msg)
27+
28+
29+
async def update_reports():
30+
async with connect(f"{SERVER}/updateReports", open_timeout=60):
31+
pass
32+
33+
34+
async def main():
35+
cases = await get_case_count()
36+
for case in range(1, cases + 1):
37+
print(f"Running test case {case:03d} / {cases}... ", end="\t")
38+
try:
39+
await run_case(case)
40+
except WebSocketException as exc:
41+
print(f"ERROR: {type(exc).__name__}: {exc}")
42+
except Exception as exc:
43+
print(f"FAIL: {type(exc).__name__}: {exc}")
44+
else:
45+
print("OK")
46+
print("Ran {cases} test cases")
47+
await update_reports()
48+
print("Updated reports")
49+
50+
51+
if __name__ == "__main__":
52+
asyncio.run(main())

compliance/asyncio/server.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import asyncio
2+
import logging
3+
4+
from websockets.asyncio.server import serve
5+
6+
7+
logging.basicConfig(level=logging.WARNING)
8+
9+
HOST, PORT = "0.0.0.0", 9002
10+
11+
12+
async def echo(ws):
13+
async for msg in ws:
14+
await ws.send(msg)
15+
16+
17+
async def main():
18+
async with serve(
19+
echo,
20+
HOST,
21+
PORT,
22+
server_header="websockets.sync",
23+
max_size=2**25,
24+
) as server:
25+
try:
26+
await server.serve_forever()
27+
except KeyboardInterrupt:
28+
pass
29+
30+
31+
if __name__ == "__main__":
32+
asyncio.run(main())

compliance/config/fuzzingclient.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
{
3+
"servers": [{
4+
"url": "ws://host.docker.internal:9002"
5+
}, {
6+
"url": "ws://host.docker.internal:9003"
7+
}],
8+
"outdir": "./reports/servers",
9+
"cases": ["*"],
10+
"exclude-cases": []
11+
}

compliance/config/fuzzingserver.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
{
3+
"url": "ws://localhost:9001",
4+
"outdir": "./reports/clients",
5+
"cases": ["*"],
6+
"exclude-cases": []
7+
}

compliance/fuzzingclient.json

-11
This file was deleted.

compliance/fuzzingserver.json

-12
This file was deleted.

compliance/sync/client.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import json
2+
import logging
3+
4+
from websockets.exceptions import WebSocketException
5+
from websockets.sync.client import connect
6+
7+
8+
logging.basicConfig(level=logging.WARNING)
9+
10+
SERVER = "ws://127.0.0.1:9001"
11+
12+
13+
def get_case_count():
14+
with connect(f"{SERVER}/getCaseCount") as ws:
15+
return json.loads(ws.recv())
16+
17+
18+
def run_case(case):
19+
with connect(
20+
f"{SERVER}/runCase?case={case}",
21+
user_agent_header="websockets.sync",
22+
max_size=2**25,
23+
) as ws:
24+
for msg in ws:
25+
ws.send(msg)
26+
27+
28+
def update_reports():
29+
with connect(f"{SERVER}/updateReports", open_timeout=60):
30+
pass
31+
32+
33+
def main():
34+
cases = get_case_count()
35+
for case in range(1, cases + 1):
36+
print(f"Running test case {case:03d} / {cases}... ", end="\t")
37+
try:
38+
run_case(case)
39+
except WebSocketException as exc:
40+
print(f"ERROR: {type(exc).__name__}: {exc}")
41+
except Exception as exc:
42+
print(f"FAIL: {type(exc).__name__}: {exc}")
43+
else:
44+
print("OK")
45+
print("Ran {cases} test cases")
46+
update_reports()
47+
print("Updated reports")
48+
49+
50+
if __name__ == "__main__":
51+
main()

compliance/sync/server.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import logging
2+
3+
from websockets.sync.server import serve
4+
5+
6+
logging.basicConfig(level=logging.WARNING)
7+
8+
HOST, PORT = "0.0.0.0", 9003
9+
10+
11+
def echo(ws):
12+
for msg in ws:
13+
ws.send(msg)
14+
15+
16+
def main():
17+
with serve(
18+
echo,
19+
HOST,
20+
PORT,
21+
server_header="websockets.asyncio",
22+
max_size=2**25,
23+
) as server:
24+
try:
25+
server.serve_forever()
26+
except KeyboardInterrupt:
27+
pass
28+
29+
30+
if __name__ == "__main__":
31+
main()

compliance/test_client.py

-48
This file was deleted.

0 commit comments

Comments
 (0)