Skip to content

Commit 8a9a2c7

Browse files
committed
Rework "run vs serve" logic to require explicit wasi world
* adapters/pywasm.py: * adapters/wasm-micro-runtime.py: * adapters/wasmedge.py: * adapters/wasmtime.py: * adapters/wazero.py: * adapters/wizard.py: Take a WASI world as an additional argument. * test-runner/tests/test_test_case.py: * test-runner/tests/test_test_suite_runner.py: Update tests. * test-runner/wasi_test_runner/runtime_adapter.py: Plumb wasi_world through to runtime. * test-runner/wasi_test_runner/test_case.py (WasiWorld): New type. (WasiProposal): Remove http/service, it's not a proposal. (Config.from_file): Don't infer proposals from ops. Parse out a world. * test-runner/wasi_test_runner/test_suite_runner.py (TestCaseRunner.do_run): Pass world to adapter. * tests/rust/wasm32-wasip3/src/bin/http-service.json: Adapt.
1 parent 83a190d commit 8a9a2c7

12 files changed

Lines changed: 91 additions & 46 deletions

File tree

adapters/pywasm.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ def get_version() -> str:
2727
def get_wasi_versions() -> List[str]:
2828
return ["wasm32-wasip1"]
2929

30+
31+
def get_wasi_worlds() -> List[str]:
32+
return ["wasi:cli/command"]
33+
34+
3035
def compute_argv(test_path: str,
3136
args_env_dirs: Tuple[List[str], Dict[str, str], List[Tuple[Path, str]]],
3237
proposals: List[str],
38+
wasi_world: str,
3339
wasi_version: str) -> List[str]:
3440

3541
argv = []

adapters/wasm-micro-runtime.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ def get_wasi_versions() -> List[str]:
2828
return ["wasm32-wasip1"]
2929

3030

31+
def get_wasi_worlds() -> List[str]:
32+
return ["wasi:cli/command"]
33+
34+
3135
def compute_argv(test_path: str,
3236
args_env_dirs: Tuple[List[str], Dict[str, str], List[Tuple[Path, str]]],
3337
proposals: List[str],
38+
wasi_world: str,
3439
wasi_version: str) -> List[str]:
3540

3641
argv = []

adapters/wasmedge.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ def get_wasi_versions() -> List[str]:
2828
return ["wasm32-wasip1"]
2929

3030

31+
def get_wasi_worlds() -> List[str]:
32+
return ["wasi:cli/command"]
33+
34+
3135
def compute_argv(test_path: str,
3236
args_env_dirs: Tuple[List[str], Dict[str, str], List[Tuple[Path, str]]],
3337
proposals: List[str],
38+
wasi_world: str,
3439
wasi_version: str) -> List[str]:
3540

3641
argv = []

adapters/wasmtime.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,14 @@ def get_wasi_versions() -> List[str]:
2525
return ["wasm32-wasip1", "wasm32-wasip3"]
2626

2727

28+
def get_wasi_worlds() -> List[str]:
29+
return ["wasi:cli/command", "wasi:http/service"]
30+
31+
2832
def compute_argv(test_path: str,
2933
args_env_dirs: Tuple[List[str], Dict[str, str], List[Tuple[Path, str]]],
3034
proposals: List[str],
35+
wasi_world: str,
3136
wasi_version: str) -> List[str]:
3237

