Skip to content

Commit fc3b7e8

Browse files
add WebTransport handshake, stream and datagram tests
1 parent 453a686 commit fc3b7e8

File tree

11 files changed

+455
-88
lines changed

11 files changed

+455
-88
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ certs/
22
logs/
33
logs_*/
44
*.json
5-
!implementations_quic.json
5+
!implementations_*.json
66
web/latest
77

88
*.egg-info/

docker-compose.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ services:
4242
- SSLKEYLOGFILE=/logs/keys.log
4343
- QLOGDIR=/logs/qlog/
4444
- TESTCASE=$TESTCASE_SERVER
45+
- REQUESTS=$REQUESTS_SERVER
46+
- PROTOCOLS=$PROTOCOLS_SERVER
4547
depends_on:
4648
- sim
4749
cap_add:
@@ -74,7 +76,8 @@ services:
7476
- SSLKEYLOGFILE=/logs/keys.log
7577
- QLOGDIR=/logs/qlog/
7678
- TESTCASE=$TESTCASE_CLIENT
77-
- REQUESTS=$REQUESTS
79+
- REQUESTS=$REQUESTS_CLIENT
80+
- PROTOCOLS=$PROTOCOLS_CLIENT
7881
depends_on:
7982
- sim
8083
cap_add:

empty.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ TESTCASE_CLIENT=""
1212
TESTCASE_SERVER=""
1313
WWW=""
1414
WAITFORSERVER=""
15+
PROTOCOLS_CLIENT=""
16+
PROTOCOLS_SERVER=""

implementations.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ class Role(Enum):
1010

1111

1212
def get_quic_implementations() -> Dict[str, Dict[str, str | Role]]:
13+
return get_implementations("implementations_quic.json")
14+
15+
16+
def get_webtransport_implementations() -> Dict[str, Dict[str, str | Role]]:
17+
return get_implementations("implementations_webtransport.json")
18+
19+
20+
def get_implementations(filename: str) -> Dict[str, Dict[str, str | Role]]:
1321
implementations: Dict[str, Dict[str, str | Role]] = {}
14-
with open("implementations_quic.json", "r") as f:
22+
with open(filename, "r") as f:
1523
data = json.load(f)
1624
for name, val in data.items():
1725
implementations[name] = {"image": val["image"], "url": val["url"]}

implementations_webtransport.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"webtransport-go": {
3+
"image": "martenseemann/webtransport-go-interop:latest",
4+
"url": "https://github.com/quic-go/webtransport-go",
5+
"role": "both"
6+
},
7+
"chrome": {
8+
"image": "martenseemann/chrome-webtransport-interop-runner",
9+
"url": "https://github.com/quic-interop/chrome-webtransport-interop-runner",
10+
"role": "client"
11+
}
12+
}

interop.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ def _run_test(
413413
+ str(test)
414414
)
415415

