Skip to content

Commit 707b508

Browse files
authored
Test and lint examples (#117)
* Add test runs for examples * Add mypy linting
1 parent c5deb00 commit 707b508

File tree

10 files changed

+159
-33
lines changed

10 files changed

+159
-33
lines changed

.github/workflows/lint_examples.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
export COMPONENTIZE_PY_TEST_COUNT=0
6+
export COMPONENTIZE_PY_TEST_SEED=bc6ad1950594f1fe477144ef5b3669dd5962e49de4f3b666e5cbf9072507749a
7+
export WASMTIME_BACKTRACE_DETAILS=1
8+
9+
cargo build --release
10+
11+
# CLI
12+
(cd examples/cli \
13+
&& rm -rf command || true \
14+
&& ../../target/release/componentize-py -d ../../wit -w wasi:cli/command@0.2.0 bindings . \
15+
&& mypy --strict .)
16+
17+
# HTTP
18+
# poll_loop.py has many errors that might not be worth adjusting at the moment, so ignore for now
19+
(cd examples/http \
20+
&& rm -rf proxy || true \
21+
&& ../../target/release/componentize-py -d ../../wit -w wasi:http/proxy@0.2.0 bindings . \
22+
&& mypy --strict --ignore-missing-imports -m app -p proxy)
23+
24+
# # Matrix Math
25+
(cd examples/matrix-math \
26+
&& rm -rf matrix_math || true \
27+
&& curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz \
28+
&& tar xf numpy-wasi.tar.gz \
29+
&& ../../target/release/componentize-py -d ../../wit -w matrix-math bindings . \
30+
&& mypy --strict --follow-imports silent -m app -p matrix_math)
31+
32+
# Sandbox
33+
(cd examples/sandbox \
34+
&& rm -rf sandbox || true \
35+
&& ../../target/release/componentize-py -d sandbox.wit bindings . \
36+
&& mypy --strict -m guest -p sandbox)
37+
38+
# TCP
39+
(cd examples/tcp \
40+
&& rm -rf command || true \
41+
&& ../../target/release/componentize-py -d ../../wit -w wasi:cli/command@0.2.0 bindings . \
42+
&& mypy --strict .)

.github/workflows/test.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,17 @@ jobs:
8282
- name: Test
8383
shell: bash
8484
run: COMPONENTIZE_PY_TEST_COUNT=20 PROPTEST_MAX_SHRINK_ITERS=0 cargo test --release
85+
86+
- uses: taiki-e/install-action@v2
87+
with:
88+
tool: wasmtime-cli
89+
- uses: actions/setup-python@v5
90+
with:
91+
python-version: "3.12"
92+
- run: pip install wasmtime mypy
93+
- name: Test examples
94+
shell: bash
95+
run: bash .github/workflows/test_examples.sh
96+
- name: Lint examples
97+
shell: bash
98+
run: bash .github/workflows/lint_examples.sh

.github/workflows/test_examples.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
export COMPONENTIZE_PY_TEST_COUNT=0
6+
export COMPONENTIZE_PY_TEST_SEED=bc6ad1950594f1fe477144ef5b3669dd5962e49de4f3b666e5cbf9072507749a
7+
export WASMTIME_BACKTRACE_DETAILS=1
8+
9+
cargo build --release
10+
11+
# CLI
12+
(cd examples/cli \
13+
&& ../../target/release/componentize-py -d ../../wit -w wasi:cli/command@0.2.0 componentize app -o cli.wasm \
14+
&& wasmtime run cli.wasm)
15+
16+
# HTTP
17+
# Just compiling for now
18+
(cd examples/http \
19+
&& ../../target/release/componentize-py -d ../../wit -w wasi:http/proxy@0.2.0 componentize app -o http.wasm)
20+
21+
# Matrix Math
22+
(cd examples/matrix-math \
23+
&& curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz \
24+
&& tar xf numpy-wasi.tar.gz \
25+
&& ../../target/release/componentize-py -d ../../wit -w matrix-math componentize app -o matrix-math.wasm \
26+
&& wasmtime run matrix-math.wasm '[[1, 2], [4, 5], [6, 7]]' '[[1, 2, 3], [4, 5, 6]]')
27+
28+
# Sandbox
29+
(cd examples/sandbox \
30+
&& ../../target/release/componentize-py -d sandbox.wit componentize --stub-wasi guest -o sandbox.wasm \
31+
&& python -m wasmtime.bindgen sandbox.wasm --out-dir sandbox \
32+
&& python host.py "2 + 2")
33+
34+
# TCP
35+
# Just compiling for now
36+
(cd examples/tcp \
37+
&& ../../target/release/componentize-py -d ../../wit -w wasi:cli/command@0.2.0 componentize app -o tcp.wasm)

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
target
22
__pycache__
3+
.mypy_cache
34
.venv
45
bacon.toml
56
dist
@@ -9,7 +10,11 @@ examples/matrix-math/matrix_math
910
examples/matrix-math/wasmtime-py
1011
examples/http/.spin
1112
examples/http/http.wasm
13+
examples/http/proxy
14+
examples/http/poll_loop.py
1215
examples/tcp/tcp.wasm
16+
examples/tcp/command
1317
examples/cli/cli.wasm
18+
examples/cli/command
1419
examples/sandbox/sandbox
1520
examples/sandbox/sandbox.wasm

examples/cli/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from command import exports
22

3+
34
class Run(exports.Run):
4-
def run(self):
5+
def run(self) -> None:
56
print("Hello, world!")

examples/http/app.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,39 @@
1313
from proxy.types import Ok
1414
from proxy.imports import types
1515
from proxy.imports.types import (
16-
Method_Get, Method_Post, Scheme, Scheme_Http, Scheme_Https, Scheme_Other, IncomingRequest, ResponseOutparam,
17-
OutgoingResponse, Fields, OutgoingBody, OutgoingRequest
16+
Method_Get,
17+
Method_Post,
18+
Scheme,
19+
Scheme_Http,
20+
Scheme_Https,
21+
Scheme_Other,
22+
IncomingRequest,
23+
ResponseOutparam,
24+
OutgoingResponse,
25+
Fields,
26+
OutgoingBody,
27+
OutgoingRequest,
1828
)
1929
from poll_loop import Stream, Sink, PollLoop
2030
from typing import Tuple
2131
from urllib import parse
2232

33+
2334
class IncomingHandler(exports.IncomingHandler):
2435
"""Implements the `export`ed portion of the `wasi-http` `proxy` world."""
2536

26-
def handle(self, request: IncomingRequest, response_out: ResponseOutparam):
27-
"""Handle the specified `request`, sending the response to `response_out`.
28-
29-
"""
37+
def handle(self, request: IncomingRequest, response_out: ResponseOutparam) -> None:
38+
"""Handle the specified `request`, sending the response to `response_out`."""
3039
# Dispatch the request using `asyncio`, backed by a custom event loop
3140
# based on WASI's `poll_oneoff` function.
3241
loop = PollLoop()
3342
asyncio.set_event_loop(loop)
3443
loop.run_until_complete(handle_async(request, response_out))
3544

36-
async def handle_async(request: IncomingRequest, response_out: ResponseOutparam):
45+
46+
async def handle_async(
47+
request: IncomingRequest, response_out: ResponseOutparam
48+
) -> None:
3749
"""Handle the specified `request`, sending the response to `response_out`."""
3850

3951
method = request.method()
@@ -46,7 +58,10 @@ async def handle_async(request: IncomingRequest, response_out: ResponseOutparam)
4658
# buffering the response bodies), and stream the results back to the
4759
# client as they become available.
4860

49-
urls = map(lambda pair: str(pair[1], "utf-8"), filter(lambda pair: pair[0] == "url", headers))
61+
urls = map(
62+
lambda pair: str(pair[1], "utf-8"),
63+
filter(lambda pair: pair[0] == "url", headers),
64+
)
5065

5166
response = OutgoingResponse(Fields.from_list([("content-type", b"text/plain")]))
5267

@@ -64,7 +79,11 @@ async def handle_async(request: IncomingRequest, response_out: ResponseOutparam)
6479
elif isinstance(method, Method_Post) and path == "/echo":
6580
# Echo the request body back to the client without buffering.
6681

67-
response = OutgoingResponse(Fields.from_list(list(filter(lambda pair: pair[0] == "content-type", headers))))
82+
response = OutgoingResponse(
83+
Fields.from_list(
84+
list(filter(lambda pair: pair[0] == "content-type", headers))
85+
)
86+
)
6887

6988
response_body = response.body()
7089

@@ -87,6 +106,7 @@ async def handle_async(request: IncomingRequest, response_out: ResponseOutparam)
87106
ResponseOutparam.set(response_out, Ok(response))
88107
OutgoingBody.finish(body, None)
89108

109+
90110
async def sha256(url: str) -> Tuple[str, str]:
91111
"""Download the contents of the specified URL, computing the SHA-256
92112
incrementally as the response body arrives.

examples/matrix-math/app.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
from matrix_math import exports
88
from matrix_math.types import Err
99

10+
1011
class MatrixMath(matrix_math.MatrixMath):
1112
def multiply(self, a: list[list[float]], b: list[list[float]]) -> list[list[float]]:
1213
print(f"matrix_multiply received arguments {a} and {b}")
13-
return numpy.matmul(a, b).tolist()
14+
return numpy.matmul(a, b).tolist() # type: ignore
15+
1416

1517
class Run(exports.Run):
16-
def run(self):
18+
def run(self) -> None:
1719
args = sys.argv[1:]
1820
if len(args) != 2:
19-
print(f"usage: matrix-math <matrix> <matrix>", file=sys.stderr)
21+
print("usage: matrix-math <matrix> <matrix>", file=sys.stderr)
2022
exit(-1)
2123

2224
print(MatrixMath().multiply(eval(args[0]), eval(args[1])))
23-

examples/sandbox/guest.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22
from sandbox.types import Err
33
import json
44

5-
def handle(e: Exception):
5+
6+
def handle(e: Exception) -> Err[str]:
67
message = str(e)
7-
if message == '':
8-
raise Err(f"{type(e).__name__}")
8+
if message == "":
9+
return Err(f"{type(e).__name__}")
910
else:
10-
raise Err(f"{type(e).__name__}: {message}")
11+
return Err(f"{type(e).__name__}: {message}")
12+
1113

1214
class Sandbox(sandbox.Sandbox):
1315
def eval(self, expression: str) -> str:
1416
try:
1517
return json.dumps(eval(expression))
1618
except Exception as e:
17-
handle(e)
19+
raise handle(e)
1820

19-
def exec(self, statements: str):
21+
def exec(self, statements: str) -> None:
2022
try:
2123
exec(statements)
2224
except Exception as e:
23-
handle(e)
25+
raise handle(e)

examples/tcp/app.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,28 @@
55
from command import exports
66
from typing import Tuple
77

8+
89
class Run(exports.Run):
9-
def run(self):
10+
def run(self) -> None:
1011
args = sys.argv[1:]
1112
if len(args) != 1:
12-
print(f"usage: tcp <address>:<port>", file=sys.stderr)
13+
print("usage: tcp <address>:<port>", file=sys.stderr)
1314
exit(-1)
1415

1516
address, port = parse_address_and_port(args[0])
1617
asyncio.run(send_and_receive(address, port))
1718

19+
1820
IPAddress = IPv4Address | IPv6Address
19-
21+
22+
2023
def parse_address_and_port(address_and_port: str) -> Tuple[IPAddress, int]:
21-
ip, separator, port = address_and_port.rpartition(':')
24+
ip, separator, port = address_and_port.rpartition(":")
2225
assert separator
2326
return (ipaddress.ip_address(ip.strip("[]")), int(port))
24-
25-
async def send_and_receive(address: IPAddress, port: int):
27+
28+
29+
async def send_and_receive(address: IPAddress, port: int) -> None:
2630
rx, tx = await asyncio.open_connection(str(address), port)
2731

2832
tx.write(b"hello, world!")
@@ -33,4 +37,3 @@ async def send_and_receive(address: IPAddress, port: int):
3337

3438
tx.close()
3539
await tx.wait_closed()
36-

src/summary.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,14 +1348,14 @@ class {camel}(Flag):
13481348
if stub_runtime_calls {
13491349
format!(
13501350
"
1351-
def {snake}({params}):
1351+
def {snake}({params}){return_type}:
13521352
{docs}{NOT_IMPLEMENTED}
13531353
"
13541354
)
13551355
} else {
13561356
format!(
13571357
"
1358-
def {snake}({params}):
1358+
def {snake}({params}){return_type}:
13591359
{docs}tmp = componentize_py_runtime.call_import({index}, [{args}], {result_count})[0]
13601360
(_, func, args, _) = tmp.finalizer.detach()
13611361
self.handle = tmp.handle
@@ -1403,21 +1403,21 @@ class {camel}(Flag):
14031403
let docs =
14041404
format!(r#""""{newline}{indent}{doc}{newline}{indent}"""{newline}{indent}"#);
14051405
let enter = r#"
1406-
def __enter__(self):
1406+
def __enter__(self) -> Self:
14071407
"""Returns self"""
14081408
return self
14091409
"#;
14101410
if stub_runtime_calls {
14111411
format!(
14121412
"{enter}
1413-
def __exit__(self, *args):
1413+
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None:
14141414
{docs}{NOT_IMPLEMENTED}
14151415
"
14161416
)
14171417
} else {
14181418
format!(
14191419
"{enter}
1420-
def __exit__(self, *args):
1420+
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None:
14211421
{docs}(_, func, args, _) = self.finalizer.detach()
14221422
self.handle = None
14231423
func(args[0], args[1])
@@ -1746,6 +1746,7 @@ def {snake}({params}){return_type}:
17461746

17471747
let python_imports =
17481748
"from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
1749+
from types import TracebackType
17491750
from enum import Flag, Enum, auto
17501751
from dataclasses import dataclass
17511752
from abc import abstractmethod

0 commit comments

Comments
 (0)