3338
argv = []
@@ -43,17 +48,26 @@ def compute_argv(test_path: str,
4348
argv += [test_path]
4449

4550
argv += args
46-
_add_wasi_version_options(argv, wasi_version, proposals)
51+
_add_wasi_version_options(argv, wasi_version, proposals, wasi_world)
4752
return argv
4853

4954

5055
# The user might provide WASMTIME="wasmtime --option -Sfoo". Let's
5156
# insert the options to choose the WASI version before the user's
5257
# options, so that the user can override our choices.
53-
def _add_wasi_version_options(argv: List[str], wasi_version: str, proposals: List[str]) -> None:
58+
def _add_wasi_version_options(argv: List[str], wasi_version: str,
59+
proposals: List[str], wasi_world: str) -> None:
5460
splice_pos = len(WASMTIME)
5561
while splice_pos > 1 and argv[splice_pos - 1].startswith("-"):
5662
splice_pos -= 1
63+
match wasi_world:
64+
case "wasi:cli/command":
65+
pass
66+
case "wasi:http/service":
67+
argv[splice_pos:splice_pos] = \
68+
["serve", "-Scli", "--addr=127.0.0.1:0"]
69+
splice_pos += 1
70+
5771
match wasi_version:
5872
case "wasm32-wasip1":
5973
pass
@@ -63,13 +77,6 @@ def _add_wasi_version_options(argv: List[str], wasi_version: str, proposals: Lis
6377
flags_from_proposals += ",http"
6478
if "sockets" in proposals:
6579
flags_from_proposals += ",inherit-network"
66-
if "http/service" in proposals:
67-
flags_from_proposals += ",cli"
68-
argv[splice_pos:splice_pos] = ["serve", "--addr=127.0.0.1:0"]
69-
splice_pos += 1
7080

7181
argv[splice_pos:splice_pos] = ["-Wcomponent-model-async",
7282
f"-Sp3{flags_from_proposals}"]
73-
74-
case _:
75-
pass

adapters/wazero.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@ def get_wasi_versions() -> List[str]:
3030
return ["wasm32-wasip1"]
3131

3232

33+
def get_wasi_worlds() -> List[str]:
34+
return ["wasi:cli/command"]
35+
36+
3337
def compute_argv(test_path: str,
3438
args_env_dirs: Tuple[List[str], Dict[str, str], List[Tuple[Path, str]]],
3539
proposals: List[str],
40+
wasi_world: str,
3641
wasi_version: str) -> List[str]:
3742

3843
argv = []

adapters/wizard.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@ def get_wasi_versions() -> List[str]:
3636
return ["wasm32-wasip1"]
3737

3838

39+
def get_wasi_worlds() -> List[str]:
40+
return ["wasi:cli/command"]
41+
42+
3943
def compute_argv(test_path: str,
4044
args_env_dirs: Tuple[List[str], Dict[str, str], List[Tuple[Path, str]]],
4145
proposals: List[str],
46+
wasi_world: str,
4247
wasi_version: str) -> List[str]:
4348

4449
argv = []

test-runner/tests/test_test_case.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from wasi_test_runner.test_case import (
1010
Config, Failure, Result,
1111
Run, Wait, Read, Write, Connect, Send, Recv, Request, Response, Kill,
12-
ProtocolType, WasiProposal, TestCaseValidator
12+
ProtocolType, WasiProposal, WasiWorld, TestCaseValidator
1313
)
1414

1515

@@ -225,19 +225,21 @@ def test_new_config_with_empty_proposals(_mock_file: Mock) -> None:
225225
config = Config.from_file("file")
226226

227227
assert len(config.proposals) == 0
228+
assert config.world == WasiWorld.CLI_COMMAND
228229

229230

230231
@patch(
231232
"builtins.open",
232233
new_callable=mock_open,
233-
read_data='{"operations": [{"type": "run"}], "proposals": ["http", "sockets"]}',
234+
read_data='{"operations": [{"type": "run"}], "proposals": ["http", "sockets"], "world": "wasi:http/service"}',
234235
)
235236
def test_new_config_with_multiple_proposals(_mock_file: Mock) -> None:
236237
config = Config.from_file("file")
237238

238239
assert len(config.proposals) == 2
239240
assert config.proposals[0] == WasiProposal.HTTP
240241
assert config.proposals[1] == WasiProposal.SOCKETS
242+
assert config.world == WasiWorld.HTTP_SERVICE
241243

242244

243245
@patch(
@@ -250,6 +252,16 @@ def test_new_config_should_fail_with_invalid_proposal(_mock_file: Mock) -> None:
250252
Config.from_file("file")
251253

252254

255+
@patch(
256+
"builtins.open",
257+
new_callable=mock_open,
258+
read_data='{"operations": [{"type": "run"}], world: "invalid"}',
259+
)
260+
def test_new_config_should_fail_with_invalid_world(_mock_file: Mock) -> None:
261+
with pytest.raises(ValueError):
262+
Config.from_file("file")
263+
264+
253265
def validate_config(config: Config) -> None:
254266
TestCaseValidator(config, 'test-config.json').validate()
255267

test-runner/tests/test_test_suite_runner.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ def open_mock(filename: str, *_args: Any, **_kwargs: Any) -> Any:
1717
"my-path/test3.json": '{"stdout": "output", "env": {"x": "1"}}',
1818
"my-path/test4.json": (
1919
'{"operations": [{"type": "run"}, {"type": "wait", "exit_code": 1}], '
20-
'"proposals": []}'
20+
'"proposals": [], '
21+
'"world": "wasi:http/service"}'
2122
),
2223
}
2324
if filename in file_content:
@@ -54,19 +55,23 @@ def test_runner_end_to_end() -> None:
5455
tc.Run(dirs=[(Path(test_suite_dir) / d, d) for d in test_dirs]),
5556
tc.Wait(exit_code=0)
5657
],
57-
proposals=[]
58+
proposals=[],
59+
world=tc.WasiWorld.CLI_COMMAND
5860
),
5961
tc.Config(
6062
operations=[tc.Run(args=["a", "b"]), tc.Wait(exit_code=1)],
61-
proposals=[]
63+
proposals=[],
64+
world=tc.WasiWorld.CLI_COMMAND
6265
),
6366
tc.Config(
6467
operations=[tc.Run(env={"x": "1"}), tc.Read(id="stdout", payload="output"), tc.Wait(exit_code=0)],
65-
proposals=[]
68+
proposals=[],
69+
world=tc.WasiWorld.CLI_COMMAND
6670
),
6771
tc.Config(
6872
operations=[tc.Run(), tc.Wait(exit_code=1)],
69-
proposals=[]
73+
proposals=[],
74+
world=tc.WasiWorld.HTTP_SERVICE
7075
),
7176
]
7277

