Files
extism/python/tests/test_extism.py
zach 360df45e1a fix: require modules to have exported, bounded memory when manifest memory.max_pages field is set (#356)
- Requires modules compiled to run with manifests that set `max_memory`
to have an exported memory with lower and upper bounds
- Includes the size of memory exported from modules when calculating
available memory for plugins

## How to compile a module with bounded memory 

You will need to pass `--max-memory=$NUM_BYTES` to wasm-ld. `$NUM_BYTES`
must be a multiple of the page size. Here are some examples for
supported PDK languages:

**C** 
Pass `-Wl,--max-memory=65536` to your C compiler

**Rust**: 
In a `.cargo/config` file:
```toml
[target.wasm32-unknown-unknown]
rustflags = ["-Clink-args=--max-memory=65536"]
 ```
**Haskell**
Add the following to the cabal file entry for your `cabal.project` file:

```
package myproject
  ghc-options:
    -optl -Wl,--max-memory=65536
```
**AssemblyScript**
Pass `--maximumMemory 65536` to the assemblyscropt compiler

**TinyGo**:
Create a `target.json` file:
```json
{
    "inherits": [ "wasm" ],
    "ldflags": [
        "--max-memory=65536",
    ]
}
```
and build using `tinygo -target ./target.json`
2023-06-01 09:37:42 -07:00

139 lines
4.8 KiB
Python

import unittest
import extism
import hashlib
import json
import time
from threading import Thread
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 test_extism_host_function(self):
@extism.host_fn
def hello_world(plugin, params, results, user_data):
offs = plugin.alloc(len(user_data))
mem = plugin.memory(offs)
mem[:] = user_data
results[0].value = offs.offset
with extism.Context() as ctx:
f = [
extism.Function(
"hello_world",
[extism.ValType.I64],
[extism.ValType.I64],
hello_world,
b"test",
)
]
plugin = ctx.plugin(self._manifest(functions=True), functions=f, wasi=True)
res = plugin.call("count_vowels", "aaa")
self.assertEqual(res, b"test")
def test_extism_plugin_cancel(self):
with extism.Context() as ctx:
plugin = ctx.plugin(self._loop_manifest())
cancel_handle = plugin.cancel_handle()
def cancel(handle):
time.sleep(0.5)
handle.cancel()
Thread(target=cancel, args=[cancel_handle]).run()
self.assertRaises(extism.Error, lambda: plugin.call("infinite_loop", b""))
def _manifest(self, functions=False):
wasm = self._count_vowels_wasm(functions)
hash = hashlib.sha256(wasm).hexdigest()
return {"wasm": [{"data": wasm, "hash": hash}]}
def _loop_manifest(self):
wasm = self._infinite_loop_wasm()
hash = hashlib.sha256(wasm).hexdigest()
return {
"wasm": [{"data": wasm, "hash": hash}],
"timeout_ms": 1000,
}
def _count_vowels_wasm(self, functions=False):
return read_test_wasm("code.wasm" if not functions else "code-functions.wasm")
def _infinite_loop_wasm(self):
return read_test_wasm("loop.wasm")
def read_test_wasm(p):
path = join(dirname(__file__), "..", "..", "wasm", p)
with open(path, "rb") as wasm_file:
return wasm_file.read()