416-
reqs = " ".join([test.urlprefix() + p for p in test.get_paths()])
416+
reqs = test.get_paths()
417417
logging.debug("Requests: %s", reqs)
418418
params = (
419419
"WAITFORSERVER=server:443 "
@@ -429,7 +429,8 @@ def _run_test(
429429
'SCENARIO="{}" '
430430
"CLIENT=" + self._implementations[client]["image"] + " "
431431
"SERVER=" + self._implementations[server]["image"] + " "
432-
'REQUESTS="' + reqs + '" '
432+
'REQUESTS_CLIENT="' + " ".join(reqs) + '" '
433+
'REQUESTS_SERVER="' + " ".join(test.get_paths_server()) + '" '
433434
).format(test.scenario())
434435
params += " ".join(test.additional_envs())
435436
containers = "sim client server " + " ".join(test.additional_containers())
@@ -476,9 +477,20 @@ def _run_test(
476477

477478
if not expired:
478479
lines = output.splitlines()
480+
exit_lines = [
481+
str(line) for line in lines if "exited with code" in str(line)
482+
]
483+
if exit_lines:
484+
logging.debug("Container exits: %s", " | ".join(exit_lines))
485+
client_exited_ok = any(
486+
"client exited with code 0" in str(line) for line in lines
487+
)
488+
server_exited_ok = any(
489+
"server exited with code 0" in str(line) for line in lines
490+
)
479491
if self._is_unsupported(lines):
480492
status = TestResult.UNSUPPORTED
481-
elif any("client exited with code 0" in str(line) for line in lines):
493+
elif client_exited_ok or server_exited_ok:
482494
try:
483495
status = test.check()
484496
except FileNotFoundError as e:

pull.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,44 @@
22
import os
33
import sys
44

5-
from implementations import get_quic_implementations
6-
7-
print("Pulling the simulator...")
8-
os.system("docker pull martenseemann/quic-network-simulator")
9-
10-
print("\nPulling the iperf endpoint...")
11-
os.system("docker pull martenseemann/quic-interop-iperf-endpoint")
5+
from implementations import get_quic_implementations, get_webtransport_implementations
126

137

148
def get_args():
159
parser = argparse.ArgumentParser()
10+
parser.add_argument(
11+
"-p",
12+
"--protocol",
13+
default="quic",
14+
help="quic / webtransport",
15+
)
1616
parser.add_argument("-i", "--implementations", help="implementations to pull")
1717
return parser.parse_args()
1818

1919

20-
impls = get_quic_implementations()
20+
args = get_args()
21+
if args.protocol == "quic":
22+
impls = get_quic_implementations()
23+
elif args.protocol == "webtransport":
24+
impls = get_webtransport_implementations()
25+
else:
26+
sys.exit("Unknown protocol: " + args.protocol)
2127
implementations = {}
22-
if get_args().implementations:
23-
for s in get_args().implementations.split(","):
28+
if args.implementations:
29+
for s in args.implementations.split(","):
2430
if s not in [n for n, _ in impls.items()]:
2531
sys.exit("implementation " + s + " not found.")
2632
implementations[s] = impls[s]
2733
else:
2834
implementations = impls
2935

36+
print("Pulling the simulator...")
37+
os.system("docker pull martenseemann/quic-network-simulator")
38+
39+
if args.protocol == "quic":
40+
print("\nPulling the iperf endpoint...")
41+
os.system("docker pull martenseemann/quic-interop-iperf-endpoint")
42+
3043
for name, value in implementations.items():
3144
print("\nPulling " + name + "...")
3245
os.system("docker pull " + value["image"])

run.py

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,25 @@
55
from typing import List, Tuple
66

77
import testcase
8-
from implementations import Role, get_quic_implementations
8+
from implementations import (
9+
Role,
10+
get_quic_implementations,
11+
get_webtransport_implementations,
12+
)
913
from interop import InteropRunner
1014
from testcases_quic import MEASUREMENTS, TESTCASES_QUIC
15+
from testcases_webtransport import TESTCASES_WEBTRANSPORT
1116

1217

1318
def main():
14-
impls = get_quic_implementations()
15-
implementations_all = {
16-
name: {"image": value["image"], "url": value["url"]}
17-
for name, value in impls.items()
18-
}
19-
client_implementations = [
20-
name
21-
for name, value in impls.items()
22-
if value["role"] == Role.BOTH or value["role"] == Role.CLIENT
23-
]
24-
server_implementations = [
25-
name
26-
for name, value in impls.items()
27-
if value["role"] == Role.BOTH or value["role"] == Role.SERVER
28-
]
29-
3019
def get_args():
3120
parser = argparse.ArgumentParser()
21+
parser.add_argument(
22+
"-p",
23+
"--protocol",
24+
default="quic",
25+
help="quic / webtransport",
26+
)
3227
parser.add_argument(
3328
"-d",
3429
"--debug",
@@ -86,6 +81,29 @@ def get_args():
8681
)
8782
return parser.parse_args()
8883

84+
protocol = get_args().protocol
85+
if protocol == "quic":
86+
impls = get_quic_implementations()
87+
elif protocol == "webtransport":
88+
impls = get_webtransport_implementations()
89+
else:
90+
sys.exit("Unknown protocol: " + protocol)
91+
92+
implementations_all = {
93+
name: {"image": value["image"], "url": value["url"]}
94+
for name, value in impls.items()
95+
}
96+
client_implementations = [
97+
name
98+
for name, value in impls.items()
99+
if value["role"] == Role.BOTH or value["role"] == Role.CLIENT
100+
]
101+
server_implementations = [
102+
name
103+
for name, value in impls.items()
104+
if value["role"] == Role.BOTH or value["role"] == Role.SERVER
105+
]
106+
89107
replace_arg = get_args().replace
90108
if replace_arg:
91109
for s in replace_arg.split(","):
@@ -122,19 +140,23 @@ def get_impl_pairs(clients, servers, must_include) -> List[Tuple[str, str]]:
122140
def get_tests_and_measurements(
123141
arg,
124142
) -> Tuple[List[testcase.TestCase], List[testcase.TestCase]]:
143+
if protocol == "quic":
144+
testcases = TESTCASES_QUIC
145+
elif protocol == "webtransport":
146+
testcases = TESTCASES_WEBTRANSPORT
125147
if arg is None:
126-
return TESTCASES_QUIC, MEASUREMENTS
148+
return testcases, MEASUREMENTS
127149
elif arg == "onlyTests":
128-
return TESTCASES_QUIC, []
150+
return testcases, []
129151
elif arg == "onlyMeasurements":
130152
return [], MEASUREMENTS
131153
elif not arg:
132154
return []
133155
tests = []
134156
measurements = []
135157
for t in arg.split(","):
136-
if t in [tc.name() for tc in TESTCASES_QUIC]:
137-
tests += [tc for tc in TESTCASES_QUIC if tc.name() == t]
158+
if t in [tc.name() for tc in testcases]:
159+
tests += [tc for tc in testcases if tc.name() == t]
138160
elif t in [tc.name() for tc in MEASUREMENTS]:
139161
measurements += [tc for tc in MEASUREMENTS if tc.name() == t]
140162
else:
@@ -145,7 +167,7 @@ def get_tests_and_measurements(
145167
"Available measurements: {}"
146168
).format(
147169
t,
148-
", ".join([t.name() for t in TESTCASES_QUIC]),
170+
", ".join([t.name() for t in testcases]),
149171
", ".join([t.name() for t in MEASUREMENTS]),
150172
)
151173
)

testcase.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,18 @@ def _server_trace(self):
189189
self._cached_server_trace = TraceAnalyzer(trace, self._keylog_file())
190190
return self._cached_server_trace
191191

192-
def _generate_random_file(self, size: int, filename: str = None) -> str:
192+
def _generate_random_file(
193+
self, size: int, filename: str = None, directory: str = None
194+
) -> str:
193195
if filename is None:
194196
filename = generate_slug()
195197
# see https://www.stefanocappellini.it/generate-pseudorandom-bytes-with-python/ for benchmarks
196198
enc = AES.new(os.urandom(32), AES.MODE_OFB, b"a" * 16)
197-
f = open(self.server_www_dir() + filename, "wb")
199+
if directory is not None:
200+
path = os.path.join(directory, filename)
201+
else:
202+
path = os.path.join(self.server_www_dir(), filename)
203+
f = open(path, "wb")
198204
f.write(enc.encrypt(b" " * size))
199205
f.close()
200206
logging.debug("Generated random file: %s of size: %d", filename, size)
@@ -213,29 +219,40 @@ def _check_version_and_files(self) -> bool:
213219
return False
214220
return self._check_files()
215221

216-
def _check_files(self) -> bool:
217-
if len(self._files) == 0:
222+
def _check_files(
223+
self, source_dir: str = None, download_dir: str = None, files: List[str] = None
224+
) -> bool:
225+
if source_dir is None:
226+
source_dir = self.server_www_dir()
227+
if download_dir is None:
228+
download_dir = self.client_download_dir()
229+
if files is None:
230+
files = self._files
231+
232+
if len(files) == 0:
218233
raise Exception("No test files generated.")
219-
files = [
234+
235+
downloaded_files = [
220236
n
221-
for n in os.listdir(self.client_download_dir())
222-
if os.path.isfile(os.path.join(self.client_download_dir(), n))
237+
for n in os.listdir(download_dir)
238+
if os.path.isfile(os.path.join(download_dir, n))
223239
]
224-
too_many = [f for f in files if f not in self._files]
240+
too_many = [f for f in downloaded_files if f not in files]
225241
if len(too_many) != 0:
226242
logging.info("Found unexpected downloaded files: %s", too_many)
227-
too_few = [f for f in self._files if f not in files]
243+
too_few = [f for f in files if f not in downloaded_files]
228244
if len(too_few) != 0:
229245
logging.info("Missing files: %s", too_few)
230246
if len(too_many) != 0 or len(too_few) != 0:
231247
return False
232-
for f in self._files:
233-
fp = self.client_download_dir() + f
248+
249+
for f in files:
250+
fp = os.path.join(download_dir, f)
234251
if not os.path.isfile(fp):
235252
logging.info("File %s does not exist.", fp)
236253
return False
237254
try:
238-
size = os.path.getsize(self.server_www_dir() + f)
255+
size = os.path.getsize(os.path.join(source_dir, f))
239256
downloaded_size = os.path.getsize(fp)
240257
if size != downloaded_size:
241258
logging.info(
@@ -245,13 +262,13 @@ def _check_files(self) -> bool:
245262
downloaded_size,
246263
)
247264
return False
248-
if not filecmp.cmp(self.server_www_dir() + f, fp, shallow=False):
265+
if not filecmp.cmp(os.path.join(source_dir, f), fp, shallow=False):
249266
logging.info("File contents of %s do not match.", fp)
250267
return False
251268
except Exception as exception:
252269
logging.info(
253270
"Could not compare files %s and %s: %s",
254-
self.server_www_dir() + f,
271+
os.path.join(source_dir, f),
255272
fp,
256273
exception,
257274
)
@@ -299,9 +316,14 @@ def cleanup(self):
299316
self._server_download_dir.cleanup()
300317
self._server_download_dir = None
301318

302-
@abc.abstractmethod
303319
def get_paths(self):
304-
pass
320+
return []
321+
322+
def get_paths_raw(self):
323+
return []
324+
325+
def get_paths_server(self):
326+
return []
305327

306328
@abc.abstractmethod
307329
def check(self) -> TestResult:

0 commit comments

Comments
 (0)