test-runner/wasi_test_runner/runtime_adapter.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import NamedTuple, List, Tuple, Dict, Any
66

7-
from .test_case import WasiVersion
7+
from .test_case import WasiVersion, WasiWorld
88

99

1010
class RuntimeMeta(NamedTuple):
@@ -93,17 +93,20 @@ def get_meta(self) -> RuntimeMeta:
9393
return self._meta
9494

9595
def compute_argv(self, test_path: str,
96-
args: List[str], env: Dict[str, str],
96+
args: List[str],
97+
env: Dict[str, str],
9798
dirs: List[Tuple[Path, str]],
9899
proposals: List[str],
100+
wasi_world: WasiWorld,
99101
wasi_version: WasiVersion) -> List[str]:
100102
# too-many-positional-arguments is a post-3.0 pylint message.
101103
# pylint: disable-msg=unknown-option-value
102104
# pylint: disable-msg=too-many-arguments
103105
# pylint: disable-msg=too-many-positional-arguments
104106
args_env_dirs = [args, env, dirs]
105107
argv = self._adapter.compute_argv(test_path, args_env_dirs,
106-
proposals, wasi_version.value)
108+
proposals, wasi_world.value,
109+
wasi_version.value)
107110
assert isinstance(argv, list)
108111
assert all(isinstance(arg, str) for arg in argv)
109112
return argv

test-runner/wasi_test_runner/test_case.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77

88
# Top level configuration keys
99
LEGACY_CONFIG_KEYS = {"args", "dirs", "env", "exit_code", "stderr", "stdout"}
10-
CONFIG_KEYS = {"operations", "proposals"}
10+
CONFIG_KEYS = {"operations", "proposals", "world"}
1111

1212

1313
# Supported operations
1414
SUPPORTED_OPERATIONS = {"run", "wait", "read", "write", "connect",
1515
"send", "recv", "request", "kill"}
1616

1717

18+
class WasiWorld(StrEnum):
19+
CLI_COMMAND = 'wasi:cli/command'
20+
HTTP_SERVICE = 'wasi:http/service'
21+
22+
1823
class WasiVersion(StrEnum):
1924
WASM32_WASIP1 = 'wasm32-wasip1'
2025
WASM32_WASIP2 = 'wasm32-wasip2'
@@ -243,29 +248,9 @@ def from_config(cls: Type[K], config: Dict[str, Any]) -> K:
243248

244249
class WasiProposal(StrEnum):
245250
HTTP = 'http'
246-
HTTP_SERVICE = 'http/service'
247251
SOCKETS = 'sockets'
248252

249253

250-
def _infer_proposals_from_operations(ops: List[Operation]) -> List[WasiProposal]:
251-
sockets = False
252-
http_service = False
253-
for op in ops:
254-
match op:
255-
case Recv() | Send() | Connect():
256-
sockets = True
257-
case Request():
258-
http_service = True
259-
case _:
260-
pass
261-
ret = []
262-
if sockets:
263-
ret.append(WasiProposal.SOCKETS)
264-
if http_service:
265-
ret.append(WasiProposal.HTTP_SERVICE)
266-
return ret
267-
268-
269254
T = TypeVar("T", bound="Config")
270255

271256

@@ -274,6 +259,7 @@ class Config(NamedTuple):
274259
operations: List[Operation] = [Run(), Wait()]
275260
# WASI proposals needed for the test.
276261
proposals: List[WasiProposal] = []
262+
world: WasiWorld = WasiWorld.CLI_COMMAND
277263

278264
@classmethod
279265
def from_file(cls: Type[T], config_file: str) -> T:
@@ -288,12 +274,16 @@ def from_file(cls: Type[T], config_file: str) -> T:
288274
if dict_config.get("operations") is not None:
289275
operations = cls._operations_from_config(test_config_path, dict_config.get("operations"))
290276

291-
if dict_config.get("proposals") is None:
292-
proposals = _infer_proposals_from_operations(operations)
293-
else:
277+
proposals = []
278+
if dict_config.get("proposals") is not None:
294279
proposals = cls._proposals_from_config(dict_config.get("proposals"))
295280

296-
return cls(operations=operations, proposals=proposals)
281+
world = dict_config.get("world", WasiWorld.CLI_COMMAND.value)
282+
if world not in WasiWorld:
283+
raise ValueError(f"Unknown WASI world: {world}")
284+
285+
return cls(operations=operations, proposals=proposals,
286+
world=WasiWorld(world))
297287

298288
cls._validate_config(dict_config, LEGACY_CONFIG_KEYS)
299289

@@ -328,6 +318,7 @@ def from_file(cls: Type[T], config_file: str) -> T:
328318
# reliable, plus we'd be introducing a third level of
329319
# configuration.
330320
proposals=[],
321+
world=WasiWorld.CLI_COMMAND
331322
)
332323

333324
def proposals_as_str(self) -> List[str]:

0 commit comments

Comments
 (0)