Coverage for tests/bindgen/conftest.py: 98%
51 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-20 16:25 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-20 16:25 +0000
1"""Fixtures to define test suites for generated code Python guest code.
3These tests work by allowing you to write a WIT file, implement the guest
4code in Python via componentize-py, and then test the generated Python
5bindings. To add a new test, first create the needed fixtures:
7* Create a new sub directory.
8* Within that directory create a `.wit` file.
9* Create an `app.py` file in that directory implementing the guest code.
11Then to write the test itself:
13* Create a `test_<name>.py` in the same directory.
14* Use the `bindgest_testcase` in your test to create the wasm component
15 and generate python bindings for this component.
17## Example
19Given this directory:
21```
22bare_funcs/
23├── app.py <-- guest code implementation
24├── barefuncs <-- componentize-py bindings
25│ ├── __init__.py
26│ └── types.py
27├── component.wit <-- test .wit file
28└── test_mycomp.py <-- pytest test case of bindings
29```
31With a `component.wit` file of:
33```wit
34package component:barefuncs;
36world barefuncs {
37 export foo: func(a: s32) -> s32;
38}
39```
41And guest code of:
43```python
44class Barefuncs:
45 def foo(self, a: int) -> int:
46 return a + 1
47```
49You can write a testcase for this using:
51```python
52from pathlib import Path
55def test_bare_funcs(bindgen_testcase):
56 testcase = bindgen_testcase(
57 guest_code_dir=Path(__file__).parent,
58 world_name='barefuncs',
59 )
60 store, root = generate_bindings(testcase)
61 assert root.foo(store, 10) == 11
62```
64"""
65from pathlib import Path
66from dataclasses import dataclass, field
67import importlib
68import tempfile
69import subprocess
70import shutil
72from pytest import fixture
74import wasmtime
75from wasmtime.bindgen import generate
78TEST_ROOT = Path(__file__).parent
79BINDGEN_DIR = TEST_ROOT / 'generated'
82@dataclass
83class BindgenTestCase:
84 guest_code_dir: Path
85 world_name: str
86 wit_filename: str = 'component.wit'
87 app_dir: Path = field(init=False)
88 app_name: str = field(init=False, default='app', repr=False)
90 def __post_init__(self):
91 self.app_dir = Path(self.guest_code_dir).resolve()
93 @property
94 def wit_full_path(self):
95 return self.guest_code_dir.joinpath(self.wit_filename)
97 @property
98 def testsuite_name(self):
99 # The name of the directory that contains the
100 # guest Python code is used as the identifier for
101 # package names, etc.
102 return self.guest_code_dir.name
105def generate_bindings(guest_code_dir: Path,
106 world_name: str,
107 wit_filename: str = 'component.wit'):
108 tc = BindgenTestCase(
109 guest_code_dir=guest_code_dir,
110 world_name=world_name,
111 wit_filename=wit_filename)
112 return _generate_bindings(tc)
115def _generate_bindings(testcase: BindgenTestCase):
116 wit_path = testcase.wit_full_path
117 componentize_py = shutil.which('componentize-py')
118 if componentize_py is None:
119 raise RuntimeError("Could not find componentize-py executable.")
120 with tempfile.NamedTemporaryFile('w') as f:
121 output_wasm = str(f.name + '.wasm')
122 subprocess.run([
123 componentize_py, '-d', str(wit_path), '-w', testcase.world_name,
124 'componentize', '--stub-wasi', testcase.app_name,
125 '-o', output_wasm
126 ], check=True, cwd=testcase.guest_code_dir)
127 # Once we've done that now generate the python bindings.
128 testsuite_name = testcase.testsuite_name
129 with open(output_wasm, 'rb') as out:
130 # Mapping of filename -> content_bytes
131 results = generate(testsuite_name, out.read())
132 for filename, contents in results.items():
133 path = BINDGEN_DIR / testsuite_name / filename
134 path.parent.mkdir(parents=True, exist_ok=True)
135 path.write_bytes(contents)
136 # Return an instantiated module for the caller to test.
137 pkg = importlib.import_module(f'.generated.{testsuite_name}',
138 package=__package__)
139 store = wasmtime.Store()
140 root = pkg.Root(store)
141 return store, root
144@fixture
145def bindgen_testcase():
146 return generate_bindings