-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Each test case is now in the same directory with the corresponding WIT file and guest code (app.py). * The helper functions for generating wasm components and host bindings is moved to a pytest fixture to avoid relative package issues when using direct imports. * Remove the use of changing the cwd in the test process.
- Loading branch information
Showing
7 changed files
with
195 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
[pytest] | ||
addopts = --doctest-modules --mypy | ||
addopts = --doctest-modules --mypy --ignore-glob=tests/bindgen/*/app.py | ||
norecursedirs = | ||
tests/bindgen/* | ||
tests/bindgen/generated/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,140 +0,0 @@ | ||
"""Test suite for testing generated code with guest code written in Python. | ||
These tests work by allowing you to write a WIT file, implement the guest | ||
code in Python via componentize-py, and then test the generated Python | ||
bindings. To add a new test, first create the needed fixtures: | ||
* Create a new sub directory. | ||
* Within that directory create a `.wit` file. | ||
* Create an `app.py` file in that directory implementing the guest code. | ||
Then to write the test itself: | ||
* Define the params of the testcase with `BindgenTestCase`. | ||
* Generate the Python bindings using `generate_bindings()`. This will also | ||
build the `app.wasm` file using `componentize-py`. | ||
* `generate_bindings()` returns the store and the instantiated `Root` object | ||
which you can then test. | ||
## Example | ||
Given this directory: | ||
``` | ||
bare_funcs/ | ||
├── app.py <-- guest code implementation | ||
├── barefuncs <-- componentize-py bindings | ||
│ ├── __init__.py | ||
│ └── types.py | ||
└── component.wit <-- test .wit file | ||
``` | ||
With a `component.wit` file of: | ||
```wit | ||
package component:barefuncs; | ||
world barefuncs { | ||
export foo: func(a: s32) -> s32; | ||
} | ||
``` | ||
And guest code of: | ||
```python | ||
class Barefuncs: | ||
def foo(self, a: int) -> int: | ||
return a + 1 | ||
``` | ||
You can write a testcase for this using: | ||
```python | ||
def test_bare_funcs(): | ||
testcase = BindgenTestCase( | ||
guest_code_dir='bare_funcs', | ||
world_name='barefuncs', | ||
) | ||
store, root = generate_bindings(testcase) | ||
assert root.foo(store, 10) == 11 | ||
``` | ||
""" | ||
import os | ||
from pathlib import Path | ||
from dataclasses import dataclass, field | ||
from wasmtime.bindgen import generate | ||
import wasmtime | ||
import contextlib | ||
import importlib | ||
import tempfile | ||
import subprocess | ||
import shutil | ||
|
||
|
||
TEST_ROOT = Path(__file__).parent | ||
BINDGEN_DIR = TEST_ROOT / 'generated' | ||
|
||
|
||
@contextlib.contextmanager | ||
def chdir(dirname: Path): | ||
original = os.getcwd() | ||
try: | ||
os.chdir(str(dirname)) | ||
yield | ||
finally: | ||
os.chdir(original) | ||
|
||
|
||
@dataclass | ||
class BindgenTestCase: | ||
world_name: str | ||
guest_code_dir: str | ||
app_dir: Path = field(init=False) | ||
wit_filename: str = 'component.wit' | ||
app_name: str = 'app' | ||
|
||
def __post_init__(self): | ||
self.app_dir = TEST_ROOT / self.guest_code_dir | ||
|
||
@property | ||
def wit_full_path(self): | ||
return self.app_dir.joinpath(self.wit_filename) | ||
|
||
@property | ||
def testsuite_name(self): | ||
# The name of the directory that contains the | ||
# guest Python code is used as the identifier for | ||
# package names, etc. | ||
return self.app_dir.name | ||
|
||
|
||
def generate_bindings(testcase: BindgenTestCase): | ||
wit_path = testcase.wit_full_path | ||
componentize_py = shutil.which('componentize-py') | ||
if componentize_py is None: | ||
raise RuntimeError("Could not find componentize-py executable.") | ||
with tempfile.NamedTemporaryFile('w') as f: | ||
output_wasm = str(f.name + '.wasm') | ||
with chdir(testcase.app_dir): | ||
subprocess.run([ | ||
componentize_py, '-d', str(wit_path), '-w', testcase.world_name, | ||
'componentize', '--stub-wasi', testcase.app_name, | ||
'-o', output_wasm | ||
], check=True) | ||
# Once we've done that now generate the python bindings. | ||
testsuite_name = testcase.testsuite_name | ||
with open(output_wasm, 'rb') as out: | ||
# Mapping of filename -> content_bytes | ||
results = generate(testsuite_name, out.read()) | ||
for filename, contents in results.items(): | ||
path = BINDGEN_DIR / testsuite_name / filename | ||
path.parent.mkdir(parents=True, exist_ok=True) | ||
path.write_bytes(contents) | ||
# Return an instantiated module for the caller to test. | ||
pkg = importlib.import_module(f'.generated.{testsuite_name}', | ||
package=__package__) | ||
store = wasmtime.Store() | ||
root = pkg.Root(store) | ||
return store, root | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from pathlib import Path | ||
|
||
|
||
def test_bare_funcs(bindgen_testcase): | ||
store, root = bindgen_testcase( | ||
guest_code_dir=Path(__file__).parent, | ||
world_name='barefuncs', | ||
) | ||
assert root.foo(store, 10) == 11 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
"""Fixtures to define test suites for generated code Python guest code. | ||
These tests work by allowing you to write a WIT file, implement the guest | ||
code in Python via componentize-py, and then test the generated Python | ||
bindings. To add a new test, first create the needed fixtures: | ||
* Create a new sub directory. | ||
* Within that directory create a `.wit` file. | ||
* Create an `app.py` file in that directory implementing the guest code. | ||
Then to write the test itself: | ||
* Define the params of the testcase with `BindgenTestCase`. | ||
* Generate the Python bindings using `generate_bindings()`. This will also | ||
build the `app.wasm` file using `componentize-py`. | ||
* `generate_bindings()` returns the store and the instantiated `Root` object | ||
which you can then test. | ||
## Example | ||
Given this directory: | ||
``` | ||
bare_funcs/ | ||
├── app.py <-- guest code implementation | ||
├── barefuncs <-- componentize-py bindings | ||
│ ├── __init__.py | ||
│ └── types.py | ||
└── component.wit <-- test .wit file | ||
``` | ||
With a `component.wit` file of: | ||
```wit | ||
package component:barefuncs; | ||
world barefuncs { | ||
export foo: func(a: s32) -> s32; | ||
} | ||
``` | ||
And guest code of: | ||
```python | ||
class Barefuncs: | ||
def foo(self, a: int) -> int: | ||
return a + 1 | ||
``` | ||
You can write a testcase for this using: | ||
```python | ||
def test_bare_funcs(): | ||
testcase = BindgenTestCase( | ||
guest_code_dir='bare_funcs', | ||
world_name='barefuncs', | ||
) | ||
store, root = generate_bindings(testcase) | ||
assert root.foo(store, 10) == 11 | ||
``` | ||
""" | ||
import os | ||
from pathlib import Path | ||
from dataclasses import dataclass, field | ||
import importlib | ||
import tempfile | ||
import subprocess | ||
import shutil | ||
|
||
from pytest import fixture | ||
|
||
import wasmtime | ||
from wasmtime.bindgen import generate | ||
|
||
|
||
TEST_ROOT = Path(__file__).parent | ||
BINDGEN_DIR = TEST_ROOT / 'generated' | ||
|
||
|
||
@dataclass | ||
class BindgenTestCase: | ||
guest_code_dir: Path | ||
world_name: str | ||
wit_filename: str = 'component.wit' | ||
app_dir: Path = field(init=False) | ||
app_name: str = field(init=False, default='app', repr=False) | ||
|
||
def __post_init__(self): | ||
self.app_dir = Path(self.guest_code_dir).resolve() | ||
|
||
@property | ||
def wit_full_path(self): | ||
return self.guest_code_dir.joinpath(self.wit_filename) | ||
|
||
@property | ||
def testsuite_name(self): | ||
# The name of the directory that contains the | ||
# guest Python code is used as the identifier for | ||
# package names, etc. | ||
return self.guest_code_dir.name | ||
|
||
|
||
def generate_bindings(guest_code_dir: Path, | ||
world_name: str, | ||
wit_filename: str = 'component.wit'): | ||
tc = BindgenTestCase( | ||
guest_code_dir=guest_code_dir, | ||
world_name=world_name, | ||
wit_filename=wit_filename) | ||
return _generate_bindings(tc) | ||
|
||
|
||
def _generate_bindings(testcase: BindgenTestCase): | ||
wit_path = testcase.wit_full_path | ||
componentize_py = shutil.which('componentize-py') | ||
if componentize_py is None: | ||
raise RuntimeError("Could not find componentize-py executable.") | ||
with tempfile.NamedTemporaryFile('w') as f: | ||
output_wasm = str(f.name + '.wasm') | ||
subprocess.run([ | ||
componentize_py, '-d', str(wit_path), '-w', testcase.world_name, | ||
'componentize', '--stub-wasi', testcase.app_name, | ||
'-o', output_wasm | ||
], check=True, cwd=testcase.guest_code_dir) | ||
# Once we've done that now generate the python bindings. | ||
testsuite_name = testcase.testsuite_name | ||
with open(output_wasm, 'rb') as out: | ||
# Mapping of filename -> content_bytes | ||
results = generate(testsuite_name, out.read()) | ||
for filename, contents in results.items(): | ||
path = BINDGEN_DIR / testsuite_name / filename | ||
path.parent.mkdir(parents=True, exist_ok=True) | ||
path.write_bytes(contents) | ||
# Return an instantiated module for the caller to test. | ||
pkg = importlib.import_module(f'.generated.{testsuite_name}', | ||
package=__package__) | ||
store = wasmtime.Store() | ||
root = pkg.Root(store) | ||
return store, root | ||
|
||
|
||
@fixture | ||
def bindgen_testcase(): | ||
return generate_bindings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from pathlib import Path | ||
|
||
|
||
def test_bare_funcs(bindgen_testcase): | ||
store, root = bindgen_testcase( | ||
guest_code_dir=Path(__file__).parent, | ||
world_name='testworld', | ||
) | ||
interface = root.my_interface_name() | ||
instance = interface.DemoResourceClass(store, 'myname') | ||
result = instance.greet(store, 'Hello there') | ||
assert result == 'Hello there, myname!' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from pathlib import Path | ||
|
||
|
||
def test_lists(bindgen_testcase): | ||
store, root = bindgen_testcase( | ||
guest_code_dir=Path(__file__).parent, | ||
world_name='lists', | ||
) | ||
assert root.strings(store, '') == '' | ||
assert root.strings(store, 'a') == 'a' | ||
assert root.strings(store, 'hello world') == 'hello world' | ||
assert root.strings(store, 'hello ⚑ world') == 'hello ⚑ world' | ||
|
||
assert root.bytes(store, b'') == b'' | ||
assert root.bytes(store, b'a') == b'a' | ||
assert root.bytes(store, b'\x01\x02') == b'\x01\x02' | ||
|
||
assert root.ints(store, []) == [] | ||
assert root.ints(store, [1]) == [1] | ||
assert root.ints(store, [1, 2, 100, 10000]) == [1, 2, 100, 10000] | ||
|
||
assert root.string_list(store, []) == [] | ||
assert root.string_list(store, ['']) == [''] | ||
assert root.string_list( | ||
store, ['a', 'b', '', 'd', 'hello'] | ||
) == ['a', 'b', '', 'd', 'hello'] |
This file was deleted.
Oops, something went wrong.