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

1"""Fixtures to define test suites for generated code Python guest code. 

2 

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: 

6 

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. 

10 

11Then to write the test itself: 

12 

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. 

16 

17## Example 

18 

19Given this directory: 

20 

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``` 

30 

31With a `component.wit` file of: 

32 

33```wit 

34package component:barefuncs; 

35 

36world barefuncs { 

37 export foo: func(a: s32) -> s32; 

38} 

39``` 

40 

41And guest code of: 

42 

43```python 

44class Barefuncs: 

45 def foo(self, a: int) -> int: 

46 return a + 1 

47``` 

48 

49You can write a testcase for this using: 

50 

51```python 

52from pathlib import Path 

53 

54 

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``` 

63 

64""" 

65from pathlib import Path 

66from dataclasses import dataclass, field 

67import importlib 

68import tempfile 

69import subprocess 

70import shutil 

71 

72from pytest import fixture 

73 

74import wasmtime 

75from wasmtime.bindgen import generate 

76 

77 

78TEST_ROOT = Path(__file__).parent 

79BINDGEN_DIR = TEST_ROOT / 'generated' 

80 

81 

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) 

89 

90 def __post_init__(self): 

91 self.app_dir = Path(self.guest_code_dir).resolve() 

92 

93 @property 

94 def wit_full_path(self): 

95 return self.guest_code_dir.joinpath(self.wit_filename) 

96 

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 

103 

104 

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) 

113 

114 

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 

142 

143 

144@fixture 

145def bindgen_testcase(): 

146 return generate_bindings