diff --git a/src/main/java/io/roastedroot/proxywasm/impl/Imports.java b/src/main/java/io/roastedroot/proxywasm/impl/Imports.java index 7bd28b1..1c41873 100644 --- a/src/main/java/io/roastedroot/proxywasm/impl/Imports.java +++ b/src/main/java/io/roastedroot/proxywasm/impl/Imports.java @@ -1,5 +1,7 @@ package io.roastedroot.proxywasm.impl; +import static io.roastedroot.proxywasm.v1.Helpers.string; + import com.dylibso.chicory.experimental.hostmodule.annotations.HostModule; import com.dylibso.chicory.experimental.hostmodule.annotations.WasmExport; import com.dylibso.chicory.runtime.Instance; @@ -800,4 +802,64 @@ int proxyContinueStream(int arg) { // should never reach here return WasmResult.INTERNAL_FAILURE.getValue(); } + + @WasmExport + int proxyHttpCall( + int uriData, + int uriSize, + int headersData, + int headersSize, + int bodyData, + int bodySize, + int trailersData, + int trailersSize, + int timeout, + int returnCalloutID) { + + try { + var uri = string(readMemory(uriData, uriSize)); + var headers = decodeMap(headersData, headersSize); + var body = readMemory(bodyData, bodySize); + var trailers = decodeMap(trailersData, trailersSize); + + int calloutId = handler.httpCall(uri, headers, body, trailers, timeout); + + putUint32(returnCalloutID, calloutId); + return WasmResult.OK.getValue(); + + } catch (WasmException e) { + return e.result().getValue(); + } + } + + @WasmExport + int proxyDispatchHttpCall( + int upstreamNameData, + int upstreamNameSize, + int headersData, + int headersSize, + int bodyData, + int bodySize, + int trailersData, + int trailersSize, + int timeoutMilliseconds, + int returnCalloutID) { + + try { + var upstreamName = string(readMemory(upstreamNameData, upstreamNameSize)); + var headers = decodeMap(headersData, headersSize); + var body = readMemory(bodyData, bodySize); + var trailers = decodeMap(trailersData, trailersSize); + + int calloutId = + handler.dispatchHttpCall( + upstreamName, headers, body, trailers, timeoutMilliseconds); + + putUint32(returnCalloutID, calloutId); + return WasmResult.OK.getValue(); + + } catch (WasmException e) { + return e.result().getValue(); + } + } } diff --git a/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java b/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java index ba39ba6..76c8f38 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java @@ -1,5 +1,6 @@ package io.roastedroot.proxywasm.v1; +import java.util.HashMap; import java.util.Map; /** @@ -253,4 +254,15 @@ public WasmResult continueDownstream() { public WasmResult continueUpstream() { return next().continueUpstream(); } + + @Override + public int httpCall( + String uri, + HashMap headers, + byte[] body, + HashMap trailers, + int timeout) + throws WasmException { + return next().httpCall(uri, headers, body, trailers, timeout); + } } diff --git a/src/main/java/io/roastedroot/proxywasm/v1/Context.java b/src/main/java/io/roastedroot/proxywasm/v1/Context.java index d6ada63..ecc3cc3 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/Context.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/Context.java @@ -42,8 +42,6 @@ public void close() { } // plugin is indicating it wants to finish closing - // TODO: need a unit test for this. We likely can't implement the test until we provide tick - // callbacks to the plugin. WasmResult done() { if (!closeStarted) { // spec says: return NOT_FOUND when active context was not pending finalization. diff --git a/src/main/java/io/roastedroot/proxywasm/v1/Handler.java b/src/main/java/io/roastedroot/proxywasm/v1/Handler.java index 09cbb09..3258ccb 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/Handler.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/Handler.java @@ -1,5 +1,6 @@ package io.roastedroot.proxywasm.v1; +import java.util.HashMap; import java.util.Map; public interface Handler { @@ -160,11 +161,11 @@ default WasmResult done() { /** * Sets a low-resolution timer period (tick_period). * - * When set, the host will call proxy_on_tick every tick_period milliseconds. Setting tick_period to 0 disables the timer. + * When set, the host will call proxy_on_tick every tickPeriodMilliseconds milliseconds. Setting tickPeriodMilliseconds to 0 disables the timer. * * @return The current time in nanoseconds */ - default WasmResult setTickPeriodMilliseconds(int tick_period) { + default WasmResult setTickPeriodMilliseconds(int tickPeriodMilliseconds) { return WasmResult.UNIMPLEMENTED; } @@ -385,4 +386,24 @@ default WasmResult continueDownstream() { default WasmResult continueUpstream() { return WasmResult.UNIMPLEMENTED; } + + default int httpCall( + String uri, + HashMap headers, + byte[] body, + HashMap trailers, + int timeoutMilliseconds) + throws WasmException { + throw new WasmException(WasmResult.UNIMPLEMENTED); + } + + default int dispatchHttpCall( + String upstreamName, + HashMap headers, + byte[] body, + HashMap trailers, + int timeoutMilliseconds) + throws WasmException { + throw new WasmException(WasmResult.UNIMPLEMENTED); + } } diff --git a/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java b/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java index 0692a15..53b3f90 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java @@ -40,6 +40,9 @@ public final class ProxyWasm implements Closeable { private Context activeContext; private HashMap contexts = new HashMap<>(); + private Map httpCallResponseHeaders; + private Map httpCallResponseTrailers; + private byte[] httpCallResponseBody; private ProxyWasm(Builder other) throws StartException { this.vmConfig = other.vmConfig; @@ -151,6 +154,21 @@ public WasmResult setEffectiveContextID(int contextID) { public WasmResult done() { return activeContext.done(); } + + @Override + public Map getHttpCallResponseHeaders() { + return httpCallResponseHeaders; + } + + @Override + public Map getHttpCallResponseTrailers() { + return httpCallResponseTrailers; + } + + @Override + public byte[] getHttpCallResponseBody() { + return httpCallResponseBody; + } }; } @@ -229,6 +247,25 @@ public static ProxyWasm.Builder builder() { return new ProxyWasm.Builder(); } + public void callOnHttpCallResponse( + int calloutID, Map headers, Map trailers, byte[] body) { + + this.httpCallResponseHeaders = headers; + this.httpCallResponseTrailers = trailers; + this.httpCallResponseBody = body; + + this.exports.proxyOnHttpCallResponse( + pluginContext.id(), calloutID, len(headers), len(body), len(trailers)); + + this.httpCallResponseHeaders = null; + this.httpCallResponseTrailers = null; + this.httpCallResponseBody = null; + } + + public int contextId() { + return pluginContext.id(); + } + public static class Builder implements Cloneable { private Exports exports; diff --git a/src/test/go-examples/dispatch_call_on_tick/README.md b/src/test/go-examples/dispatch_call_on_tick/README.md new file mode 100644 index 0000000..6c0c082 --- /dev/null +++ b/src/test/go-examples/dispatch_call_on_tick/README.md @@ -0,0 +1,5 @@ +## Attribution + +This example originally came from: +https://github.com/proxy-wasm/proxy-wasm-go-sdk/tree/main/examples/dispatch_call_on_tick + diff --git a/src/test/go-examples/dispatch_call_on_tick/go.mod b/src/test/go-examples/dispatch_call_on_tick/go.mod new file mode 100644 index 0000000..f191b8a --- /dev/null +++ b/src/test/go-examples/dispatch_call_on_tick/go.mod @@ -0,0 +1,5 @@ +module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/dispatch_call_on_tick + +go 1.24 + +require github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 diff --git a/src/test/go-examples/dispatch_call_on_tick/go.sum b/src/test/go-examples/dispatch_call_on_tick/go.sum new file mode 100644 index 0000000..3ddb896 --- /dev/null +++ b/src/test/go-examples/dispatch_call_on_tick/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 h1:wTcK6gcyTKJMeDka69AMjZYvisdI8CBXzTEfZ+2pOxI= +github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924/go.mod h1:9mBRvh8I6Td6sg3CwEY+zGFE4DKaIoieCaca1kQnDBE= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/test/go-examples/dispatch_call_on_tick/main.go b/src/test/go-examples/dispatch_call_on_tick/main.go new file mode 100644 index 0000000..e42170c --- /dev/null +++ b/src/test/go-examples/dispatch_call_on_tick/main.go @@ -0,0 +1,97 @@ +// Copyright 2020-2024 Tetrate +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "crypto/rand" + + "github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm" + "github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types" +) + +const tickMilliseconds uint32 = 100 + +func main() {} +func init() { + proxywasm.SetVMContext(&vmContext{}) +} + +// vmContext implements types.VMContext. +type vmContext struct { + // Embed the default VM context here, + // so that we don't need to reimplement all the methods. + types.DefaultVMContext +} + +// NewPluginContext implements types.VMContext. +func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { + return &pluginContext{contextID: contextID} +} + +// pluginContext implements types.PluginContext. +type pluginContext struct { + // Embed the default plugin context here, + // so that we don't need to reimplement all the methods. + types.DefaultPluginContext + contextID uint32 + callBack func(numHeaders, bodySize, numTrailers int) + cnt int +} + +// OnPluginStart implements types.PluginContext. +func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { + if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil { + proxywasm.LogCriticalf("failed to set tick period: %v", err) + return types.OnPluginStartStatusFailed + } + proxywasm.LogInfof("set tick period milliseconds: %d", tickMilliseconds) + ctx.callBack = func(numHeaders, bodySize, numTrailers int) { + ctx.cnt++ + proxywasm.LogInfof("called %d for contextID=%d", ctx.cnt, ctx.contextID) + headers, err := proxywasm.GetHttpCallResponseHeaders() + if err != nil && err != types.ErrorStatusNotFound { + panic(err) + } + for _, h := range headers { + proxywasm.LogInfof("response header for the dispatched call: %s: %s", h[0], h[1]) + } + headers, err = proxywasm.GetHttpCallResponseTrailers() + if err != nil && err != types.ErrorStatusNotFound { + panic(err) + } + for _, h := range headers { + proxywasm.LogInfof("response trailer for the dispatched call: %s: %s", h[0], h[1]) + } + } + return types.OnPluginStartStatusOK +} + +// OnTick implements types.PluginContext. +func (ctx *pluginContext) OnTick() { + headers := [][2]string{ + {":method", "GET"}, {":authority", "some_authority"}, {"accept", "*/*"}, + } + // Pick random value to select the request path. + buf := make([]byte, 1) + _, _ = rand.Read(buf) + if buf[0]%2 == 0 { + headers = append(headers, [2]string{":path", "/ok"}) + } else { + headers = append(headers, [2]string{":path", "/fail"}) + } + if _, err := proxywasm.DispatchHttpCall("web_service", headers, nil, nil, 5000, ctx.callBack); err != nil { + proxywasm.LogCriticalf("dispatch httpcall failed: %v", err) + } +} diff --git a/src/test/go-examples/dispatch_call_on_tick/main.wasm b/src/test/go-examples/dispatch_call_on_tick/main.wasm new file mode 100644 index 0000000..e661312 Binary files /dev/null and b/src/test/go-examples/dispatch_call_on_tick/main.wasm differ diff --git a/src/test/go-examples/http_body_chunk/go.mod b/src/test/go-examples/http_body_chunk/go.mod index 0def967..a6cdd76 100644 --- a/src/test/go-examples/http_body_chunk/go.mod +++ b/src/test/go-examples/http_body_chunk/go.mod @@ -1,4 +1,4 @@ -module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body +module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body_chunk go 1.24 diff --git a/src/test/go-examples/http_headers/go.mod b/src/test/go-examples/http_headers/go.mod index ec1fb37..aaec77e 100644 --- a/src/test/go-examples/http_headers/go.mod +++ b/src/test/go-examples/http_headers/go.mod @@ -1,4 +1,4 @@ -module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body +module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_headers go 1.24 diff --git a/src/test/go-examples/properties/go.mod b/src/test/go-examples/properties/go.mod index 0def967..5d3791f 100644 --- a/src/test/go-examples/properties/go.mod +++ b/src/test/go-examples/properties/go.mod @@ -1,4 +1,4 @@ -module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/http_body +module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/properties go 1.24 diff --git a/src/test/java/io/roastedroot/proxywasm/DispatchCallOnTickTest.java b/src/test/java/io/roastedroot/proxywasm/DispatchCallOnTickTest.java new file mode 100644 index 0000000..52880c0 --- /dev/null +++ b/src/test/java/io/roastedroot/proxywasm/DispatchCallOnTickTest.java @@ -0,0 +1,47 @@ +package io.roastedroot.proxywasm; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.dylibso.chicory.wasm.Parser; +import io.roastedroot.proxywasm.v1.ProxyWasm; +import io.roastedroot.proxywasm.v1.StartException; +import java.nio.file.Path; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + */ +public class DispatchCallOnTickTest { + + static int tickMilliseconds = 100; + + @Test + public void testOnTick() throws StartException { + + var handler = new MockHandler(); + var module = + Parser.parse(Path.of("./src/test/go-examples/dispatch_call_on_tick/main.wasm")); + ProxyWasm.Builder builder = ProxyWasm.builder().withPluginHandler(handler); + try (var host = builder.build(module)) { + assertEquals(tickMilliseconds, handler.getTickPeriodMilliseconds()); + + for (int i = 1; i <= 10; i++) { + host.tick(); // call OnTick + } + + assertEquals(10, handler.getHttpCalls().size()); + handler.getHttpCalls().entrySet().stream() + .forEach( + entry -> { + host.callOnHttpCallResponse( + entry.getKey(), Map.of(), Map.of(), new byte[0]); + }); + + // Check Envoy logs. + for (int i = 1; i <= 10; i++) { + handler.assertLogsContain( + String.format("called %d for contextID=%d", i, host.contextId())); + } + } + } +} diff --git a/src/test/java/io/roastedroot/proxywasm/MockHandler.java b/src/test/java/io/roastedroot/proxywasm/MockHandler.java index 96a7ad6..e73c134 100644 --- a/src/test/java/io/roastedroot/proxywasm/MockHandler.java +++ b/src/test/java/io/roastedroot/proxywasm/MockHandler.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -42,12 +43,11 @@ public HttpResponse( final ArrayList loggedMessages = new ArrayList<>(); + private int tickPeriodMilliseconds; private Map httpRequestHeaders = new HashMap<>(); private Map httpRequestTrailers = new HashMap<>(); private Map httpResponseHeaders = new HashMap<>(); private Map httpResponseTrailers = new HashMap<>(); - private Map httpCallResponseHeaders = new HashMap<>(); - private Map httpCallResponseTrailers = new HashMap<>(); private Map grpcReceiveInitialMetadata = new HashMap<>(); private Map grpcReceiveTrailerMetadata = new HashMap<>(); private HttpResponse senthttpResponse; @@ -57,7 +57,6 @@ public HttpResponse( private byte[] httpResponseBody = new byte[0]; private byte[] downStreamData = new byte[0]; private byte[] upstreamData = new byte[0]; - private byte[] httpCallResponseBody = new byte[0]; private byte[] grpcReceiveBuffer = new byte[0]; static final boolean DEBUG = "true".equals(System.getenv("DEBUG")); @@ -95,6 +94,16 @@ public void assertLogsDoNotContain(String message) { } } + @Override + public WasmResult setTickPeriodMilliseconds(int tickPeriodMilliseconds) { + this.tickPeriodMilliseconds = tickPeriodMilliseconds; + return WasmResult.OK; + } + + public int getTickPeriodMilliseconds() { + return tickPeriodMilliseconds; + } + @Override public Map getHttpRequestHeaders() { return httpRequestHeaders; @@ -115,16 +124,6 @@ public Map getHttpResponseTrailers() { return httpResponseTrailers; } - @Override - public Map getHttpCallResponseHeaders() { - return httpCallResponseHeaders; - } - - @Override - public Map getHttpCallResponseTrailers() { - return httpCallResponseTrailers; - } - @Override public Map getGrpcReceiveInitialMetaData() { return grpcReceiveInitialMetadata; @@ -159,18 +158,6 @@ public WasmResult setHttpResponseTrailers(Map trailers) { return WasmResult.OK; } - @Override - public WasmResult setHttpCallResponseHeaders(Map headers) { - this.httpCallResponseHeaders = headers; - return WasmResult.OK; - } - - @Override - public WasmResult setHttpCallResponseTrailers(Map trailers) { - this.httpCallResponseTrailers = trailers; - return WasmResult.OK; - } - @Override public WasmResult setGrpcReceiveInitialMetaData(Map metadata) { this.grpcReceiveInitialMetadata = metadata; @@ -193,11 +180,6 @@ public byte[] getGrpcReceiveBuffer() { return this.grpcReceiveBuffer; } - @Override - public byte[] getHttpCallResponseBody() { - return this.httpCallResponseBody; - } - @Override public byte[] getUpstreamData() { return this.upstreamData; @@ -250,12 +232,6 @@ public WasmResult setUpstreamData(byte[] data) { return WasmResult.OK; } - @Override - public WasmResult setHttpCallResponseBody(byte[] body) { - this.httpCallResponseBody = body; - return WasmResult.OK; - } - @Override public WasmResult setGrpcReceiveBuffer(byte[] buffer) { this.grpcReceiveBuffer = buffer; @@ -288,4 +264,77 @@ public WasmResult sendHttpResponse( public HttpResponse getSentHttpResponse() { return senthttpResponse; } + + public static class HttpCall { + public enum Type { + REGULAR, + DISPATCH + } + + public final Type callType; + public final String uri; + public final Object headers; + public final byte[] body; + public final HashMap trailers; + public final int timeoutMilliseconds; + + public HttpCall( + Type callType, + String uri, + HashMap headers, + byte[] body, + HashMap trailers, + int timeoutMilliseconds) { + this.callType = callType; + this.uri = uri; + this.headers = headers; + this.body = body; + this.trailers = trailers; + this.timeoutMilliseconds = timeoutMilliseconds; + } + } + + private final AtomicInteger lastCallId = new AtomicInteger(0); + private final HashMap httpCalls = new HashMap<>(); + + public HashMap getHttpCalls() { + return httpCalls; + } + + @Override + public int httpCall( + String uri, + HashMap headers, + byte[] body, + HashMap trailers, + int timeoutMilliseconds) + throws WasmException { + var id = lastCallId.incrementAndGet(); + HttpCall value = + new HttpCall( + HttpCall.Type.REGULAR, uri, headers, body, trailers, timeoutMilliseconds); + httpCalls.put(id, value); + return id; + } + + @Override + public int dispatchHttpCall( + String upstreamName, + HashMap headers, + byte[] body, + HashMap trailers, + int timeoutMilliseconds) + throws WasmException { + var id = lastCallId.incrementAndGet(); + HttpCall value = + new HttpCall( + HttpCall.Type.DISPATCH, + upstreamName, + headers, + body, + trailers, + timeoutMilliseconds); + httpCalls.put(id, value); + return id; + } } diff --git a/src/test/java/io/roastedroot/proxywasm/OnRequestHeadersTest.java b/src/test/java/io/roastedroot/proxywasm/OnRequestHeadersTest.java index 8c3d2a0..b279fd9 100644 --- a/src/test/java/io/roastedroot/proxywasm/OnRequestHeadersTest.java +++ b/src/test/java/io/roastedroot/proxywasm/OnRequestHeadersTest.java @@ -10,9 +10,6 @@ import java.util.Map; import org.junit.jupiter.api.Test; -/** - * Unit test for simple App. - */ public class OnRequestHeadersTest { @Test diff --git a/src/test/java/io/roastedroot/proxywasm/TimersClocksRandomTest.java b/src/test/java/io/roastedroot/proxywasm/TimersClocksRandomTest.java index 2a0c513..c30a3f4 100644 --- a/src/test/java/io/roastedroot/proxywasm/TimersClocksRandomTest.java +++ b/src/test/java/io/roastedroot/proxywasm/TimersClocksRandomTest.java @@ -12,9 +12,6 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; -/** - * Unit test for simple App. - */ public class TimersClocksRandomTest { @Test diff --git a/src/test/java/io/roastedroot/proxywasm/VmPluginConfigurationTest.java b/src/test/java/io/roastedroot/proxywasm/VmPluginConfigurationTest.java index 1b05033..ea07b87 100644 --- a/src/test/java/io/roastedroot/proxywasm/VmPluginConfigurationTest.java +++ b/src/test/java/io/roastedroot/proxywasm/VmPluginConfigurationTest.java @@ -10,9 +10,6 @@ import java.util.Map; import org.junit.jupiter.api.Test; -/** - * Unit test for simple App. - */ public class VmPluginConfigurationTest { @Test