Skip to content

Commit 225b84b

Browse files
generalize setup to allow for testing of other protocols
1 parent e0d83ed commit 225b84b

File tree

8 files changed

+400
-381
lines changed

8 files changed

+400
-381
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.json
5+
!implementations_quic.json
66
web/latest
77

88
*.egg-info/

implementations.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import json
22
from enum import Enum
3-
4-
IMPLEMENTATIONS = {}
3+
from typing import Dict
54

65

76
class Role(Enum):
@@ -10,16 +9,19 @@ class Role(Enum):
109
CLIENT = "client"
1110

1211

13-
with open("implementations.json", "r") as f:
14-
data = json.load(f)
15-
for name, val in data.items():
16-
IMPLEMENTATIONS[name] = {"image": val["image"], "url": val["url"]}
17-
role = val["role"]
18-
if role == "server":
19-
IMPLEMENTATIONS[name]["role"] = Role.SERVER
20-
elif role == "client":
21-
IMPLEMENTATIONS[name]["role"] = Role.CLIENT
22-
elif role == "both":
23-
IMPLEMENTATIONS[name]["role"] = Role.BOTH
24-
else:
25-
raise Exception("unknown role: " + role)
12+
def get_quic_implementations() -> Dict[str, Dict[str, str | Role]]:
13+
implementations: Dict[str, Dict[str, str | Role]] = {}
14+
with open("implementations_quic.json", "r") as f:
15+
data = json.load(f)
16+
for name, val in data.items():
17+
implementations[name] = {"image": val["image"], "url": val["url"]}
18+
role = val["role"]
19+
if role == "server":
20+
implementations[name]["role"] = Role.SERVER
21+
elif role == "client":
22+
implementations[name]["role"] = Role.CLIENT
23+
elif role == "both":
24+
implementations[name]["role"] = Role.BOTH
25+
else:
26+
raise Exception("unknown role: " + role)
27+
return implementations

interop.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
import tempfile
1111
from datetime import datetime
1212
from typing import Callable, List, Tuple
13-
1413
import prettytable
1514
from termcolor import colored
1615

17-
import testcases
16+
import testcases_quic
1817
from result import TestResult
19-
from testcases import Perspective
18+
from testcase import Perspective
2019

2120

2221
class MeasurementResult:
@@ -50,8 +49,8 @@ def __init__(
5049
self,
5150
implementations: dict,
5251
client_server_pairs: List[Tuple[str, str]],
53-
tests: List[testcases.TestCase],
54-
measurements: List[testcases.Measurement],
52+
tests: List[testcases_quic.TestCase],
53+
measurements: List[testcases_quic.Measurement],
5554
output: str,
5655
markdown: bool,
5756
debug: bool,
@@ -117,7 +116,7 @@ def _check_impl_is_compliant(self, name: str, role: Perspective) -> bool:
117116
dir="/tmp", prefix="compliance_downloads_"
118117
)
119118

120-
testcases.generate_cert_chain(certs_dir.name)
119+
testcases_quic.generate_cert_chain(certs_dir.name)
121120

122121
if role == Perspective.CLIENT:
123122
# check that the client is capable of returning UNSUPPORTED
@@ -305,8 +304,8 @@ def _export_results(self):
305304
}
306305
for x in self._tests + self._measurements
307306
},
308-
"quic_draft": testcases.QUIC_DRAFT,
309-
"quic_version": testcases.QUIC_VERSION,
307+
"quic_draft": testcases_quic.QUIC_DRAFT,
308+
"quic_version": testcases_quic.QUIC_VERSION,
310309
"results": [],
311310
"measurements": [],
312311
}
@@ -367,7 +366,7 @@ def _copy_logs(self, container: str, dir: tempfile.TemporaryDirectory):
367366
)
368367

