Coverage for tests/component/test_func.py: 100%
206 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-12-01 19:40 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2025-12-01 19:40 +0000
1import unittest
2from dataclasses import dataclass
3from wasmtime import Store, WasmtimeError, Engine
4from wasmtime.component import *
5from typing import Any, List
7def list_component(ty_wit: str):
8 return f"""
9 (component
10 (import "x" (func $x (param "x" {ty_wit}) (result {ty_wit})))
11 (core module $libc
12 (memory (export "mem") 1)
13 (global $base (mut i32) (i32.const 100))
14 (func (export "realloc") (param i32 i32 i32 i32) (result i32)
15 (local $ret i32)
16 local.get 0
17 if unreachable end
18 local.get 1
19 if unreachable end
21 (local.set $ret (global.get $base))
22 (global.set $base
23 (i32.add
24 (global.get $base)
25 (local.get 2)))
26 local.get $ret)
27 )
28 (core instance $libc (instantiate $libc))
29 (core module $a
30 (import "" "x" (func (param i32 i32 i32)))
31 (func (export "x") (param i32 i32) (result i32)
32 local.get 0
33 local.get 1
34 i32.const 0
35 call 0
36 i32.const 0)
37 )
38 (core func $x (canon lower (func $x)
39 (memory $libc "mem") (realloc (func $libc "realloc"))))
40 (core instance $a (instantiate $a
41 (with "" (instance
42 (export "x" (func $x))
43 ))
44 ))
45 (func (export "x") (param "x" {ty_wit}) (result {ty_wit})
46 (canon lift (core func $a "x") (memory $libc "mem")
47 (realloc (func $libc "realloc"))))
48 )
49 """
51def retptr_component(ty_wit: str, ty_wasm: str):
52 return f"""
53 (component
54 (type $t' {ty_wit})
55 (import "t" (type $t (eq $t')))
56 (import "x" (func $x (param "x" $t) (result $t)))
57 (core module $libc (memory (export "mem") 1))
58 (core instance $libc (instantiate $libc))
59 (core module $a
60 (import "" "x" (func (param {ty_wasm} i32)))
61 (func (export "x") (param {ty_wasm}) (result i32)
62 local.get 0
63 local.get 1
64 i32.const 0
65 call 0
66 i32.const 0)
67 )
68 (core func $x (canon lower (func $x) (memory $libc "mem")))
69 (core instance $a (instantiate $a
70 (with "" (instance
71 (export "x" (func $x))
72 ))
73 ))
74 (func (export "x") (param "x" $t) (result $t)
75 (canon lift (core func $a "x") (memory $libc "mem")))
76 )
77 """
79class TestFunc(unittest.TestCase):
80 def roundtrip(self, wat: str, values: List[Any], bad = TypeError) -> None:
81 engine = Engine()
82 store = Store(engine)
83 component = Component(engine, wat)
84 value_being_tested = None
86 def roundtrip(_store, val):
87 self.assertEqual(value_being_tested, val)
88 return val
90 linker = Linker(engine)
91 with linker.root() as l:
92 l.add_func('x', roundtrip)
93 instance = linker.instantiate(store, component)
94 f = instance.get_func(store, 'x')
95 assert(f is not None)
96 for val in values:
97 value_being_tested = val
98 ret = f(store, val)
99 self.assertEqual(ret, val)
100 f.post_return(store)
102 class Bad:
103 pass
104 with self.assertRaises(bad):
105 f(store, Bad())
107 def roundtrip_simple(self, ty_wit: str, ty_wasm: str, values: List[Any]) -> None:
108 wat = f"""
109 (component
110 (type $t' {ty_wit})
111 (import "t" (type $t (eq $t')))
112 (import "x" (func $x (param "x" $t) (result $t)))
113 (core module $a
114 (import "" "x" (func (param {ty_wasm}) (result {ty_wasm})))
115 (func (export "x") (param {ty_wasm}) (result {ty_wasm})
116 local.get 0
117 call 0)
118 )
119 (core func $x (canon lower (func $x)))
120 (core instance $a (instantiate $a
121 (with "" (instance
122 (export "x" (func $x))
123 ))
124 ))
125 (func (export "x") (param "x" $t) (result $t)
126 (canon lift (core func $a "x")))
127 )
128 """
129 self.roundtrip(wat, values)
131 def test_init(self):
132 with self.assertRaises(WasmtimeError):
133 Func()
135 def test_type_reflection(self):
136 engine = Engine()
137 store = Store(engine)
138 component = Component(engine, """
139 (component
140 (core module $a
141 (func (export "a"))
142 (func (export "b") (param i32) (result i32) unreachable)
143 )
144 (core instance $a (instantiate $a))
145 (func (export "a") (canon lift (core func $a "a")))
146 (func (export "b") (param "x" u32) (result u32)
147 (canon lift (core func $a "b")))
148 )
149 """)
150 instance = Linker(engine).instantiate(store, component)
151 ai = instance.get_export_index(store, 'a')
152 bi = instance.get_export_index(store, 'b')
153 assert(ai is not None)
154 assert(bi is not None)
155 a = instance.get_func(store, ai)
156 b = instance.get_func(store, bi)
157 assert(a is not None)
158 assert(b is not None)
159 self.assertEqual(a.type(store).params, [])
160 self.assertIsNone(a.type(store).result)
161 self.assertEqual(b.type(store).params, [('x', U32())])
162 self.assertEqual(b.type(store).result, U32())
164 def test_call(self):
165 engine = Engine()
166 store = Store(engine)
167 component = Component(engine, """
168 (component
169 (core module $a
170 (func (export "a"))
171 (func (export "b") (param i32)
172 local.get 0
173 i32.const 100
174 i32.ne
175 if unreachable end)
176 (func (export "c") (result i32) (i32.const 101))
177 )
178 (core instance $a (instantiate $a))
179 (func (export "a") (canon lift (core func $a "a")))
180 (func (export "b") (param "x" u32) (canon lift (core func $a "b")))
181 (func (export "c") (result u32) (canon lift (core func $a "c")))
182 )
183 """)
184 instance = Linker(engine).instantiate(store, component)
185 a = instance.get_func(store, 'a')
186 assert(a is not None)
187 ret = a(store)
188 self.assertEqual(ret, None)
189 a.post_return(store)
191 b = instance.get_func(store, 'b')
192 assert(b is not None)
193 ret = b(store, 100)
194 self.assertEqual(ret, None)
195 b.post_return(store)
197 c = instance.get_func(store, 'c')
198 assert(c is not None)
199 ret = c(store)
200 self.assertEqual(ret, 101)
201 c.post_return(store)
203 with self.assertRaises(TypeError):
204 b(store)
206 def test_roundtrip_empty(self):
207 engine = Engine()
208 store = Store(engine)
209 component = Component(engine, f"""
210 (component
211 (import "x" (func $x))
212 (core module $a
213 (import "" "x" (func))
214 (func (export "x") call 0)
215 )
216 (core func $x (canon lower (func $x)))
217 (core instance $a (instantiate $a
218 (with "" (instance
219 (export "x" (func $x))
220 ))
221 ))
222 (func (export "x") (canon lift (core func $a "x")))
223 )
224 """)
226 linker = Linker(engine)
227 with linker.root() as l:
228 l.add_func('x', lambda _store: None)
229 instance = linker.instantiate(store, component)
230 f = instance.get_func(store, 'x')
231 assert(f is not None)
233 self.assertIsNone(f(store))
234 f.post_return(store)
236 def test_roundtrip_primitive(self):
237 self.roundtrip_simple('bool', 'i32', [True, False])
238 self.roundtrip_simple('u8', 'i32', [0, 1, 42, 255])
239 self.roundtrip_simple('u16', 'i32', [0, 1, 42, 65535])
240 self.roundtrip_simple('u32', 'i32', [0, 1, 42, 4294967295])
241 self.roundtrip_simple('u64', 'i64', [0, 1, 42, 18446744073709551615])
242 self.roundtrip_simple('s8', 'i32', [0, 1, -1, 42, -42, 127, -128])
243 self.roundtrip_simple('s16', 'i32', [0, 1, -1, 42, -42, 32767, -32768])
244 self.roundtrip_simple('s32', 'i32', [0, 1, -1, 42, -42, 2147483647, -2147483648])
245 self.roundtrip_simple('s64', 'i64', [0, 1, -1, 42, -42, 9223372036854775807, -9223372036854775808])
246 self.roundtrip_simple('f32', 'f32', [0.0, 1.0, -1.0])
247 self.roundtrip_simple('f64', 'f64', [0.0, 1.0, -1.0])
248 self.roundtrip_simple('char', 'i32', ['a', 'b'])
250 def test_resources(self):
251 engine = Engine()
252 store = Store(engine)
253 component = Component(engine, f"""
254 (component
255 (import "t" (type $t (sub resource)))
256 (import "mk" (func $mk (result (own $t))))
257 (import "borrow" (func $borrow (param "t" (borrow $t))))
258 (import "own" (func $own (param "t" (own $t))))
259 (core module $a
260 (import "" "mk" (func $mk (result i32)))
261 (import "" "borrow" (func $borrow (param i32)))
262 (import "" "own" (func $own (param i32)))
263 (import "" "drop" (func $drop (param i32)))
265 (func (export "mk") (result i32) call $mk)
266 (func (export "borrow") (param i32)
267 (call $borrow (local.get 0))
268 (call $drop (local.get 0)))
269 (func (export "own") (param i32) local.get 0 call $own)
270 )
271 (core func $mk (canon lower (func $mk)))
272 (core func $borrow (canon lower (func $borrow)))
273 (core func $own (canon lower (func $own)))
274 (core func $drop (canon resource.drop $t))
275 (core instance $a (instantiate $a
276 (with "" (instance
277 (export "mk" (func $mk))
278 (export "borrow" (func $borrow))
279 (export "own" (func $own))
280 (export "drop" (func $drop))
281 ))
282 ))
283 (func (export "mk") (result (own $t))
284 (canon lift (core func $a "mk")))
285 (func (export "borrow") (param "x" (borrow $t))
286 (canon lift (core func $a "borrow")))
287 (func (export "own") (param "x" (own $t))
288 (canon lift (core func $a "own")))
289 )
290 """)
292 ty = ResourceType.host(2)
294 def mk(_store):
295 return ResourceHost.own(1, 2)
297 def borrow(_store, b):
298 self.assertFalse(b.owned)
299 handle = b.to_host(store)
300 self.assertEqual(handle.type, 2)
302 def own(_store, b):
303 self.assertTrue(b.owned)
305 linker = Linker(engine)
306 with linker.root() as l:
307 l.add_resource('t', ty, lambda _store, _rep: None)
308 l.add_func('mk', mk)
309 l.add_func('borrow', borrow)
310 l.add_func('own', own)
311 instance = linker.instantiate(store, component)
312 f_mk = instance.get_func(store, 'mk')
313 f_borrow = instance.get_func(store, 'borrow')
314 f_own = instance.get_func(store, 'own')
315 assert(f_mk is not None)
316 assert(f_borrow is not None)
317 assert(f_own is not None)
319 r1 = f_mk(store)
320 self.assertIsInstance(r1, ResourceAny)
321 f_mk.post_return(store)
322 f_borrow(store, r1)
323 f_borrow.post_return(store)
324 f_borrow(store, ResourceHost.own(1, 2))
325 f_borrow.post_return(store)
326 f_own(store, r1)
327 f_own.post_return(store)
328 f_own(store, ResourceHost.own(1, 2))
329 f_own.post_return(store)
331 with self.assertRaises(TypeError):
332 f_borrow(store, 1)
334 with self.assertRaises(TypeError):
335 f_own(store, 1)
338 def test_enum(self):
339 self.roundtrip_simple('(enum "a" "b" "c")', 'i32', ['a', 'b', 'c'])
340 with self.assertRaises(WasmtimeError):
341 self.roundtrip_simple('(enum "a" "b" "c")', 'i32', ['d'])
344 def test_flags(self):
345 vals = [{'a'}, {'b'}, {'c'}, {'a', 'b'}, {'a', 'c'}, {'b', 'c'}]
346 self.roundtrip_simple('(flags "a" "b" "c")', 'i32', vals)
347 with self.assertRaises(WasmtimeError):
348 self.roundtrip_simple('(flags "a" "b" "c")', 'i32', [{'d'}])
351 def test_record(self):
352 ty_wit = '(record (field "a" u32) (field "b" bool))'
353 ty_wasm = 'i32 i32'
354 wat = retptr_component(ty_wit, ty_wasm)
355 @dataclass
356 class Record:
357 a: int
358 b: bool
359 values = [
360 Record(0, False),
361 Record(1, True),
362 Record(42, False),
363 Record(65535, True)
364 ]
365 self.roundtrip(wat, values, bad = AttributeError)
367 @dataclass
368 class RecordBad:
369 a: int
370 with self.assertRaises(AttributeError):
371 self.roundtrip(wat, [RecordBad(0)])
373 def test_tuple(self):
374 ty_wit = '(tuple u32 bool)'
375 ty_wasm = 'i32 i32'
376 wat = retptr_component(ty_wit, ty_wasm)
377 values = [(0, False), (1, True), (42, False), (65535, True)]
378 self.roundtrip(wat, values)
380 with self.assertRaises(TypeError):
381 self.roundtrip(wat, [(0,)])
383 with self.assertRaises(TypeError):
384 self.roundtrip(wat, [(0, 'x')])
386 def test_string(self):
387 wat = list_component('string')
388 self.roundtrip(wat, ['', 'a', 'hello, world!'])
390 def test_list(self):
391 wat = list_component('(list u8)')
392 self.roundtrip(wat, [b'', b'a', b'hello, world!'])
393 wat = list_component('(list u32)')
394 self.roundtrip(wat, [[], [1], [1, 2, 3, 4, 5]])
395 with self.assertRaises(TypeError):
396 self.roundtrip(wat, [[1, 2, 3, 'x']])
398 def test_variant(self):
399 self.roundtrip_simple('(variant (case "a") (case "b"))',
400 'i32',
401 [Variant('a'), Variant('b')])
403 with self.assertRaises(ValueError):
404 self.roundtrip_simple('(variant (case "a") (case "b"))',
405 'i32',
406 [Variant('c')])
408 wat = retptr_component('(variant (case "a" u32) (case "b" u64))', 'i32 i64')
409 self.roundtrip(wat, [Variant('a', 1), Variant('b', 2)])
411 wat = retptr_component('(variant (case "a" u32) (case "b" f32))', 'i32 i32')
412 self.roundtrip(wat, [1, 2.], bad=ValueError)
413 wat = retptr_component('(variant (case "a") (case "b" f32))', 'i32 f32')
414 self.roundtrip(wat, [None, 2.], bad=ValueError)
417 def test_option(self):
418 wat = retptr_component('(option u32)', 'i32 i32')
419 self.roundtrip(wat, [None, 3, 0], bad=ValueError)
422 def test_result(self):
423 wat = retptr_component('(result u32 (error f32))', 'i32 i32')
424 self.roundtrip(wat, [3, 1.], bad=ValueError)
426 wat = retptr_component('(result u32)', 'i32 i32')
427 self.roundtrip(wat, [3, None], bad=ValueError)
429 wat = retptr_component('(result (error f32))', 'i32 f32')
430 self.roundtrip(wat, [3., None], bad=ValueError)
432 self.roundtrip_simple('(result)', 'i32', [Variant('ok'), Variant('err')])
435 # TODO: roundtrip future
436 # TODO: roundtrip stream
437 # TODO: roundtrip error-context
438 # TODO: typechecking in variants-of-python-types (e.g. `add_classes`)