test: add more host function tests, cleanup tests to use wasm/code.wasm when possible (#219)

This commit is contained in:
zach
2023-01-19 08:09:16 -08:00
committed by GitHub
parent d73468a3ac
commit aa04fd3e5c
29 changed files with 270 additions and 42 deletions

View File

@@ -34,6 +34,6 @@ jobs:
- name: Test Go Host SDK
run: |
go version
cd go
LD_LIBRARY_PATH=/usr/local/lib go run main.go
LD_LIBRARY_PATH=/usr/local/lib go test
cd go
LD_LIBRARY_PATH=/usr/local/lib go run main.go | grep "Hello from Go!"

View File

@@ -13,3 +13,4 @@ build-test:
.PHONY: test
test: build-test
cd test && ./test

Binary file not shown.

View File

@@ -10,6 +10,8 @@ std::vector<uint8_t> read(const char *filename) {
std::istreambuf_iterator<char>());
}
const std::string code = "../../wasm/code.wasm";
namespace {
using namespace extism;
@@ -20,7 +22,7 @@ TEST(Context, Basic) {
TEST(Plugin, Manifest) {
Context context;
Manifest manifest = Manifest::path("code.wasm");
Manifest manifest = Manifest::path(code);
manifest.set_config("a", "1");
ASSERT_NO_THROW(Plugin plugin = context.plugin(manifest));
@@ -38,7 +40,7 @@ TEST(Plugin, BadManifest) {
TEST(Plugin, Bytes) {
Context context;
auto wasm = read("code.wasm");
auto wasm = read(code.c_str());
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
Plugin plugin = context.plugin(wasm);
@@ -48,7 +50,7 @@ TEST(Plugin, Bytes) {
TEST(Plugin, UpdateConfig) {
Context context;
auto wasm = read("code.wasm");
auto wasm = read(code.c_str());
Plugin plugin = context.plugin(wasm);
Config config;
@@ -58,13 +60,34 @@ TEST(Plugin, UpdateConfig) {
TEST(Plugin, FunctionExists) {
Context context;
auto wasm = read("code.wasm");
auto wasm = read(code.c_str());
Plugin plugin = context.plugin(wasm);
ASSERT_FALSE(plugin.function_exists("bad_function"));
ASSERT_TRUE(plugin.function_exists("count_vowels"));
}
TEST(Plugin, HostFunction) {
Context context;
auto wasm = read("../../wasm/code-functions.wasm");
auto t = std::vector<ValType>{ValType::I64};
Function hello_world =
Function("hello_world", t, t,
[](CurrentPlugin plugin, const std::vector<Val> &params,
std::vector<Val> &results, void *user_data) {
auto offs = plugin.alloc(4);
memcpy(plugin.memory() + offs, "test", 4);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin = context.plugin(wasm, true, functions);
auto buf = plugin.call("count_vowels", "aaa");
ASSERT_EQ(buf.length, 4);
ASSERT_EQ((std::string)buf, "test");
}
}; // namespace
int main(int argc, char **argv) {

View File

@@ -40,6 +40,7 @@
(ocaml (>= 4.14.1))
(dune (>= 3.2))
(ppx_yojson_conv (>= 0.15.0))
(ppx_inline_test (>= 0.15.0))
(base64 (>= 3.5.0))
)
(tags

View File

@@ -7,12 +7,13 @@ authors: ["Extism Authors <oss@extism.org>"]
license: "BSD-3-Clause"
tags: ["topics" "wasm" "plugin"]
homepage: "https://github.com/extism/extism"
doc: "https://extism.org"
doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml" {>= "4.14.1"}
"dune" {>= "3.2" & >= "3.2"}
"ppx_yojson_conv" {>= "0.15.0"}
"ppx_inline_test" {>= "0.15.0"}
"base64" {>= "3.5.0"}
"odoc" {with-doc}
]

100
extism.go
View File

@@ -14,6 +14,41 @@ import (
#cgo LDFLAGS: -L/usr/local/lib -lextism
#include <extism.h>
#include <stdlib.h>
int64_t extism_val_i64(ExtismValUnion* x){
return x->i64;
}
int32_t extism_val_i32(ExtismValUnion* x){
return x->i32;
}
float extism_val_f32(ExtismValUnion* x){
return x->f32;
}
double extism_val_f64(ExtismValUnion* x){
return x->f64;
}
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
x->i64 = i;
}
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
x->i32 = i;
}
void extism_val_set_f32(ExtismValUnion* x, float f){
x->f32 = f;
}
void extism_val_set_f64(ExtismValUnion* x, double f){
x->f64 = f;
}
*/
import "C"
@@ -33,6 +68,7 @@ var (
I64 ValType = C.I64
F32 ValType = C.F32
F64 ValType = C.F64
V128 ValType = C.V128
FuncRef ValType = C.FuncRef
ExternRef ValType = C.ExternRef
)
@@ -75,9 +111,9 @@ type CurrentPlugin struct {
pointer *C.ExtismCurrentPlugin
}
func GetCurrentPlugin(ptr *C.ExtismCurrentPlugin) CurrentPlugin {
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
return CurrentPlugin{
pointer: ptr,
pointer: (*C.ExtismCurrentPlugin)(ptr),
}
}
@@ -87,6 +123,21 @@ func (p *CurrentPlugin) Memory(offs uint) []byte {
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
}
// Alloc a new memory block of the given length, returning its offset
func (p *CurrentPlugin) Alloc(n uint) uint {
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
}
// Free the memory block specified by the given offset
func (p *CurrentPlugin) Free(offs uint) {
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
}
// Length returns the number of bytes allocated at the specified offset
func (p *CurrentPlugin) Length(offs uint) uint {
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
}
// NewContext creates a new context, it should be freed using the `Free` method
func NewContext() Context {
p := C.extism_context_new()
@@ -360,3 +411,48 @@ func (plugin *Plugin) Free() {
func (ctx Context) Reset() {
C.extism_context_reset(ctx.pointer)
}
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetI64(v unsafe.Pointer) int64 {
return int64(C.extism_val_i64(&(*Val)(v).v))
}
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetUInt(v unsafe.Pointer) uint {
return uint(C.extism_val_i64(&(*Val)(v).v))
}
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetI32(v unsafe.Pointer) int32 {
return int32(C.extism_val_i32(&(*Val)(v).v))
}
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetF32(v unsafe.Pointer) float32 {
return float32(C.extism_val_f32(&(*Val)(v).v))
}
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetF64(v unsafe.Pointer) float64 {
return float64(C.extism_val_i64(&(*Val)(v).v))
}
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetI64(v unsafe.Pointer, i int64) {
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
}
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetI32(v unsafe.Pointer, i int32) {
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
}
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetF32(v unsafe.Pointer, i float32) {
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
}
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetF64(v unsafe.Pointer, f float64) {
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
}

View File

@@ -7,7 +7,7 @@ authors: ["Extism Authors <oss@extism.org>"]
license: "BSD-3-Clause"
tags: ["topics" "wasm" "plugin"]
homepage: "https://github.com/extism/extism"
doc: "https://extism.org"
doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml" {>= "4.14.1"}
@@ -35,3 +35,5 @@ build: [
]
]
dev-repo: "git+https://github.com/extism/extism.git"
build-env: [EXTISM_TEST_NO_LIB = ""]
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]

2
extism.opam.template Normal file
View File

@@ -0,0 +1,2 @@
build-env: [EXTISM_TEST_NO_LIB = ""]
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]

View File

@@ -6,11 +6,16 @@ import (
"testing"
)
func manifest() Manifest {
func manifest(functions bool) Manifest {
path := "./wasm/code.wasm"
if functions {
path = "./wasm/code-functions.wasm"
}
return Manifest{
Wasm: []Wasm{
WasmFile{
Path: "./wasm/code.wasm",
Path: path,
},
},
}
@@ -38,7 +43,7 @@ func TestCallPlugin(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(), false)
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -58,7 +63,7 @@ func TestFreePlugin(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(), false)
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -78,7 +83,7 @@ func TestContextReset(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(), false)
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -98,7 +103,7 @@ func TestCanUpdateAManifest(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(), false)
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -107,7 +112,7 @@ func TestCanUpdateAManifest(t *testing.T) {
t.Error(err)
}
plugin.UpdateManifest(manifest(), false)
plugin.UpdateManifest(manifest(false), []Function{}, false)
// can still call the plugin
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
@@ -119,7 +124,7 @@ func TestFunctionExists(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(), false)
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}
@@ -136,7 +141,7 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(), false)
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
if err != nil {
t.Error(err)
}

View File

@@ -17,12 +17,18 @@ EXTISM_GO_FUNCTION(hello_world);
import "C"
//export hello_world
func hello_world(plugin *C.ExtismCurrentPlugin, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData uintptr) {
func hello_world(plugin unsafe.Pointer, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData uintptr) {
fmt.Println("Hello from Go!")
s := cgo.Handle(userData)
fmt.Println(s.Value().(string))
inputSlice := unsafe.Slice(inputs, nInputs)
outputSlice := unsafe.Slice(outputs, nOutputs)
// Get memory pointed to by first element of input slice
p := extism.GetCurrentPlugin(plugin)
mem := p.Memory(extism.ValGetUInt(unsafe.Pointer(&inputSlice[0])))
fmt.Println(string(mem))
outputSlice[0] = inputSlice[0]
}

View File

@@ -7,7 +7,7 @@ unwrap (Right x) = return x
unwrap (Left (ExtismError msg)) =
assertFailure msg
defaultManifest = manifest [wasmFile "test/code.wasm"]
defaultManifest = manifest [wasmFile "../../wasm/code.wasm"]
initPlugin :: Context -> IO Plugin
initPlugin context =

Binary file not shown.

View File

@@ -87,6 +87,7 @@ export enum ValType {
I64,
F32,
F64,
V128,
FuncRef,
ExternRef,
}

Binary file not shown.

View File

@@ -2,14 +2,23 @@ import * as extism from "../src/index";
import { readFileSync } from "fs";
import { join } from "path";
function manifest(): extism.Manifest {
function manifest(functions: boolean = false): extism.Manifest {
return {
wasm: [{ path: join(__dirname, "/code.wasm") }],
wasm: [
{
path: join(
__dirname,
functions
? "/../../wasm/code-functions.wasm"
: "/../../wasm/code.wasm"
),
},
],
};
}
function wasmBuffer(): Buffer {
return readFileSync(join(__dirname, "/code.wasm"));
return readFileSync(join(__dirname, "/../../wasm/code.wasm"));
}
describe("test extism", () => {
@@ -102,4 +111,27 @@ describe("test extism", () => {
).rejects.toMatch(/Plugin error/);
});
});
test("host functions work", async () => {
await extism.withContext(async (ctx: extism.Context) => {
const plugin = ctx.plugin(manifest(true), true, [
new extism.HostFunction(
"hello_world",
[extism.ValType.I64],
[extism.ValType.I64],
(plugin: any, params: any, results: any, user_data: string) => {
const offs = plugin.memoryAlloc(user_data.length);
const mem = plugin.memory(offs);
mem.write(user_data);
results[0].v.i64 = offs;
},
"test"
),
]);
const res = await plugin.call("count_vowels", "aaa");
expect(res.toString()).toBe("test");
});
});
});

View File

@@ -27,7 +27,16 @@ let locate () =
init paths
|> function
| Some x -> x
| None -> raise Not_found
| None -> (
let fail n =
Printf.fprintf stderr
"Unable to find Extism installation, see \
https://extism.org/docs/install/ for installation instructions\n";
exit n
in
match Sys.getenv_opt "EXTISM_TEST_NO_LIB" with
| None -> fail 1
| Some _ -> fail 0)
let from =
let filename = locate () in
@@ -41,23 +50,25 @@ let extism_context_new = fn "extism_context_new" (void @-> returning context)
let extism_context_free = fn "extism_context_free" (context @-> returning void)
module Extism_val_type = struct
type t = I32 | I64 | F32 | F64 | FuncRef | ExternRef
type t = I32 | I64 | F32 | F64 | V128 | FuncRef | ExternRef
let to_int = function
| I32 -> 0
| I64 -> 1
| F32 -> 2
| F64 -> 3
| FuncRef -> 4
| ExternRef -> 5
| V128 -> 4
| FuncRef -> 5
| ExternRef -> 6
let of_int = function
| 0 -> I32
| 1 -> I64
| 2 -> F32
| 3 -> F64
| 4 -> FuncRef
| 5 -> ExternRef
| 4 -> V128
| 5 -> FuncRef
| 6 -> ExternRef
| n -> invalid_arg ("Extism_val_type.of_int: " ^ string_of_int n)
let t : t typ = view ~read:of_int ~write:to_int int

View File

@@ -16,7 +16,14 @@ end
(** [Val_type] enumerates every possible argument/result type *)
module Val_type : sig
type t = I32 | I64 | F32 | F64 | FuncRef | ExternRef (** Value type *)
type t =
| I32
| I64
| F32
| F64
| V128
| FuncRef
| ExternRef (** Value type *)
val of_int : int -> t
val to_int : t -> int

View File

@@ -1,6 +1,7 @@
(library
(name extism_manifest)
(public_name extism-manifest)
(inline_tests)
(libraries base64)
(preprocess
(pps ppx_yojson_conv)))
(pps ppx_yojson_conv ppx_inline_test)))

View File

@@ -96,3 +96,16 @@ let of_file filename =
t_of_yojson j
let with_config t config = { t with config = Some config }
let%test "rountrip" =
let config = [ ("a", Some "b"); ("b", Some "c") ] in
let memory = { max_pages = Some 5 } in
let t =
create ~config ~memory ~allowed_hosts:[ "example.com" ]
~allowed_paths:[ ("a", "b") ]
~timeout_ms:1000 []
in
let a = to_json t in
let b = of_json a in
let c = to_json b in
String.equal a c

View File

@@ -7,4 +7,5 @@ from .extism import (
host_fn,
Function,
ValType,
Val,
)

View File

@@ -217,6 +217,8 @@ class Function:
)
def __del__(self):
if not hasattr(self, "pointer"):
return
if self.pointer is not None:
_lib.extism_function_free(self.pointer)
@@ -413,8 +415,9 @@ class ValType(Enum):
I64 = 1
F32 = 2
F64 = 3
FUNC_REF = 4
EXTERN_REF = 5
V128 = 4
FUNC_REF = 5
EXTERN_REF = 6
class Val:

Binary file not shown.

View File

@@ -76,8 +76,30 @@ class TestExtism(unittest.TestCase):
"plugin timeout exceeded 1000ms expectation",
)
def _manifest(self):
wasm = self._count_vowels_wasm()
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 _manifest(self, functions=False):
wasm = self._count_vowels_wasm(functions)
hash = hashlib.sha256(wasm).hexdigest()
return {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max_pages": 5}}
@@ -90,14 +112,14 @@ class TestExtism(unittest.TestCase):
"timeout_ms": 1000,
}
def _count_vowels_wasm(self):
return read_test_wasm("code.wasm")
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__), p)
path = join(dirname(__file__), "..", "..", "wasm", p)
with open(path, "rb") as wasm_file:
return wasm_file.read()

Binary file not shown.

View File

@@ -83,7 +83,7 @@ class TestExtism < Minitest::Test
{
wasm: [
{
path: File.join(__dir__, "code.wasm"),
path: File.join(__dir__, "../../wasm/code.wasm"),
},
],
}

View File

@@ -1,7 +1,7 @@
fn main() {
let fn_macro = "
#define EXTISM_FUNCTION(N) extern void N(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)
#define EXTISM_GO_FUNCTION(N) extern void N(ExtismCurrentPlugin*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, uintptr_t)
#define EXTISM_GO_FUNCTION(N) extern void N(void*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, uintptr_t)
";
if let Ok(bindings) = cbindgen::Builder::new()
.with_crate(".")

View File

@@ -4,7 +4,7 @@
#include <stdbool.h>
#define EXTISM_FUNCTION(N) extern void N(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)
#define EXTISM_GO_FUNCTION(N) extern void N(ExtismCurrentPlugin*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, uintptr_t)
#define EXTISM_GO_FUNCTION(N) extern void N(void*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, uintptr_t)
/**