369368
def _run_testcase(
370-
self, server: str, client: str, test: Callable[[], testcases.TestCase]
369+
self, server: str, client: str, test: Callable[[], testcases_quic.TestCase]
371370
) -> TestResult:
372371
return self._run_test(server, client, None, test)[0]
373372

@@ -376,7 +375,7 @@ def _run_test(
376375
server: str,
377376
client: str,
378377
log_dir_prefix: None,
379-
test: Callable[[], testcases.TestCase],
378+
fn: Callable[[], testcases_quic.TestCase],
380379
) -> Tuple[TestResult, float]:
381380
start_time = datetime.now()
382381
sim_log_dir = tempfile.TemporaryDirectory(dir="/tmp", prefix="logs_sim_")
@@ -390,7 +389,7 @@ def _run_test(
390389
log_handler.setFormatter(formatter)
391390
logging.getLogger().addHandler(log_handler)
392391

393-
testcase = test(
392+
test = fn(
394393
sim_log_dir=sim_log_dir,
395394
client_keylog_file=client_log_dir.name + "/keys.log",
396395
server_keylog_file=server_log_dir.name + "/keys.log",
@@ -401,27 +400,27 @@ def _run_test(
401400
+ ". Client: "
402401
+ client
403402
+ ". Running test case: "
404-
+ str(testcase)
403+
+ str(test)
405404
)
406405

407-
reqs = " ".join([testcase.urlprefix() + p for p in testcase.get_paths()])
406+
reqs = " ".join([test.urlprefix() + p for p in test.get_paths()])
408407
logging.debug("Requests: %s", reqs)
409408
params = (
410409
"WAITFORSERVER=server:443 "
411-
"CERTS=" + testcase.certs_dir() + " "
412-
"TESTCASE_SERVER=" + testcase.testname(Perspective.SERVER) + " "
413-
"TESTCASE_CLIENT=" + testcase.testname(Perspective.CLIENT) + " "
414-
"WWW=" + testcase.www_dir() + " "
415-
"DOWNLOADS=" + testcase.download_dir() + " "
410+
"CERTS=" + test.certs_dir() + " "
411+
"TESTCASE_SERVER=" + test.testname(Perspective.SERVER) + " "
412+
"TESTCASE_CLIENT=" + test.testname(Perspective.CLIENT) + " "
413+
"WWW=" + test.www_dir() + " "
414+
"DOWNLOADS=" + test.download_dir() + " "
416415
"SERVER_LOGS=" + server_log_dir.name + " "
417416
"CLIENT_LOGS=" + client_log_dir.name + " "
418417
'SCENARIO="{}" '
419418
"CLIENT=" + self._implementations[client]["image"] + " "
420419
"SERVER=" + self._implementations[server]["image"] + " "
421420
'REQUESTS="' + reqs + '" '
422-
).format(testcase.scenario())
423-
params += " ".join(testcase.additional_envs())
424-
containers = "sim client server " + " ".join(testcase.additional_containers())
421+
).format(test.scenario())
422+
params += " ".join(test.additional_envs())
423+
containers = "sim client server " + " ".join(test.additional_containers())
425424
cmd = (
426425
params
427426
+ " docker compose --env-file empty.env up --abort-on-container-exit --timeout 1 "
@@ -438,7 +437,7 @@ def _run_test(
438437
shell=True,
439438
stdout=subprocess.PIPE,
440439
stderr=subprocess.STDOUT,
441-
timeout=testcase.timeout(),
440+
timeout=test.timeout(),
442441
)
443442
output = r.stdout
444443
except subprocess.TimeoutExpired as ex:
@@ -448,7 +447,7 @@ def _run_test(
448447
logging.debug("%s", output.decode("utf-8", errors="replace"))
449448

450449
if expired:
451-
logging.debug("Test failed: took longer than %ds.", testcase.timeout())
450+
logging.debug("Test failed: took longer than %ds.", test.timeout())
452451
r = subprocess.run(
453452
"docker compose --env-file empty.env stop " + containers,
454453
shell=True,
@@ -469,7 +468,7 @@ def _run_test(
469468
status = TestResult.UNSUPPORTED
470469
elif any("client exited with code 0" in str(line) for line in lines):
471470
try:
472-
status = testcase.check()
471+
status = test.check()
473472
except FileNotFoundError as e:
474473
logging.error(f"testcase.check() threw FileNotFoundError: {e}")
475474
status = TestResult.FAILED
@@ -478,41 +477,41 @@ def _run_test(
478477
logging.getLogger().removeHandler(log_handler)
479478
log_handler.close()
480479
if status == TestResult.FAILED or status == TestResult.SUCCEEDED:
481-
log_dir = self._log_dir + "/" + server + "_" + client + "/" + str(testcase)
480+
log_dir = self._log_dir + "/" + server + "_" + client + "/" + str(test)
482481
if log_dir_prefix:
483482
log_dir += "/" + log_dir_prefix
484483
shutil.copytree(server_log_dir.name, log_dir + "/server")
485484
shutil.copytree(client_log_dir.name, log_dir + "/client")
486485
shutil.copytree(sim_log_dir.name, log_dir + "/sim")
487486
shutil.copyfile(log_file.name, log_dir + "/output.txt")
488487
if self._save_files and status == TestResult.FAILED:
489-
shutil.copytree(testcase.www_dir(), log_dir + "/www")
488+
shutil.copytree(test.www_dir(), log_dir + "/www")
490489
try:
491-
shutil.copytree(testcase.download_dir(), log_dir + "/downloads")
490+
shutil.copytree(test.download_dir(), log_dir + "/downloads")
492491
except Exception as exception:
493492
logging.info("Could not copy downloaded files: %s", exception)
494493

495-
testcase.cleanup()
494+
test.cleanup()
496495
server_log_dir.cleanup()
497496
client_log_dir.cleanup()
498497
sim_log_dir.cleanup()
499498
logging.debug(
500499
"Test: %s took %ss, status: %s",
501-
str(testcase),
500+
str(test),
502501
(datetime.now() - start_time).total_seconds(),
503502
str(status),
504503
)
505504

506505
# measurements also have a value
507-
if hasattr(testcase, "result"):
508-
value = testcase.result()
506+
if hasattr(test, "result"):
507+
value = test.result()
509508
else:
510509
value = None
511510

512511
return status, value
513512

514513
def _run_measurement(
515-
self, server: str, client: str, test: Callable[[], testcases.Measurement]
514+
self, server: str, client: str, test: Callable[[], testcases_quic.Measurement]
516515
) -> MeasurementResult:
517516
values = []
518517
for i in range(0, test.repetitions()):

pull.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import sys
44

5-
from implementations import IMPLEMENTATIONS
5+
from implementations import get_quic_implementations
66

77
print("Pulling the simulator...")
88
os.system("docker pull martenseemann/quic-network-simulator")
@@ -17,14 +17,15 @@ def get_args():
1717
return parser.parse_args()
1818

1919

20+
impls = get_quic_implementations()
2021
implementations = {}
2122
if get_args().implementations:
2223
for s in get_args().implementations.split(","):
23-
if s not in [n for n, _ in IMPLEMENTATIONS.items()]:
24+
if s not in [n for n, _ in impls.items()]:
2425
sys.exit("implementation " + s + " not found.")
25-
implementations[s] = IMPLEMENTATIONS[s]
26+
implementations[s] = impls[s]
2627
else:
27-
implementations = IMPLEMENTATIONS
28+
implementations = impls
2829

2930
for name, value in implementations.items():
3031
print("\nPulling " + name + "...")

run.py

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,29 @@
44
import sys
55
from typing import List, Tuple
66

7-
import testcases
8-
from implementations import IMPLEMENTATIONS, Role
7+
import testcase
8+
from implementations import Role, get_quic_implementations
99
from interop import InteropRunner
10-
from testcases import MEASUREMENTS, TESTCASES
11-
12-
implementations = {
13-
name: {"image": value["image"], "url": value["url"]}
14-
for name, value in IMPLEMENTATIONS.items()
15-
}
16-
client_implementations = [
17-
name
18-
for name, value in IMPLEMENTATIONS.items()
19-
if value["role"] == Role.BOTH or value["role"] == Role.CLIENT
20-
]
21-
server_implementations = [
22-
name
23-
for name, value in IMPLEMENTATIONS.items()
24-
if value["role"] == Role.BOTH or value["role"] == Role.SERVER
25-
]
10+
from testcases_quic import MEASUREMENTS, TESTCASES_QUIC
2611

2712

2813
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+
2930
def get_args():
3031
parser = argparse.ArgumentParser()
3132
parser.add_argument(
@@ -46,7 +47,7 @@ def get_args():
4647
"-t",
4748
"--test",
4849
help="test cases (comma-separatated). Valid test cases are: "
49-
+ ", ".join([x.name() for x in TESTCASES + MEASUREMENTS]),
50+
+ ", ".join([x.name() for x in TESTCASES_QUIC + MEASUREMENTS]),
5051
)
5152
parser.add_argument(
5253
"-r",
@@ -92,9 +93,9 @@ def get_args():
9293
if len(pair) != 2:
9394
sys.exit("Invalid format for replace")
9495
name, image = pair[0], pair[1]
95-
if name not in IMPLEMENTATIONS:
96+
if name not in impls:
9697
sys.exit("Implementation " + name + " not found.")
97-
implementations[name]["image"] = image
98+
implementations_all[name]["image"] = image
9899

99100
def get_impls(arg, availableImpls, role) -> List[str]:
100101
if not arg:
@@ -120,20 +121,20 @@ def get_impl_pairs(clients, servers, must_include) -> List[Tuple[str, str]]:
120121

121122
def get_tests_and_measurements(
122123
arg,
123-
) -> Tuple[List[testcases.TestCase], List[testcases.TestCase]]:
124+
) -> Tuple[List[testcase.TestCase], List[testcase.TestCase]]:
124125
if arg is None:
125-
return TESTCASES, MEASUREMENTS
126+
return TESTCASES_QUIC, MEASUREMENTS
126127
elif arg == "onlyTests":
127-
return TESTCASES, []
128+
return TESTCASES_QUIC, []
128129
elif arg == "onlyMeasurements":
129130
return [], MEASUREMENTS
130131
elif not arg:
131132
return []
132133
tests = []
133134
measurements = []
134135
for t in arg.split(","):
135-
if t in [tc.name() for tc in TESTCASES]:
136-
tests += [tc for tc in TESTCASES if tc.name() == t]
136+
if t in [tc.name() for tc in TESTCASES_QUIC]:
137+
tests += [tc for tc in TESTCASES_QUIC if tc.name() == t]
137138
elif t in [tc.name() for tc in MEASUREMENTS]:
138139
measurements += [tc for tc in MEASUREMENTS if tc.name() == t]
139140
else:
@@ -144,7 +145,7 @@ def get_tests_and_measurements(
144145
"Available measurements: {}"
145146
).format(
146147
t,
147-
", ".join([t.name() for t in TESTCASES]),
148+
", ".join([t.name() for t in TESTCASES_QUIC]),
148149
", ".join([t.name() for t in MEASUREMENTS]),
149150
)
150151
)
@@ -160,7 +161,7 @@ def get_tests_and_measurements(
160161
if len(kind) == 1:
161162
no_auto_unsupported.add(kind[0])
162163
return InteropRunner(
163-
implementations=implementations,
164+
implementations=implementations_all,
164165
client_server_pairs=get_impl_pairs(clients, servers, get_args().must_include),
165166
tests=t[0],
166167
measurements=t[1],

0 commit comments

Comments
 (0)