From 358cdc020bbbb3c35f3bc86a2e91ff354b22b34b Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 12:14:49 -0500 Subject: [PATCH 1/7] docs: New Readme --- .gitignore | 30 ++ README.md | 221 ++++++++- src/main/java/org/extism/sdk/Extism.java | 149 +++--- .../org/extism/sdk/wasm/UrlWasmSource.java | 50 ++ .../extism/sdk/wasm/WasmSourceResolver.java | 2 +- src/test/java/org/extism/sdk/PluginTests.java | 459 ++++++++++-------- 6 files changed, 620 insertions(+), 291 deletions(-) create mode 100644 .gitignore create mode 100644 src/main/java/org/extism/sdk/wasm/UrlWasmSource.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e4f8e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +.project +.classpath +.settings + +target/ diff --git a/README.md b/README.md index 799821e..9ba3c9a 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,32 @@ -Extism Java-SDK ---- +# Extism Java SDK -Java SDK for the [extism](https://extism.org/) WebAssembly Plugin-System. +Java SDK for the [Extism](https://extism.org/) WebAssembly Plugin-System. -> **Note**: This is an early 1.0 release and is unstable until we hit 1.0. If you are looking to integrate now consider looking at the 0.x version in the [extism/extism](https://github.com/extism/extism/tree/main/java) repo. +> **Note**: This is an early 1.0 release and is unstable until we hit 1.0 in December. If you are looking for a stable release consider the 0.x version in the [Extism/extism](https://github.com/extism/extism/tree/main/java) repo. -# Build +## Installation -To build the extism java-sdk run the following command: +### Install the Extism Runtime Dependency -``` -mvn clean verify +For this library, you first need to install the Extism Runtime. You can [download the shared object directly from a release](https://github.com/extism/extism/releases) or use the [Extism CLI](https://github.com/extism/cli) to install it: + +> **Note**: This library has breaking changes and targets 1.0 of the runtime. For the time being, install the runtime from our nightly development builds on git: `sudo extism lib install --version git`. + +```bash +sudo extism lib install latest + +#=> Fetching https://github.com/extism/extism/releases/download/v0.5.2/libextism-aarch64-apple-darwin-v0.5.2.tar.gz +#=> Copying libextism.dylib to /usr/local/lib/libextism.dylib +#=> Copying extism.h to /usr/local/include/extism.h ``` -# Usage +### Install Jar -To use the extism java-sdk you need to add the `org.extism.sdk` dependency to your dependency management and ensure that -the native extism library is installed on your system. For installing the native library refer to the [extism documentation](https://extism.org/docs/install). +To use the Extism java-sdk you need to add the `org.extism.sdk` dependency to your dependency management system. -Instead of installing the native library on your system, you can also download the appropriate library for your platform -yourself. -To do that simply download the [extism release](https://github.com/extism/extism/releases) to a `folder` and run your java application with the system property `-Djna.library.path=/path/to/folder`. +#### Maven -## Maven -To use the extism java-sdk with maven you need to add the following dependency to your `pom.xml` file: +To use the Extism java-sdk with maven you need to add the following dependency to your `pom.xml` file: ```xml org.extism.sdk @@ -32,10 +35,192 @@ To use the extism java-sdk with maven you need to add the following dependency t ``` -## Gradle -To use the extism java-sdk with maven you need to add the following dependency to your `build.gradle` file: +#### Gradle + +To use the Extism java-sdk with maven you need to add the following dependency to your `build.gradle` file: ``` implementation 'org.extism.sdk:extism:1.0.0-rc1' ``` +## Getting Started + +This guide should walk you through some of the concepts in Extism and this java library. + +### Creating A Plug-in + +The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file. +Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web: + +```java +import org.extism.sdk.manifest.Manifest; +import org.extism.sdk.wasm.UrlWasmSource; +import org.extism.sdk.Plugin; + +var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"; +var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); +var plugin = new Plugin(manifest, false, null); +``` + +> **Note**: See [the Manifest docs](https://extism.github.io/ruby-sdk/Extism/Manifest.html) as it has a rich schema and a lot of options. + +### Calling A Plug-in's Exports + +This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Plugin#call](https://example.com) TODO link to javadoc: + +```java +var output = plugin.call("count_vowels", "Hello, World!"); +System.out.println(output); +// => "{"count": 3, "total": 3, "vowels": "aeiouAEIOU"}" +``` + +All exports have a simple interface of bytes-in and bytes-out. +This plug-in happens to take a string and return a JSON encoded string with a report of results. + +### Plug-in State + +Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. +Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. +You can see this by making subsequent calls to the export: + +```java +var output = plugin.call("count_vowels", "Hello, World!"); +System.out.println(output); +// => "{"count": 3, "total": 6, "vowels": "aeiouAEIOU"}" + +var output = plugin.call("count_vowels", "Hello, World!"); +System.out.println(output); +// => "{"count": 3, "total": 9, "vowels": "aeiouAEIOU"}" +``` + +These variables will persist until this plug-in is freed or you initialize a new one. + + +### Host Functions + +Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store! + +Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in. + +[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some ruby methods you write which can be passed down and invoked from any language inside the plug-in. + +Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in: + +### Configuration + +Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. +Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example: + +```java +var plugin = new Plugin(manifest, false, null); +var output = plugin.call("count_vowels", "Yellow, World!"); +System.out.println(output); +// => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} + +// Let's change the vowels config it uses to determine what is a vowel: +var config = Map.of("vowels", "aeiouyAEIOUY"); +var manifest2 = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config); +var plugin = new Plugin(manifest2, false, null); +var output = plugin.call("count_vowels", "Yellow, World!"); +System.out.println(output); +// => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"} +// ^ note count changed to 4 as we configured Y as a vowel this time +``` + +### Host Functions + +Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, +let's store it in a persistent key-value store! + +Wasm can't use our app's KV store on its own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in. + +[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. +They are simply some java methods you write which can be passed down and invoked from any language inside the plug-in. + +Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in: + +```java +var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"; +var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); +var plugin = new Plugin(manifest, false, null); +``` + +> *Note*: The source code for this plug-in is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) +> and is written in rust, but it could be written in any of our PDK languages. + +Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy its import interface for a KV store. +We want to expose two functions to our plugin, `kv_write(String key, Bytes value)` which writes a bytes value to a key and `Bytes kv_read(String key)` which reads the bytes at the given `key`. + +```java +// Our application KV store +// Pretend this is redis or a database :) +var kvStore = new HashMap(); + +ExtismFunction kvWrite = (plugin, params, returns, data) -> { + System.out.println("Hello from kv_write Java Function!"); + var key = plugin.inputString(params[0]); + var value = plugin.inputBytes(params[1]); + System.out.println("Writing to key " + key); + kvStore.put(key, value); +}; + +ExtismFunction kvRead = (plugin, params, returns, data) -> { + System.out.println("Hello from kv_read Java Function!"); + var key = plugin.inputString(params[0]); + System.out.println("Reading from key " + key); + var value = kvStore.get(key); + if (value == null) { + // default to zeroed bytes + var zero = new byte[]{0,0,0,0}; + plugin.returnBytes(returns[0], zero); + } else { + plugin.returnBytes(returns[0], value); + } +}; + +HostFunction kvWriteHostFn = new HostFunction<>( + "kv_write", + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64}, + new LibExtism.ExtismValType[0], + kvWrite, + Optional.empty() +); + +HostFunction kvReadHostFn = new HostFunction<>( + "kv_read", + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, + kvRead, + Optional.empty() +); + +``` + +// TODO link to javadoc here +> *Note*: In order to write host functions you should get familiar with the methods on the [ExtismCurrentPlugin](https://example.com) class. +> The `plugin` parameter is an instance of this class. + +Now we just need to pass in these function references when creating the plugin:. + +```java +HostFunction[] functions = {kvWriteHostFn, kvReadHostFn}; +var plugin = new Plugin(manifest, false, functions); +var output = plugin.call("count_vowels", "Hello, World!"); +// => Hello from kv_read Java Function! +// => Reading from key count-vowels +// => Hello from kv_write Java Function! +// => Writing to key count-vowels +System.out.println(output); +// => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} +``` + +## Development + +# Build + +To build the Extism java-sdk run the following command: + +``` +mvn clean verify +``` + diff --git a/src/main/java/org/extism/sdk/Extism.java b/src/main/java/org/extism/sdk/Extism.java index 6734652..9c18f08 100644 --- a/src/main/java/org/extism/sdk/Extism.java +++ b/src/main/java/org/extism/sdk/Extism.java @@ -1,74 +1,75 @@ -package org.extism.sdk; - -import org.extism.sdk.manifest.Manifest; - -import java.nio.file.Path; -import java.util.Objects; - -/** - * Extism convenience functions. - */ -public class Extism { - - /** - * Configure a log file with the given {@link Path} and configure the given {@link LogLevel}. - * - * @param path - * @param level - * - * @deprecated will be replaced with better logging API. - */ - @Deprecated(forRemoval = true) - public static void setLogFile(Path path, LogLevel level) { - - Objects.requireNonNull(path, "path"); - Objects.requireNonNull(level, "level"); - - var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel()); - if (!result) { - var error = String.format("Could not set extism logger to %s with level %s", path, level); - throw new ExtismException(error); - } - } - - /** - * Invokes the named {@code function} from the {@link Manifest} with the given {@code input}. - * - * @param manifest the manifest containing the function - * @param function the name of the function to call - * @param input the input as string - * @return the output as string - * @throws ExtismException if the call fails - */ - public static String invokeFunction(Manifest manifest, String function, String input) throws ExtismException { - try (var plugin = new Plugin(manifest, false, null)) { - return plugin.call(function, input); - } - } - - /** - * Error levels for the Extism logging facility. - * - * @see Extism#setLogFile(Path, LogLevel) - */ - public enum LogLevel { - - INFO("info"), // - - DEBUG("debug"), // - - WARN("warn"), // - - TRACE("trace"); - - private final String level; - - LogLevel(String level) { - this.level = level; - } - - public String getLevel() { - return level; - } - } -} +package org.extism.sdk; + +import org.extism.sdk.manifest.Manifest; + +import java.nio.file.Path; +import java.util.Objects; + +/** + * Extism convenience functions. + */ +public class Extism { + + /** + * Configure a log file with the given {@link Path} and configure the given {@link LogLevel}. + * + * @param path + * @param level + * + * @deprecated will be replaced with better logging API. + */ + @Deprecated(forRemoval = true) + public static void setLogFile(Path path, LogLevel level) { + + Objects.requireNonNull(path, "path"); + Objects.requireNonNull(level, "level"); + + var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel()); + if (!result) { + var error = String.format("Could not set extism logger to %s with level %s", path, level); + throw new ExtismException(error); + } + } + + /** + * Invokes the named {@code function} from the {@link Manifest} with the given {@code input}. + * This is a convenience method. Prefer initializing and using a {@link Plugin} where possible. + * + * @param manifest the manifest containing the function + * @param function the name of the function to call + * @param input the input as string + * @return the output as string + * @throws ExtismException if the call fails + */ + public static String invokeFunction(Manifest manifest, String function, String input) throws ExtismException { + try (var plugin = new Plugin(manifest, false, null)) { + return plugin.call(function, input); + } + } + + /** + * Error levels for the Extism logging facility. + * + * @see Extism#setLogFile(Path, LogLevel) + */ + public enum LogLevel { + + INFO("info"), // + + DEBUG("debug"), // + + WARN("warn"), // + + TRACE("trace"); + + private final String level; + + LogLevel(String level) { + this.level = level; + } + + public String getLevel() { + return level; + } + } +} diff --git a/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java new file mode 100644 index 0000000..5cf5f13 --- /dev/null +++ b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java @@ -0,0 +1,50 @@ +package org.extism.sdk.wasm; + +/** + * WASM Source represented by a url. + */ +public class UrlWasmSource implements WasmSource { + + private final String name; + + private final String url; + + private final String hash; + + /** + * Provides a quick way to instantiate with just a url + * + * @param url String url to the wasm file + * @return + */ + public static UrlWasmSource fromUrl(String url) { + return new UrlWasmSource(null, url, null); + } + + /** + * Constructor + * @param name + * @param url + * @param hash + */ + public UrlWasmSource(String name, String url, String hash) { + this.name = name; + this.url = url; + this.hash = hash; + } + + @Override + public String name() { + return name; + } + + @Override + public String hash() { + return hash; + } + + public String url() { + return url; + } +} + diff --git a/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java b/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java index f55e667..8b6ab82 100644 --- a/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java +++ b/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java @@ -4,6 +4,7 @@ import org.extism.sdk.support.Hashing; import java.io.IOException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; @@ -18,7 +19,6 @@ public PathWasmSource resolve(Path path) { } public PathWasmSource resolve(String name, Path path) { - Objects.requireNonNull(path, "path"); var wasmFile = path.toFile(); diff --git a/src/test/java/org/extism/sdk/PluginTests.java b/src/test/java/org/extism/sdk/PluginTests.java index 8351267..39645c1 100644 --- a/src/test/java/org/extism/sdk/PluginTests.java +++ b/src/test/java/org/extism/sdk/PluginTests.java @@ -1,198 +1,261 @@ -package org.extism.sdk; - -import com.sun.jna.Pointer; -import org.extism.sdk.manifest.Manifest; -import org.extism.sdk.manifest.MemoryOptions; -import org.extism.sdk.wasm.WasmSourceResolver; -import org.junit.jupiter.api.Test; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.extism.sdk.TestWasmSources.CODE; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class PluginTests { - - // static { - // Extism.setLogFile(Paths.get("/tmp/extism.log"), Extism.LogLevel.TRACE); - // } - - @Test - public void shouldInvokeFunctionWithMemoryOptions() { - var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(0)); - assertThrows(ExtismException.class, () -> { - Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - }); - } - - @Test - public void shouldInvokeFunctionWithConfig() { - //FIXME check if config options are available in wasm call - var config = Map.of("key1", "value1"); - var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, config); - var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - - @Test - public void shouldInvokeFunctionFromFileWasmSource() { - var manifest = new Manifest(CODE.pathWasmSource()); - var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - - @Test - public void shouldInvokeFunctionFromByteArrayWasmSource() { - var manifest = new Manifest(CODE.byteArrayWasmSource()); - var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - - @Test - public void shouldFailToInvokeUnknownFunction() { - assertThrows(ExtismException.class, () -> { - var manifest = new Manifest(CODE.pathWasmSource()); - Extism.invokeFunction(manifest, "unknown", "dummy"); - }, "Function not found: unknown"); - } - - @Test - public void shouldAllowInvokeFunctionFromFileWasmSourceApiUsageExample() { - - var wasmSourceResolver = new WasmSourceResolver(); - var manifest = new Manifest(wasmSourceResolver.resolve(CODE.getWasmFilePath())); - - var functionName = "count_vowels"; - var input = "Hello World"; - - try (var plugin = new Plugin(manifest, false, null)) { - var output = plugin.call(functionName, input); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - } - - @Test - public void shouldAllowInvokeFunctionFromFileWasmSourceMultipleTimes() { - var manifest = new Manifest(CODE.pathWasmSource()); - var functionName = "count_vowels"; - var input = "Hello World"; - - try (var plugin = new Plugin(manifest, false, null)) { - var output = plugin.call(functionName, input); - assertThat(output).isEqualTo("{\"count\": 3}"); - - output = plugin.call(functionName, input); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - } - - @Test - public void shouldAllowInvokeHostFunctionFromPDK() { - var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - - class MyUserData extends HostUserData { - private String data1; - private int data2; - - public MyUserData(String data1, int data2) { - super(); - this.data1 = data1; - this.data2 = data2; - } - } - - ExtismFunction helloWorldFunction = (ExtismFunction) (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); - - int offs = plugin.alloc(4); - Pointer mem = plugin.memory(); - mem.write(offs, "test".getBytes(), 0, 4); - returns[0].v.i64 = offs; - - data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2))); - }; - - HostFunction helloWorld = new HostFunction<>( - "hello_world", - parametersTypes, - resultsTypes, - helloWorldFunction, - Optional.of(new MyUserData("test", 2)) - ); - - HostFunction[] functions = {helloWorld}; - - Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); - String functionName = "count_vowels"; - - try (var plugin = new Plugin(manifest, true, functions)) { - var output = plugin.call(functionName, "this is a test"); - assertThat(output).isEqualTo("test"); - } - } - - @Test - public void shouldAllowInvokeHostFunctionWithoutUserData() { - - var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - - ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); - - int offs = plugin.alloc(4); - Pointer mem = plugin.memory(); - mem.write(offs, "test".getBytes(), 0, 4); - returns[0].v.i64 = offs; - - assertThat(data.isEmpty()); - }; - - HostFunction f = new HostFunction<>( - "hello_world", - parametersTypes, - resultsTypes, - helloWorldFunction, - Optional.empty() - ) - .withNamespace("env"); - - HostFunction g = new HostFunction<>( - "hello_world", - parametersTypes, - resultsTypes, - helloWorldFunction, - Optional.empty() - ) - .withNamespace("test"); - - HostFunction[] functions = {f,g}; - - Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); - String functionName = "count_vowels"; - - try (var plugin = new Plugin(manifest, true, functions)) { - var output = plugin.call(functionName, "this is a test"); - assertThat(output).isEqualTo("test"); - } - } - - - @Test - public void shouldFailToInvokeUnknownHostFunction() { - Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); - String functionName = "count_vowels"; - - try { - var plugin = new Plugin(manifest, true, null); - plugin.call(functionName, "this is a test"); - } catch (ExtismException e) { - assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined"); - } - } - -} +package org.extism.sdk; + +import com.sun.jna.Pointer; +import org.extism.sdk.manifest.Manifest; +import org.extism.sdk.manifest.MemoryOptions; +import org.extism.sdk.wasm.UrlWasmSource; +import org.extism.sdk.wasm.WasmSourceResolver; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.extism.sdk.TestWasmSources.CODE; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PluginTests { + + // static { + // Extism.setLogFile(Paths.get("/tmp/extism.log"), Extism.LogLevel.TRACE); + // } + + @Test + public void shouldInvokeFunctionWithMemoryOptions() { + var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(0)); + assertThrows(ExtismException.class, () -> { + Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + }); + } + + @Test + public void shouldInvokeFunctionWithConfig() { + //FIXME check if config options are available in wasm call + var config = Map.of("key1", "value1"); + var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, config); + var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + + @Test + public void shouldInvokeFunctionFromFileWasmSource() { + var manifest = new Manifest(CODE.pathWasmSource()); + var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + + @Test + public void shouldInvokeFunctionFromUrlWasmSource() { + var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"; + var config = Map.of("vowels", "aeiouyAEIOUY"); + var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config); + var plugin = new Plugin(manifest, false, null); + var output = plugin.call("count_vowels", "Yellow, World!"); + assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}"); + } + + @Test + public void shouldInvokeFunctionFromUrlWasmSourceHostFuncs() { + var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"; + var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); + + // Our application KV store + // Pretend this is redis or a database :) + var kvStore = new HashMap(); + + ExtismFunction kvWrite = (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + var key = plugin.inputString(params[0]); + var value = plugin.inputBytes(params[1]); + System.out.println("Writing to key " + key); + kvStore.put(key, value); + }; + + ExtismFunction kvRead = (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + var key = plugin.inputString(params[0]); + System.out.println("Reading from key " + key); + var value = kvStore.get(key); + if (value == null) { + // default to zeroed bytes + var zero = new byte[]{0,0,0,0}; + plugin.returnBytes(returns[0], zero); + } else { + plugin.returnBytes(returns[0], value); + } + }; + + HostFunction kvWriteHostFn = new HostFunction<>( + "kv_write", + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64}, + new LibExtism.ExtismValType[0], + kvWrite, + Optional.empty() + ); + + HostFunction kvReadHostFn = new HostFunction<>( + "kv_read", + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, + kvRead, + Optional.empty() + ); + + HostFunction[] functions = {kvWriteHostFn, kvReadHostFn}; + var plugin = new Plugin(manifest, false, functions); + var output = plugin.call("count_vowels", "Hello, World!"); + } + + @Test + public void shouldInvokeFunctionFromByteArrayWasmSource() { + var manifest = new Manifest(CODE.byteArrayWasmSource()); + var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + + @Test + public void shouldFailToInvokeUnknownFunction() { + assertThrows(ExtismException.class, () -> { + var manifest = new Manifest(CODE.pathWasmSource()); + Extism.invokeFunction(manifest, "unknown", "dummy"); + }, "Function not found: unknown"); + } + + @Test + public void shouldAllowInvokeFunctionFromFileWasmSourceApiUsageExample() { + + var wasmSourceResolver = new WasmSourceResolver(); + var manifest = new Manifest(wasmSourceResolver.resolve(CODE.getWasmFilePath())); + + var functionName = "count_vowels"; + var input = "Hello World"; + + try (var plugin = new Plugin(manifest, false, null)) { + var output = plugin.call(functionName, input); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + } + + @Test + public void shouldAllowInvokeFunctionFromFileWasmSourceMultipleTimes() { + var manifest = new Manifest(CODE.pathWasmSource()); + var functionName = "count_vowels"; + var input = "Hello World"; + + try (var plugin = new Plugin(manifest, false, null)) { + var output = plugin.call(functionName, input); + assertThat(output).isEqualTo("{\"count\": 3}"); + + output = plugin.call(functionName, input); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + } + + @Test + public void shouldAllowInvokeHostFunctionFromPDK() { + var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + + class MyUserData extends HostUserData { + private String data1; + private int data2; + + public MyUserData(String data1, int data2) { + super(); + this.data1 = data1; + this.data2 = data2; + } + } + + ExtismFunction helloWorldFunction = (ExtismFunction) (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); + + int offs = plugin.alloc(4); + Pointer mem = plugin.memory(); + mem.write(offs, "test".getBytes(), 0, 4); + returns[0].v.i64 = offs; + + data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2))); + }; + + HostFunction helloWorld = new HostFunction<>( + "hello_world", + parametersTypes, + resultsTypes, + helloWorldFunction, + Optional.of(new MyUserData("test", 2)) + ); + + HostFunction[] functions = {helloWorld}; + + Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); + String functionName = "count_vowels"; + + try (var plugin = new Plugin(manifest, true, functions)) { + var output = plugin.call(functionName, "this is a test"); + assertThat(output).isEqualTo("test"); + } + } + + @Test + public void shouldAllowInvokeHostFunctionWithoutUserData() { + + var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + + ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); + + int offs = plugin.alloc(4); + Pointer mem = plugin.memory(); + mem.write(offs, "test".getBytes(), 0, 4); + returns[0].v.i64 = offs; + + assertThat(data.isEmpty()); + }; + + HostFunction f = new HostFunction<>( + "hello_world", + parametersTypes, + resultsTypes, + helloWorldFunction, + Optional.empty() + ) + .withNamespace("env"); + + HostFunction g = new HostFunction<>( + "hello_world", + parametersTypes, + resultsTypes, + helloWorldFunction, + Optional.empty() + ) + .withNamespace("test"); + + HostFunction[] functions = {f,g}; + + Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); + String functionName = "count_vowels"; + + try (var plugin = new Plugin(manifest, true, functions)) { + var output = plugin.call(functionName, "this is a test"); + assertThat(output).isEqualTo("test"); + } + } + + + @Test + public void shouldFailToInvokeUnknownHostFunction() { + Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); + String functionName = "count_vowels"; + + try { + var plugin = new Plugin(manifest, true, null); + plugin.call(functionName, "this is a test"); + } catch (ExtismException e) { + assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined"); + } + } + +} From d7dcdeac8db6b14f52257a06608ac21cf2967061 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 12:18:11 -0500 Subject: [PATCH 2/7] fix copy paste error --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 9ba3c9a..fc6ef04 100644 --- a/README.md +++ b/README.md @@ -95,17 +95,6 @@ System.out.println(output); These variables will persist until this plug-in is freed or you initialize a new one. - -### Host Functions - -Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store! - -Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in. - -[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some ruby methods you write which can be passed down and invoked from any language inside the plug-in. - -Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in: - ### Configuration Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. From 65d5a61a4e6cf80b55e29e876ffa1394844299ae Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 12:18:50 -0500 Subject: [PATCH 3/7] remove ruby link --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fc6ef04..b5fcaad 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,8 @@ var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); var plugin = new Plugin(manifest, false, null); ``` -> **Note**: See [the Manifest docs](https://extism.github.io/ruby-sdk/Extism/Manifest.html) as it has a rich schema and a lot of options. +> TODO link to javadoc +> **Note**: See [the Manifest docs](https://example.com) as it has a rich schema and a lot of options. ### Calling A Plug-in's Exports From 54b5a0c20987689ad3d86aca1f8ed6712ea50b8c Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 12:32:09 -0500 Subject: [PATCH 4/7] shields --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b5fcaad..7ffdb5c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Java SDK for the [Extism](https://extism.org/) WebAssembly Plugin-System. > **Note**: This is an early 1.0 release and is unstable until we hit 1.0 in December. If you are looking for a stable release consider the 0.x version in the [Extism/extism](https://github.com/extism/extism/tree/main/java) repo. +[![javadoc](https://javadoc.io/badge2/org.extism.sdk/extism/javadoc.svg)](https://javadoc.io/doc/org.extism.sdk/extism) +[![maven](https://img.shields.io/maven-central/v/org.extism.sdk/extism)](https://search.maven.org/artifact/org.extism.sdk/extism) + ## Installation ### Install the Extism Runtime Dependency From b5a8046652ff48f2bfb4a2869f4e0f98e83bd668 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 12:35:52 -0500 Subject: [PATCH 5/7] add javadoc links --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7ffdb5c..5cf56e7 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Java SDK for the [Extism](https://extism.org/) WebAssembly Plugin-System. > **Note**: This is an early 1.0 release and is unstable until we hit 1.0 in December. If you are looking for a stable release consider the 0.x version in the [Extism/extism](https://github.com/extism/extism/tree/main/java) repo. -[![javadoc](https://javadoc.io/badge2/org.extism.sdk/extism/javadoc.svg)](https://javadoc.io/doc/org.extism.sdk/extism) [![maven](https://img.shields.io/maven-central/v/org.extism.sdk/extism)](https://search.maven.org/artifact/org.extism.sdk/extism) +[![javadoc](https://javadoc.io/badge2/org.extism.sdk/extism/javadoc.svg)](https://javadoc.io/doc/org.extism.sdk/extism) ## Installation @@ -65,12 +65,11 @@ var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); var plugin = new Plugin(manifest, false, null); ``` -> TODO link to javadoc -> **Note**: See [the Manifest docs](https://example.com) as it has a rich schema and a lot of options. +> **Note**: See [the Manifest docs](https://www.javadoc.io/doc/org.extism.sdk/extism/latest/org/extism/sdk/manifest/Manifest.html) as it has a rich schema and a lot of options. ### Calling A Plug-in's Exports -This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Plugin#call](https://example.com) TODO link to javadoc: +This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Plugin#call](https://www.javadoc.io/doc/org.extism.sdk/extism/latest/org/extism/sdk/Plugin.html#call(java.lang.String,byte[])) ```java var output = plugin.call("count_vowels", "Hello, World!"); @@ -189,8 +188,7 @@ HostFunction kvReadHostFn = new HostFunction<>( ``` -// TODO link to javadoc here -> *Note*: In order to write host functions you should get familiar with the methods on the [ExtismCurrentPlugin](https://example.com) class. +> *Note*: In order to write host functions you should get familiar with the methods on the [ExtismCurrentPlugin](https://www.javadoc.io/doc/org.extism.sdk/extism/latest/org/extism/sdk/ExtismCurrentPlugin.html) class. > The `plugin` parameter is an instance of this class. Now we just need to pass in these function references when creating the plugin:. From ca76f4b8885b7d3615e2e9b18565ac29faff4181 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 14:15:09 -0500 Subject: [PATCH 6/7] renormalize line endings --- src/main/java/org/extism/sdk/Extism.java | 150 ++--- .../org/extism/sdk/wasm/UrlWasmSource.java | 100 ++-- src/test/java/org/extism/sdk/PluginTests.java | 522 +++++++++--------- 3 files changed, 386 insertions(+), 386 deletions(-) diff --git a/src/main/java/org/extism/sdk/Extism.java b/src/main/java/org/extism/sdk/Extism.java index 9c18f08..8e106b3 100644 --- a/src/main/java/org/extism/sdk/Extism.java +++ b/src/main/java/org/extism/sdk/Extism.java @@ -1,75 +1,75 @@ -package org.extism.sdk; - -import org.extism.sdk.manifest.Manifest; - -import java.nio.file.Path; -import java.util.Objects; - -/** - * Extism convenience functions. - */ -public class Extism { - - /** - * Configure a log file with the given {@link Path} and configure the given {@link LogLevel}. - * - * @param path - * @param level - * - * @deprecated will be replaced with better logging API. - */ - @Deprecated(forRemoval = true) - public static void setLogFile(Path path, LogLevel level) { - - Objects.requireNonNull(path, "path"); - Objects.requireNonNull(level, "level"); - - var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel()); - if (!result) { - var error = String.format("Could not set extism logger to %s with level %s", path, level); - throw new ExtismException(error); - } - } - - /** - * Invokes the named {@code function} from the {@link Manifest} with the given {@code input}. - * This is a convenience method. Prefer initializing and using a {@link Plugin} where possible. - * - * @param manifest the manifest containing the function - * @param function the name of the function to call - * @param input the input as string - * @return the output as string - * @throws ExtismException if the call fails - */ - public static String invokeFunction(Manifest manifest, String function, String input) throws ExtismException { - try (var plugin = new Plugin(manifest, false, null)) { - return plugin.call(function, input); - } - } - - /** - * Error levels for the Extism logging facility. - * - * @see Extism#setLogFile(Path, LogLevel) - */ - public enum LogLevel { - - INFO("info"), // - - DEBUG("debug"), // - - WARN("warn"), // - - TRACE("trace"); - - private final String level; - - LogLevel(String level) { - this.level = level; - } - - public String getLevel() { - return level; - } - } -} +package org.extism.sdk; + +import org.extism.sdk.manifest.Manifest; + +import java.nio.file.Path; +import java.util.Objects; + +/** + * Extism convenience functions. + */ +public class Extism { + + /** + * Configure a log file with the given {@link Path} and configure the given {@link LogLevel}. + * + * @param path + * @param level + * + * @deprecated will be replaced with better logging API. + */ + @Deprecated(forRemoval = true) + public static void setLogFile(Path path, LogLevel level) { + + Objects.requireNonNull(path, "path"); + Objects.requireNonNull(level, "level"); + + var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel()); + if (!result) { + var error = String.format("Could not set extism logger to %s with level %s", path, level); + throw new ExtismException(error); + } + } + + /** + * Invokes the named {@code function} from the {@link Manifest} with the given {@code input}. + * This is a convenience method. Prefer initializing and using a {@link Plugin} where possible. + * + * @param manifest the manifest containing the function + * @param function the name of the function to call + * @param input the input as string + * @return the output as string + * @throws ExtismException if the call fails + */ + public static String invokeFunction(Manifest manifest, String function, String input) throws ExtismException { + try (var plugin = new Plugin(manifest, false, null)) { + return plugin.call(function, input); + } + } + + /** + * Error levels for the Extism logging facility. + * + * @see Extism#setLogFile(Path, LogLevel) + */ + public enum LogLevel { + + INFO("info"), // + + DEBUG("debug"), // + + WARN("warn"), // + + TRACE("trace"); + + private final String level; + + LogLevel(String level) { + this.level = level; + } + + public String getLevel() { + return level; + } + } +} diff --git a/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java index 5cf5f13..9398fdc 100644 --- a/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java +++ b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java @@ -1,50 +1,50 @@ -package org.extism.sdk.wasm; - -/** - * WASM Source represented by a url. - */ -public class UrlWasmSource implements WasmSource { - - private final String name; - - private final String url; - - private final String hash; - - /** - * Provides a quick way to instantiate with just a url - * - * @param url String url to the wasm file - * @return - */ - public static UrlWasmSource fromUrl(String url) { - return new UrlWasmSource(null, url, null); - } - - /** - * Constructor - * @param name - * @param url - * @param hash - */ - public UrlWasmSource(String name, String url, String hash) { - this.name = name; - this.url = url; - this.hash = hash; - } - - @Override - public String name() { - return name; - } - - @Override - public String hash() { - return hash; - } - - public String url() { - return url; - } -} - +package org.extism.sdk.wasm; + +/** + * WASM Source represented by a url. + */ +public class UrlWasmSource implements WasmSource { + + private final String name; + + private final String url; + + private final String hash; + + /** + * Provides a quick way to instantiate with just a url + * + * @param url String url to the wasm file + * @return + */ + public static UrlWasmSource fromUrl(String url) { + return new UrlWasmSource(null, url, null); + } + + /** + * Constructor + * @param name + * @param url + * @param hash + */ + public UrlWasmSource(String name, String url, String hash) { + this.name = name; + this.url = url; + this.hash = hash; + } + + @Override + public String name() { + return name; + } + + @Override + public String hash() { + return hash; + } + + public String url() { + return url; + } +} + diff --git a/src/test/java/org/extism/sdk/PluginTests.java b/src/test/java/org/extism/sdk/PluginTests.java index 39645c1..2de55be 100644 --- a/src/test/java/org/extism/sdk/PluginTests.java +++ b/src/test/java/org/extism/sdk/PluginTests.java @@ -1,261 +1,261 @@ -package org.extism.sdk; - -import com.sun.jna.Pointer; -import org.extism.sdk.manifest.Manifest; -import org.extism.sdk.manifest.MemoryOptions; -import org.extism.sdk.wasm.UrlWasmSource; -import org.extism.sdk.wasm.WasmSourceResolver; -import org.junit.jupiter.api.Test; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.extism.sdk.TestWasmSources.CODE; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class PluginTests { - - // static { - // Extism.setLogFile(Paths.get("/tmp/extism.log"), Extism.LogLevel.TRACE); - // } - - @Test - public void shouldInvokeFunctionWithMemoryOptions() { - var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(0)); - assertThrows(ExtismException.class, () -> { - Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - }); - } - - @Test - public void shouldInvokeFunctionWithConfig() { - //FIXME check if config options are available in wasm call - var config = Map.of("key1", "value1"); - var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, config); - var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - - @Test - public void shouldInvokeFunctionFromFileWasmSource() { - var manifest = new Manifest(CODE.pathWasmSource()); - var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - - @Test - public void shouldInvokeFunctionFromUrlWasmSource() { - var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"; - var config = Map.of("vowels", "aeiouyAEIOUY"); - var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config); - var plugin = new Plugin(manifest, false, null); - var output = plugin.call("count_vowels", "Yellow, World!"); - assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}"); - } - - @Test - public void shouldInvokeFunctionFromUrlWasmSourceHostFuncs() { - var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"; - var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); - - // Our application KV store - // Pretend this is redis or a database :) - var kvStore = new HashMap(); - - ExtismFunction kvWrite = (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - var key = plugin.inputString(params[0]); - var value = plugin.inputBytes(params[1]); - System.out.println("Writing to key " + key); - kvStore.put(key, value); - }; - - ExtismFunction kvRead = (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - var key = plugin.inputString(params[0]); - System.out.println("Reading from key " + key); - var value = kvStore.get(key); - if (value == null) { - // default to zeroed bytes - var zero = new byte[]{0,0,0,0}; - plugin.returnBytes(returns[0], zero); - } else { - plugin.returnBytes(returns[0], value); - } - }; - - HostFunction kvWriteHostFn = new HostFunction<>( - "kv_write", - new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64}, - new LibExtism.ExtismValType[0], - kvWrite, - Optional.empty() - ); - - HostFunction kvReadHostFn = new HostFunction<>( - "kv_read", - new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, - new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, - kvRead, - Optional.empty() - ); - - HostFunction[] functions = {kvWriteHostFn, kvReadHostFn}; - var plugin = new Plugin(manifest, false, functions); - var output = plugin.call("count_vowels", "Hello, World!"); - } - - @Test - public void shouldInvokeFunctionFromByteArrayWasmSource() { - var manifest = new Manifest(CODE.byteArrayWasmSource()); - var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - - @Test - public void shouldFailToInvokeUnknownFunction() { - assertThrows(ExtismException.class, () -> { - var manifest = new Manifest(CODE.pathWasmSource()); - Extism.invokeFunction(manifest, "unknown", "dummy"); - }, "Function not found: unknown"); - } - - @Test - public void shouldAllowInvokeFunctionFromFileWasmSourceApiUsageExample() { - - var wasmSourceResolver = new WasmSourceResolver(); - var manifest = new Manifest(wasmSourceResolver.resolve(CODE.getWasmFilePath())); - - var functionName = "count_vowels"; - var input = "Hello World"; - - try (var plugin = new Plugin(manifest, false, null)) { - var output = plugin.call(functionName, input); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - } - - @Test - public void shouldAllowInvokeFunctionFromFileWasmSourceMultipleTimes() { - var manifest = new Manifest(CODE.pathWasmSource()); - var functionName = "count_vowels"; - var input = "Hello World"; - - try (var plugin = new Plugin(manifest, false, null)) { - var output = plugin.call(functionName, input); - assertThat(output).isEqualTo("{\"count\": 3}"); - - output = plugin.call(functionName, input); - assertThat(output).isEqualTo("{\"count\": 3}"); - } - } - - @Test - public void shouldAllowInvokeHostFunctionFromPDK() { - var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - - class MyUserData extends HostUserData { - private String data1; - private int data2; - - public MyUserData(String data1, int data2) { - super(); - this.data1 = data1; - this.data2 = data2; - } - } - - ExtismFunction helloWorldFunction = (ExtismFunction) (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); - - int offs = plugin.alloc(4); - Pointer mem = plugin.memory(); - mem.write(offs, "test".getBytes(), 0, 4); - returns[0].v.i64 = offs; - - data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2))); - }; - - HostFunction helloWorld = new HostFunction<>( - "hello_world", - parametersTypes, - resultsTypes, - helloWorldFunction, - Optional.of(new MyUserData("test", 2)) - ); - - HostFunction[] functions = {helloWorld}; - - Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); - String functionName = "count_vowels"; - - try (var plugin = new Plugin(manifest, true, functions)) { - var output = plugin.call(functionName, "this is a test"); - assertThat(output).isEqualTo("test"); - } - } - - @Test - public void shouldAllowInvokeHostFunctionWithoutUserData() { - - var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; - - ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); - - int offs = plugin.alloc(4); - Pointer mem = plugin.memory(); - mem.write(offs, "test".getBytes(), 0, 4); - returns[0].v.i64 = offs; - - assertThat(data.isEmpty()); - }; - - HostFunction f = new HostFunction<>( - "hello_world", - parametersTypes, - resultsTypes, - helloWorldFunction, - Optional.empty() - ) - .withNamespace("env"); - - HostFunction g = new HostFunction<>( - "hello_world", - parametersTypes, - resultsTypes, - helloWorldFunction, - Optional.empty() - ) - .withNamespace("test"); - - HostFunction[] functions = {f,g}; - - Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); - String functionName = "count_vowels"; - - try (var plugin = new Plugin(manifest, true, functions)) { - var output = plugin.call(functionName, "this is a test"); - assertThat(output).isEqualTo("test"); - } - } - - - @Test - public void shouldFailToInvokeUnknownHostFunction() { - Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); - String functionName = "count_vowels"; - - try { - var plugin = new Plugin(manifest, true, null); - plugin.call(functionName, "this is a test"); - } catch (ExtismException e) { - assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined"); - } - } - -} +package org.extism.sdk; + +import com.sun.jna.Pointer; +import org.extism.sdk.manifest.Manifest; +import org.extism.sdk.manifest.MemoryOptions; +import org.extism.sdk.wasm.UrlWasmSource; +import org.extism.sdk.wasm.WasmSourceResolver; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.extism.sdk.TestWasmSources.CODE; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PluginTests { + + // static { + // Extism.setLogFile(Paths.get("/tmp/extism.log"), Extism.LogLevel.TRACE); + // } + + @Test + public void shouldInvokeFunctionWithMemoryOptions() { + var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(0)); + assertThrows(ExtismException.class, () -> { + Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + }); + } + + @Test + public void shouldInvokeFunctionWithConfig() { + //FIXME check if config options are available in wasm call + var config = Map.of("key1", "value1"); + var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, config); + var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + + @Test + public void shouldInvokeFunctionFromFileWasmSource() { + var manifest = new Manifest(CODE.pathWasmSource()); + var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + + @Test + public void shouldInvokeFunctionFromUrlWasmSource() { + var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"; + var config = Map.of("vowels", "aeiouyAEIOUY"); + var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config); + var plugin = new Plugin(manifest, false, null); + var output = plugin.call("count_vowels", "Yellow, World!"); + assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}"); + } + + @Test + public void shouldInvokeFunctionFromUrlWasmSourceHostFuncs() { + var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"; + var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); + + // Our application KV store + // Pretend this is redis or a database :) + var kvStore = new HashMap(); + + ExtismFunction kvWrite = (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + var key = plugin.inputString(params[0]); + var value = plugin.inputBytes(params[1]); + System.out.println("Writing to key " + key); + kvStore.put(key, value); + }; + + ExtismFunction kvRead = (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + var key = plugin.inputString(params[0]); + System.out.println("Reading from key " + key); + var value = kvStore.get(key); + if (value == null) { + // default to zeroed bytes + var zero = new byte[]{0,0,0,0}; + plugin.returnBytes(returns[0], zero); + } else { + plugin.returnBytes(returns[0], value); + } + }; + + HostFunction kvWriteHostFn = new HostFunction<>( + "kv_write", + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64}, + new LibExtism.ExtismValType[0], + kvWrite, + Optional.empty() + ); + + HostFunction kvReadHostFn = new HostFunction<>( + "kv_read", + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, + new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, + kvRead, + Optional.empty() + ); + + HostFunction[] functions = {kvWriteHostFn, kvReadHostFn}; + var plugin = new Plugin(manifest, false, functions); + var output = plugin.call("count_vowels", "Hello, World!"); + } + + @Test + public void shouldInvokeFunctionFromByteArrayWasmSource() { + var manifest = new Manifest(CODE.byteArrayWasmSource()); + var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World"); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + + @Test + public void shouldFailToInvokeUnknownFunction() { + assertThrows(ExtismException.class, () -> { + var manifest = new Manifest(CODE.pathWasmSource()); + Extism.invokeFunction(manifest, "unknown", "dummy"); + }, "Function not found: unknown"); + } + + @Test + public void shouldAllowInvokeFunctionFromFileWasmSourceApiUsageExample() { + + var wasmSourceResolver = new WasmSourceResolver(); + var manifest = new Manifest(wasmSourceResolver.resolve(CODE.getWasmFilePath())); + + var functionName = "count_vowels"; + var input = "Hello World"; + + try (var plugin = new Plugin(manifest, false, null)) { + var output = plugin.call(functionName, input); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + } + + @Test + public void shouldAllowInvokeFunctionFromFileWasmSourceMultipleTimes() { + var manifest = new Manifest(CODE.pathWasmSource()); + var functionName = "count_vowels"; + var input = "Hello World"; + + try (var plugin = new Plugin(manifest, false, null)) { + var output = plugin.call(functionName, input); + assertThat(output).isEqualTo("{\"count\": 3}"); + + output = plugin.call(functionName, input); + assertThat(output).isEqualTo("{\"count\": 3}"); + } + } + + @Test + public void shouldAllowInvokeHostFunctionFromPDK() { + var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + + class MyUserData extends HostUserData { + private String data1; + private int data2; + + public MyUserData(String data1, int data2) { + super(); + this.data1 = data1; + this.data2 = data2; + } + } + + ExtismFunction helloWorldFunction = (ExtismFunction) (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); + + int offs = plugin.alloc(4); + Pointer mem = plugin.memory(); + mem.write(offs, "test".getBytes(), 0, 4); + returns[0].v.i64 = offs; + + data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2))); + }; + + HostFunction helloWorld = new HostFunction<>( + "hello_world", + parametersTypes, + resultsTypes, + helloWorldFunction, + Optional.of(new MyUserData("test", 2)) + ); + + HostFunction[] functions = {helloWorld}; + + Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); + String functionName = "count_vowels"; + + try (var plugin = new Plugin(manifest, true, functions)) { + var output = plugin.call(functionName, "this is a test"); + assertThat(output).isEqualTo("test"); + } + } + + @Test + public void shouldAllowInvokeHostFunctionWithoutUserData() { + + var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}; + + ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> { + System.out.println("Hello from Java Host Function!"); + System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0]))); + + int offs = plugin.alloc(4); + Pointer mem = plugin.memory(); + mem.write(offs, "test".getBytes(), 0, 4); + returns[0].v.i64 = offs; + + assertThat(data.isEmpty()); + }; + + HostFunction f = new HostFunction<>( + "hello_world", + parametersTypes, + resultsTypes, + helloWorldFunction, + Optional.empty() + ) + .withNamespace("env"); + + HostFunction g = new HostFunction<>( + "hello_world", + parametersTypes, + resultsTypes, + helloWorldFunction, + Optional.empty() + ) + .withNamespace("test"); + + HostFunction[] functions = {f,g}; + + Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); + String functionName = "count_vowels"; + + try (var plugin = new Plugin(manifest, true, functions)) { + var output = plugin.call(functionName, "this is a test"); + assertThat(output).isEqualTo("test"); + } + } + + + @Test + public void shouldFailToInvokeUnknownHostFunction() { + Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource())); + String functionName = "count_vowels"; + + try { + var plugin = new Plugin(manifest, true, null); + plugin.call(functionName, "this is a test"); + } catch (ExtismException e) { + assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined"); + } + } + +} From 8e32afbab13a62509dbadffecc2978778d40d604 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 6 Oct 2023 14:15:34 -0500 Subject: [PATCH 7/7] comment out test --- src/test/java/org/extism/sdk/PluginTests.java | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/test/java/org/extism/sdk/PluginTests.java b/src/test/java/org/extism/sdk/PluginTests.java index 2de55be..6ace3fe 100644 --- a/src/test/java/org/extism/sdk/PluginTests.java +++ b/src/test/java/org/extism/sdk/PluginTests.java @@ -53,57 +53,57 @@ public void shouldInvokeFunctionFromUrlWasmSource() { assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}"); } - @Test - public void shouldInvokeFunctionFromUrlWasmSourceHostFuncs() { - var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"; - var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); - - // Our application KV store - // Pretend this is redis or a database :) - var kvStore = new HashMap(); - - ExtismFunction kvWrite = (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - var key = plugin.inputString(params[0]); - var value = plugin.inputBytes(params[1]); - System.out.println("Writing to key " + key); - kvStore.put(key, value); - }; - - ExtismFunction kvRead = (plugin, params, returns, data) -> { - System.out.println("Hello from Java Host Function!"); - var key = plugin.inputString(params[0]); - System.out.println("Reading from key " + key); - var value = kvStore.get(key); - if (value == null) { - // default to zeroed bytes - var zero = new byte[]{0,0,0,0}; - plugin.returnBytes(returns[0], zero); - } else { - plugin.returnBytes(returns[0], value); - } - }; - - HostFunction kvWriteHostFn = new HostFunction<>( - "kv_write", - new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64}, - new LibExtism.ExtismValType[0], - kvWrite, - Optional.empty() - ); - - HostFunction kvReadHostFn = new HostFunction<>( - "kv_read", - new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, - new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, - kvRead, - Optional.empty() - ); - - HostFunction[] functions = {kvWriteHostFn, kvReadHostFn}; - var plugin = new Plugin(manifest, false, functions); - var output = plugin.call("count_vowels", "Hello, World!"); - } +// @Test +// public void shouldInvokeFunctionFromUrlWasmSourceHostFuncs() { +// var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"; +// var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url))); +// +// // Our application KV store +// // Pretend this is redis or a database :) +// var kvStore = new HashMap(); +// +// ExtismFunction kvWrite = (plugin, params, returns, data) -> { +// System.out.println("Hello from Java Host Function!"); +// var key = plugin.inputString(params[0]); +// var value = plugin.inputBytes(params[1]); +// System.out.println("Writing to key " + key); +// kvStore.put(key, value); +// }; +// +// ExtismFunction kvRead = (plugin, params, returns, data) -> { +// System.out.println("Hello from Java Host Function!"); +// var key = plugin.inputString(params[0]); +// System.out.println("Reading from key " + key); +// var value = kvStore.get(key); +// if (value == null) { +// // default to zeroed bytes +// var zero = new byte[]{0,0,0,0}; +// plugin.returnBytes(returns[0], zero); +// } else { +// plugin.returnBytes(returns[0], value); +// } +// }; +// +// HostFunction kvWriteHostFn = new HostFunction<>( +// "kv_write", +// new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64}, +// new LibExtism.ExtismValType[0], +// kvWrite, +// Optional.empty() +// ); +// +// HostFunction kvReadHostFn = new HostFunction<>( +// "kv_read", +// new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, +// new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64}, +// kvRead, +// Optional.empty() +// ); +// +// HostFunction[] functions = {kvWriteHostFn, kvReadHostFn}; +// var plugin = new Plugin(manifest, false, functions); +// var output = plugin.call("count_vowels", "Hello, World!"); +// } @Test public void shouldInvokeFunctionFromByteArrayWasmSource() {