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

1import unittest 

2from dataclasses import dataclass 

3from wasmtime import Store, WasmtimeError, Engine 

4from wasmtime.component import * 

5from typing import Any, List 

6 

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 

20 

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

50 

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

78 

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 

85 

86 def roundtrip(_store, val): 

87 self.assertEqual(value_being_tested, val) 

88 return val 

89 

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) 

101 

102 class Bad: 

103 pass 

104 with self.assertRaises(bad): 

105 f(store, Bad()) 

106 

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) 

130 

131 def test_init(self): 

132 with self.assertRaises(WasmtimeError): 

133 Func() 

134 

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()) 

163 

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) 

190 

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) 

196 

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) 

202 

203 with self.assertRaises(TypeError): 

204 b(store) 

205 

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 """) 

225 

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) 

232 

233 self.assertIsNone(f(store)) 

234 f.post_return(store) 

235 

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']) 

249 

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

264 

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 """) 

291 

292 ty = ResourceType.host(2) 

293 

294 def mk(_store): 

295 return ResourceHost.own(1, 2) 

296 

297 def borrow(_store, b): 

298 self.assertFalse(b.owned) 

299 handle = b.to_host(store) 

300 self.assertEqual(handle.type, 2) 

301 

302 def own(_store, b): 

303 self.assertTrue(b.owned) 

304 

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) 

318 

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) 

330 

331 with self.assertRaises(TypeError): 

332 f_borrow(store, 1) 

333 

334 with self.assertRaises(TypeError): 

335 f_own(store, 1) 

336 

337 

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']) 

342 

343 

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'}]) 

349 

350 

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) 

366 

367 @dataclass 

368 class RecordBad: 

369 a: int 

370 with self.assertRaises(AttributeError): 

371 self.roundtrip(wat, [RecordBad(0)]) 

372 

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) 

379 

380 with self.assertRaises(TypeError): 

381 self.roundtrip(wat, [(0,)]) 

382 

383 with self.assertRaises(TypeError): 

384 self.roundtrip(wat, [(0, 'x')]) 

385 

386 def test_string(self): 

387 wat = list_component('string') 

388 self.roundtrip(wat, ['', 'a', 'hello, world!']) 

389 

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']]) 

397 

398 def test_variant(self): 

399 self.roundtrip_simple('(variant (case "a") (case "b"))', 

400 'i32', 

401 [Variant('a'), Variant('b')]) 

402 

403 with self.assertRaises(ValueError): 

404 self.roundtrip_simple('(variant (case "a") (case "b"))', 

405 'i32', 

406 [Variant('c')]) 

407 

408 wat = retptr_component('(variant (case "a" u32) (case "b" u64))', 'i32 i64') 

409 self.roundtrip(wat, [Variant('a', 1), Variant('b', 2)]) 

410 

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) 

415 

416 

417 def test_option(self): 

418 wat = retptr_component('(option u32)', 'i32 i32') 

419 self.roundtrip(wat, [None, 3, 0], bad=ValueError) 

420 

421 

422 def test_result(self): 

423 wat = retptr_component('(result u32 (error f32))', 'i32 i32') 

424 self.roundtrip(wat, [3, 1.], bad=ValueError) 

425 

426 wat = retptr_component('(result u32)', 'i32 i32') 

427 self.roundtrip(wat, [3, None], bad=ValueError) 

428 

429 wat = retptr_component('(result (error f32))', 'i32 f32') 

430 self.roundtrip(wat, [3., None], bad=ValueError) 

431 

432 self.roundtrip_simple('(result)', 'i32', [Variant('ok'), Variant('err')]) 

433 

434 

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