diff --git a/providers/flagd/WASM_HOST_IMPORTS.md b/providers/flagd/WASM_HOST_IMPORTS.md new file mode 100644 index 000000000..ea9e2d870 --- /dev/null +++ b/providers/flagd/WASM_HOST_IMPORTS.md @@ -0,0 +1,219 @@ +# Required Host Imports for flagd-evaluator WASM Module + +The flagd-evaluator WASM module requires **exactly 9 host functions** to be provided. All are correctly implemented in `FlagdWasmRuntime.java`. + +## Host Functions Overview + +The WASM module imports these functions in the following priority: + +### CRITICAL (2 functions) - Required for proper operation + +1. **`get_current_time_unix_seconds`** - Provides current Unix timestamp +2. **`getRandomValues`** - Provides cryptographic entropy for hash table seeding + +### LEGACY (2 functions) - Kept for backward compatibility + +3. **`new_0`** - Creates Date object (legacy timestamp mechanism) +4. **`getTime`** - Gets timestamp from Date (legacy timestamp mechanism) + +### ERROR HANDLING (1 function) + +5. **`throw`** - Allows WASM to throw exceptions to host + +### NO-OPS (4 functions) - wasm-bindgen artifacts + +6. **`describe`** - Type description (no-op) +7. **`object_drop_ref`** - Object reference counting (no-op) +8. **`externref_table_grow`** - Externref table growth (no-op) +9. **`externref_table_set_null`** - Externref table null setting (no-op) + +## Complete Host Function Reference + +| # | Module | Function | Signature | Purpose | Implementation Status | +|---|--------|----------|-----------|---------|----------------------| +| 1 | `host` | `get_current_time_unix_seconds` | `() -> i64` | **CRITICAL:** Provides Unix timestamp for `$flagd.timestamp` context enrichment | ✅ `createGetCurrentTimeUnixSeconds()` | +| 2 | `__wbindgen_placeholder__` | `__wbg_getRandomValues_1c61fac11405ffdc` | `(i32, i32) -> nil` | **CRITICAL:** Provides cryptographic entropy for ahash (used by boon validation) | ✅ `createGetRandomValues()` | +| 3 | `__wbindgen_placeholder__` | `__wbg_new_0_23cedd11d9b40c9d` | `() -> i32` | **LEGACY:** Creates Date object (may be removed in future) | ✅ `createNew0()` | +| 4 | `__wbindgen_placeholder__` | `__wbg_getTime_ad1e9878a735af08` | `(i32) -> f64` | **LEGACY:** Gets timestamp from Date object | ✅ `createGetTime()` | +| 5 | `__wbindgen_placeholder__` | `__wbg___wbindgen_throw_dd24417ed36fc46e` | `(i32, i32) -> nil` | Allows WASM to throw exceptions with error messages | ✅ `createWbindgenThrow()` | +| 6 | `__wbindgen_placeholder__` | `__wbindgen_describe` | `(i32) -> nil` | Type description (wasm-bindgen artifact, no-op) | ✅ `createDescribe()` | +| 7 | `__wbindgen_placeholder__` | `__wbindgen_object_drop_ref` | `(i32) -> nil` | Object reference counting (wasm-bindgen artifact, no-op) | ✅ `createObjectDropRef()` | +| 8 | `__wbindgen_externref_xform__` | `__wbindgen_externref_table_grow` | `(i32) -> i32` | Externref table growth (wasm-bindgen artifact, no-op) | ✅ `createExternrefTableGrow()` | +| 9 | `__wbindgen_externref_xform__` | `__wbindgen_externref_table_set_null` | `(i32) -> nil` | Externref table null setting (wasm-bindgen artifact, no-op) | ✅ `createExternrefTableSetNull()` | + +## Implementation Details + +### 1. `get_current_time_unix_seconds` - CRITICAL + +**Purpose:** Provides the current Unix timestamp for `$flagd.timestamp` context enrichment in targeting rules. + +**Implementation:** +```java +private static HostFunction createGetCurrentTimeUnixSeconds() { + return new HostFunction( + "host", + "get_current_time_unix_seconds", + FunctionType.of( + List.of(), + List.of(ValType.I64) + ), + (Instance instance, long... args) -> { + long currentTimeSeconds = System.currentTimeMillis() / 1000; + return new long[] {currentTimeSeconds}; + } + ); +} +``` + +**Why this matters:** +- Feature flags can use `$flagd.timestamp` in targeting rules for time-based logic +- Without this function, `$flagd.timestamp` defaults to `0`, breaking time-based targeting +- This is the primary mechanism for time access (the Date-based functions are legacy) + +### 2. `getRandomValues` - CRITICAL + +**Purpose:** Provides cryptographic entropy for `ahash` hash table seeding (used by `boon` JSON schema validation). + +**Implementation:** +```java +// Static SecureRandom instance for efficiency (reused across calls) +private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + +private static HostFunction createGetRandomValues() { + return new HostFunction( + "__wbindgen_placeholder__", + "__wbg_getRandomValues_1c61fac11405ffdc", + FunctionType.of( + List.of(ValType.I32, ValType.I32), + List.of() + ), + (Instance instance, long... args) -> { + // CRITICAL: This is called by getrandom to get entropy for ahash + // ahash uses this for hash table seed generation in boon validation + int typedArrayPtr = (int) args[0]; // Unused externref handle + int bufferPtr = (int) args[1]; // Pointer to buffer in WASM memory + + // The WASM code expects a 32-byte buffer at bufferPtr + // Fill it with cryptographically secure random bytes + byte[] randomBytes = new byte[32]; + SECURE_RANDOM.nextBytes(randomBytes); + + Memory memory = instance.memory(); + memory.write(bufferPtr, randomBytes); + + return null; + } + ); +} +``` + +**Why this matters:** +- The `boon` JSON schema validator uses `ahash` for hash maps +- `ahash` calls `getrandom` to get entropy for hash table seeds +- `getrandom` calls this host function to fill a buffer with random bytes +- **Without proper random bytes, validation will fail or panic** + +### 3-4. Date-based Timestamp Functions - LEGACY + +**Purpose:** Legacy mechanism for getting timestamps via JavaScript Date objects. + +**Status:** These are kept for backward compatibility but are superseded by `get_current_time_unix_seconds`. They may be removed in a future version once the WASM module is updated to only use the direct host function. + +**Implementation:** +```java +// Creates a dummy Date object reference +private static HostFunction createNew0() { + return new HostFunction( + "__wbindgen_placeholder__", + "__wbg_new_0_23cedd11d9b40c9d", + FunctionType.of(List.of(), List.of(ValType.I32)), + (Instance instance, long... args) -> new long[] {0L} + ); +} + +// Returns current time in milliseconds as f64 +private static HostFunction createGetTime() { + return new HostFunction( + "__wbindgen_placeholder__", + "__wbg_getTime_ad1e9878a735af08", + FunctionType.of(List.of(ValType.I32), List.of(ValType.F64)), + (Instance instance, long... args) -> { + return new long[] {Double.doubleToRawLongBits((double) System.currentTimeMillis())}; + } + ); +} +``` + +### 5. `throw` - Error Handling + +**Purpose:** Allows the WASM module to throw exceptions with error messages to the Java host. + +**Implementation:** +```java +private static HostFunction createWbindgenThrow() { + return new HostFunction( + "__wbindgen_placeholder__", + "__wbg___wbindgen_throw_dd24417ed36fc46e", + FunctionType.of(List.of(ValType.I32, ValType.I32), List.of()), + (Instance instance, long... args) -> { + // Read error message from WASM memory + int ptr = (int) args[0]; + int len = (int) args[1]; + Memory memory = instance.memory(); + String message = memory.readString(ptr, len); + throw new RuntimeException("WASM threw: " + message); + } + ); +} +``` + +### 6-9. No-op Functions - wasm-bindgen Artifacts + +These functions are required by the wasm-bindgen glue code but don't need to do anything in our context: + +```java +// All return appropriate no-op values +private static HostFunction createDescribe() { /* returns null */ } +private static HostFunction createObjectDropRef() { /* returns null */ } +private static HostFunction createExternrefTableGrow() { /* returns 128L */ } +private static HostFunction createExternrefTableSetNull() { /* returns null */ } +``` + +## Verification + +You can verify which host functions are actually imported by the WASM module using: + +```bash +wasm-objdump -x flagd_evaluator.wasm | grep "Import\[" -A 10 +``` + +This will show exactly 9 imported functions matching the table above. + +## Migration Notes + +### Removed Functions + +The following functions were previously documented but are **NOT** required by the current WASM module and have been removed from `FlagdWasmRuntime.java`: + +- `__wbindgen_rethrow` +- `__wbindgen_memory` +- `__wbindgen_is_undefined` +- `__wbindgen_string_new` +- `__wbindgen_number_get` +- `__wbindgen_boolean_get` +- `__wbindgen_is_null` +- `__wbindgen_is_object` +- `__wbindgen_is_string` +- `__wbindgen_object_clone_ref` +- `__wbindgen_jsval_eq` +- `__wbindgen_error_new` + +These were wasm-bindgen artifacts that are not actually called by the compiled WASM module. + +## Summary + +✅ All 9 required host functions are correctly implemented in `FlagdWasmRuntime.java` + +✅ The two critical functions (`get_current_time_unix_seconds` and `getRandomValues`) provide essential functionality + +✅ The implementation has been cleaned up to include only what's actually needed diff --git a/providers/flagd/flagd-evaluator-java-0.1.0-SNAPSHOT.jar b/providers/flagd/flagd-evaluator-java-0.1.0-SNAPSHOT.jar new file mode 100644 index 000000000..30f8d8996 Binary files /dev/null and b/providers/flagd/flagd-evaluator-java-0.1.0-SNAPSHOT.jar differ diff --git a/providers/flagd/pom.xml b/providers/flagd/pom.xml index c26ff014b..0f006ea3d 100644 --- a/providers/flagd/pom.xml +++ b/providers/flagd/pom.xml @@ -17,6 +17,7 @@ **/e2e/*.java 1.77.0 + 1.6.1 3.25.6 @@ -175,6 +176,29 @@ 2.0.17 test + + dev.openfeature + flagd-evaluator-java + 0.1.0-SNAPSHOT + ${project.basedir}/flagd-evaluator-java-0.1.0-SNAPSHOT.jar + system + + + com.dylibso.chicory + runtime + ${chicory.version} + + + com.dylibso.chicory + wasm + ${chicory.version} + + + com.dylibso.chicory + compiler + ${chicory.version} + + diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java index caf864175..742d8ae0b 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java @@ -2,7 +2,7 @@ import dev.openfeature.contrib.providers.flagd.resolver.Resolver; import dev.openfeature.contrib.providers.flagd.resolver.common.FlagdProviderEvent; -import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver; +import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessWasmResolver; import dev.openfeature.contrib.providers.flagd.resolver.rpc.RpcResolver; import dev.openfeature.contrib.providers.flagd.resolver.rpc.cache.Cache; import dev.openfeature.sdk.EvaluationContext; @@ -79,7 +79,7 @@ public FlagdProvider(final FlagdOptions options) { switch (options.getResolverType().asString()) { case Config.RESOLVER_FILE: case Config.RESOLVER_IN_PROCESS: - this.flagResolver = new InProcessResolver(options, this::onProviderEvent); + this.flagResolver = new InProcessWasmResolver(options, this::onProviderEvent); break; case Config.RESOLVER_RPC: this.flagResolver = new RpcResolver( diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/FlagsChangedResponse.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/FlagsChangedResponse.java new file mode 100644 index 000000000..db7c48262 --- /dev/null +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/FlagsChangedResponse.java @@ -0,0 +1,43 @@ +package dev.openfeature.contrib.providers.flagd.resolver.process; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +public class FlagsChangedResponse { + + @JsonProperty("success") + private boolean success; + + @JsonProperty("changedFlags") + private List changedFlags; + + // Constructors + public FlagsChangedResponse() {} + + public FlagsChangedResponse(boolean success, List changedFlags) { + this.success = success; + this.changedFlags = changedFlags; + } + + // Getters and Setters + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public List getChangedFlags() { + return changedFlags; + } + + public void setChangedFlags(List changedFlags) { + this.changedFlags = changedFlags; + } + + @Override + public String toString() { + return "FlagsChangedResponse{" + "success=" + success + ", changedFlags=" + changedFlags + '}'; + } +} diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java new file mode 100644 index 000000000..427e147ed --- /dev/null +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java @@ -0,0 +1,203 @@ +package dev.openfeature.contrib.providers.flagd.resolver.process; + +import static dev.openfeature.contrib.providers.flagd.resolver.common.Convert.convertProtobufMapToStructure; + +import com.google.protobuf.Struct; +import dev.openfeature.contrib.providers.flagd.FlagdOptions; +import dev.openfeature.contrib.providers.flagd.resolver.Resolver; +import dev.openfeature.contrib.providers.flagd.resolver.common.FlagdProviderEvent; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueuePayload; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueueSource; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.file.FileQueueSource; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.sync.SyncStreamQueueSource; +import dev.openfeature.flagd.evaluator.EvaluationResult; +import dev.openfeature.flagd.evaluator.EvaluatorException; +import dev.openfeature.flagd.evaluator.FlagEvaluator; +import dev.openfeature.flagd.evaluator.UpdateStateResult; +import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.ImmutableStructure; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.ProviderEvent; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Structure; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.GeneralError; +import java.util.Map; +import java.util.function.Consumer; +import lombok.extern.slf4j.Slf4j; + +/** + * Resolves flag values using + * https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1. + * Flags are evaluated locally. + */ +@Slf4j +public class InProcessWasmResolver implements Resolver { + + private final Consumer onConnectionEvent; + private final QueueSource connector; + private Thread stateWatcher; + private FlagEvaluator evaluator; + + /** + * Resolves flag values using + * https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1. + * Flags are evaluated locally. + * + * @param options flagd options + * @param onConnectionEvent lambda which handles changes in the + * connection/stream + */ + public InProcessWasmResolver(FlagdOptions options, Consumer onConnectionEvent) { + this.evaluator = new FlagEvaluator(FlagEvaluator.ValidationMode.PERMISSIVE); + + this.onConnectionEvent = onConnectionEvent; + this.connector = getConnector(options); + } + + /** + * Initialize in-process resolver. + */ + public void init() throws Exception { + + connector.init(); + this.stateWatcher = new Thread(() -> { + var streamPayloads = connector.getStreamQueue(); + while (!Thread.currentThread().isInterrupted()) { + try { + final QueuePayload payload = streamPayloads.take(); + switch (payload.getType()) { + case DATA: + try { + UpdateStateResult updateStateResult = evaluator.updateState(payload.getFlagData()); + + Structure syncContext = parseSyncContext(payload.getSyncContext()); + if (updateStateResult.isSuccess()) { + onConnectionEvent.accept(new FlagdProviderEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + updateStateResult.getChangedFlags(), + syncContext)); + } + } catch (EvaluatorException e) { + log.error("Error updating state from WASM evaluator", e); + } + + + break; + case ERROR: + log.error("Received error payload from connector"); + break; + default: + log.warn(String.format("Payload with unknown type: %s", payload.getType())); + } + } catch (InterruptedException e) { + log.debug("Storage state watcher interrupted", e); + Thread.currentThread().interrupt(); + break; + } + } + }); + this.stateWatcher.setDaemon(true); + this.stateWatcher.start(); + } + + /** + * Shutdown in-process resolver. + * + * @throws InterruptedException if stream can't be closed within deadline. + */ + public void shutdown() throws InterruptedException { + if (stateWatcher != null) { + stateWatcher.interrupt(); + } + connector.shutdown(); + } + + /** + * Resolve a boolean flag. + */ + public ProviderEvaluation booleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + ProviderEvaluation resolve = resolve(Boolean.class, key, ctx); + return resolve; + } + + /** + * Resolve a string flag. + */ + public ProviderEvaluation stringEvaluation(String key, String defaultValue, EvaluationContext ctx) { + ProviderEvaluation resolve = resolve(String.class, key, ctx); + return resolve; + } + + /** + * Resolve a double flag. + */ + public ProviderEvaluation doubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { + return resolve(Double.class, key, ctx); + } + + /** + * Resolve an integer flag. + */ + public ProviderEvaluation integerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { + return resolve(Integer.class, key, ctx); + } + + /** + * Resolve an object flag. + */ + public ProviderEvaluation objectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { + final ProviderEvaluation evaluation = resolve(Object.class, key, ctx); + + return ProviderEvaluation.builder() + .value(Value.objectToValue(evaluation.getValue())) + .variant(evaluation.getVariant()) + .reason(evaluation.getReason()) + .errorCode(evaluation.getErrorCode()) + .errorMessage(evaluation.getErrorMessage()) + .flagMetadata(evaluation.getFlagMetadata()) + .build(); + } + + static QueueSource getConnector(final FlagdOptions options) { + if (options.getCustomConnector() != null) { + return options.getCustomConnector(); + } + return options.getOfflineFlagSourcePath() != null + && !options.getOfflineFlagSourcePath().isEmpty() + ? new FileQueueSource(options.getOfflineFlagSourcePath(), options.getOfflinePollIntervalMs()) + : new SyncStreamQueueSource(options); + } + + private ProviderEvaluation resolve(Class type, String key, EvaluationContext ctx) { + try { + EvaluationResult evaluationResult = evaluator.evaluateFlag(type, key, ctx); + return new ProviderEvaluation<>( + evaluationResult.getValue(), + evaluationResult.getVariant(), + evaluationResult.getReason(), + ErrorCode.valueOf(evaluationResult.getErrorCode()), + evaluationResult.getErrorMessage(), + evaluationResult.getFlagMetadata() + ); + } catch (EvaluatorException e) { + return ProviderEvaluation.builder() + .reason(Reason.ERROR.toString()) + .errorCode(ErrorCode.GENERAL) + .errorMessage("Error during wasm evaluation: " + e.getMessage()) + .build(); + } + } + + private Structure parseSyncContext(Struct syncContext) { + if (syncContext != null) { + try { + return convertProtobufMapToStructure(syncContext.getFieldsMap()); + } catch (Exception exception) { + log.error("Failed to parse metadataResponse, provider metadata may not be up-to-date"); + } + } + return new ImmutableStructure(); + } +} diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java index c694aa9ef..8c6d24f4b 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java @@ -23,7 +23,7 @@ @IncludeEngines("cucumber") @SelectDirectories("test-harness/gherkin") // if you want to run just one feature file, use the following line instead of @SelectDirectories -// @SelectFile("test-harness/gherkin/selector.feature") +// @SelectFile("test-harness/gherkin/evaluation.feature") @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory") diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java index 27806f955..012b9f98c 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java @@ -72,7 +72,7 @@ public void tearDown() { @Given("a {} flagd provider") public void setupProvider(String providerType) throws InterruptedException { String flagdConfig = "default"; - state.builder.deadline(1000).keepAlive(0).retryGracePeriod(2); + state.builder.deadline(5000).keepAlive(0).retryGracePeriod(2); boolean wait = true; switch (providerType) { diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/WasmResolverTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/WasmResolverTest.java new file mode 100644 index 000000000..95c618188 --- /dev/null +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/WasmResolverTest.java @@ -0,0 +1,72 @@ +package dev.openfeature.contrib.providers.flagd.resolver.process; + +import dev.openfeature.contrib.providers.flagd.FlagdOptions; +import dev.openfeature.contrib.providers.flagd.resolver.Resolver; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.LayeredEvaluationContext; +import dev.openfeature.sdk.Value; +import java.util.HashMap; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class WasmResolverTest { + private static InProcessWasmResolver RESOLVER; + private static InProcessResolver IN_PROCESS; + + static { + System.out.println("Creating InProcessWasmResolver..."); + FlagdOptions options = + FlagdOptions.builder().offlineFlagSourcePath("test-harness/flags/custom-ops.json").build(); + RESOLVER = new InProcessWasmResolver(options, event -> {}); + IN_PROCESS = new InProcessResolver(options, event -> {}); + try { + RESOLVER.init(); + IN_PROCESS.init(); + } catch (Exception e) { + throw new RuntimeException(e); + } + System.out.println("InProcessWasmResolver created successfully!"); + } + + private EvaluationContext apictx; + private EvaluationContext ctx; + + { + HashMap ctxData = new HashMap<>(); + IntStream.range(0, 100).forEach(idx -> { + // Do something + ctxData.put("key" + idx, new Value(idx)); + }); + apictx = new ImmutableContext(ctxData); + ctx = new LayeredEvaluationContext( + apictx, ImmutableContext.EMPTY, ImmutableContext.EMPTY, ImmutableContext.EMPTY); + + } + + @ParameterizedTest + @MethodSource("resolvers") + public void testWasmResolverInit(Resolver resolver) throws Exception { + IntStream.range(0, 1000).forEach((i) -> + resolver.booleanEvaluation("flag" + i, false, ctx)); + } + + @ParameterizedTest + @MethodSource("resolvers") + public void testWasmResolverWithoutCTX(Resolver resolver) throws Exception { + IntStream.range(0, 1000).forEach((i) -> + resolver.booleanEvaluation("flag" + i, false, ImmutableContext.EMPTY)); + + } + + public static Stream resolvers() { + return Stream.of( + Arguments.of(RESOLVER), + Arguments.of(IN_PROCESS) + ); + } +} diff --git a/providers/flagd/test-harness b/providers/flagd/test-harness index b62f5dbe8..a930ea38a 160000 --- a/providers/flagd/test-harness +++ b/providers/flagd/test-harness @@ -1 +1 @@ -Subproject commit b62f5dbe860ecf4f36ec757dfdc0b38f7b3dec6e +Subproject commit a930ea38a59865b9631602824058c1ace7c8dbd4