mirror of
https://github.com/extism/extism.git
synced 2026-04-23 03:00:11 -04:00
- New types:
- `ExtismValType` - Enum of WebAssembly types
- `ExtismValUnion` - A union of the possible WebAssembly types
- `ExtismVal` - A struct with `ExtismValType` and `ExtismValUnion`
- `ExtismFunction` - The host function wrapper type
- `ExtismFunctionType` - The type of the host function callback
- `ExtismCurrentPlugin` - Provides access to the currently running
plugin from inside a host function
- New functions:
- `extism_function_new` - Create a new `ExtismFunction`
- `extism_function_free` - Free an `ExtismFunction`
- `extism_current_plugin_memory`, `extism_current_plugin_memory_alloc`,
`extism_current_plugin_memory_free`,
`extism_current_plugin_memory_length` - Manage plugin memory from inside
a host functions
- Updated functions
- `extism_plugin_new` and `extsim_plugin_update` - now accept two extra
parameters for `ExtismFunction*` array and length of that array
## Notes
- Host functions take a user-data argument, which is owned by the
resulting `ExtismFunction` and will be cleaned up when
`extism_function_free` is called (if a cleanup function was passed in
with the user data)
- Host functions in every SDK require working with `ExtismVal` arguments
directly, this is pretty low-level for what is kind of a high-level
feature. We could work on adding some types to the SDKs that make
working with pointers to plugin data more accessible, maybe something
similar to how the Rust PDK handes input/output data.
- In each language the host functions more-or-less share a signature:
`(CurrentPlugin plugin, Val inputs[], Val outputs[], userData)`
- C, C++, OCaml and Go take a single userData argument but Python and
Node take a "rest" argument which allows passing any number of user-data
values
- Go requires the host function to be exported:
f9eb5ed839/go/main.go (L13-L26)
- Zig and Ruby should be relatively simple to add host functions to next
but I haven't really looked into Elixir, .NET or Java yet.
- Also closes #20
104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
import unittest
|
|
import extism
|
|
import hashlib
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from os.path import join, dirname
|
|
|
|
|
|
class TestExtism(unittest.TestCase):
|
|
def test_context_new(self):
|
|
ctx = extism.Context()
|
|
self.assertIsNotNone(ctx)
|
|
del ctx
|
|
|
|
def test_call_plugin(self):
|
|
with extism.Context() as ctx:
|
|
plugin = ctx.plugin(self._manifest())
|
|
j = json.loads(plugin.call("count_vowels", "this is a test"))
|
|
self.assertEqual(j["count"], 4)
|
|
j = json.loads(plugin.call("count_vowels", "this is a test again"))
|
|
self.assertEqual(j["count"], 7)
|
|
j = json.loads(plugin.call("count_vowels", "this is a test thrice"))
|
|
self.assertEqual(j["count"], 6)
|
|
j = json.loads(plugin.call("count_vowels", "🌎hello🌎world🌎"))
|
|
self.assertEqual(j["count"], 3)
|
|
|
|
def test_update_plugin_manifest(self):
|
|
with extism.Context() as ctx:
|
|
plugin = ctx.plugin(self._manifest())
|
|
# update with just the raw bytes of the wasm
|
|
plugin.update(self._count_vowels_wasm())
|
|
# should still work
|
|
j = json.loads(plugin.call("count_vowels", "this is a test"))
|
|
self.assertEqual(j["count"], 4)
|
|
|
|
def test_function_exists(self):
|
|
with extism.Context() as ctx:
|
|
plugin = ctx.plugin(self._manifest())
|
|
self.assertTrue(plugin.function_exists("count_vowels"))
|
|
self.assertFalse(plugin.function_exists("i_dont_exist"))
|
|
|
|
def test_errors_on_unknown_function(self):
|
|
with extism.Context() as ctx:
|
|
plugin = ctx.plugin(self._manifest())
|
|
self.assertRaises(
|
|
extism.Error, lambda: plugin.call("i_dont_exist", "someinput")
|
|
)
|
|
|
|
def test_can_free_plugin(self):
|
|
with extism.Context() as ctx:
|
|
plugin = ctx.plugin(self._manifest())
|
|
del plugin
|
|
|
|
def test_errors_on_bad_manifest(self):
|
|
with extism.Context() as ctx:
|
|
self.assertRaises(
|
|
extism.Error, lambda: ctx.plugin({"invalid_manifest": True})
|
|
)
|
|
plugin = ctx.plugin(self._manifest())
|
|
self.assertRaises(
|
|
extism.Error, lambda: plugin.update({"invalid_manifest": True})
|
|
)
|
|
|
|
def test_extism_version(self):
|
|
self.assertIsNotNone(extism.extism_version())
|
|
|
|
def test_extism_plugin_timeout(self):
|
|
with extism.Context() as ctx:
|
|
plugin = ctx.plugin(self._loop_manifest())
|
|
start = datetime.now()
|
|
self.assertRaises(extism.Error, lambda: plugin.call("infinite_loop", b""))
|
|
end = datetime.now()
|
|
self.assertLess(
|
|
end,
|
|
start + timedelta(seconds=1.01),
|
|
"plugin timeout exceeded 1000ms expectation",
|
|
)
|
|
|
|
def _manifest(self):
|
|
wasm = self._count_vowels_wasm()
|
|
hash = hashlib.sha256(wasm).hexdigest()
|
|
return {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max_pages": 5}}
|
|
|
|
def _loop_manifest(self):
|
|
wasm = self._infinite_loop_wasm()
|
|
hash = hashlib.sha256(wasm).hexdigest()
|
|
return {
|
|
"wasm": [{"data": wasm, "hash": hash}],
|
|
"memory": {"max_pages": 5},
|
|
"timeout_ms": 1000,
|
|
}
|
|
|
|
def _count_vowels_wasm(self):
|
|
return read_test_wasm("code.wasm")
|
|
|
|
def _infinite_loop_wasm(self):
|
|
return read_test_wasm("loop.wasm")
|
|
|
|
|
|
def read_test_wasm(p):
|
|
path = join(dirname(__file__), p)
|
|
with open(path, "rb") as wasm_file:
|
|
return wasm_file.read()
|