Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions providers/flagd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<io.grpc.version>1.77.0</io.grpc.version>
<!-- caution - updating this will break compatibility with older protobuf-java versions -->
<protobuf-java.min.version>3.25.6</protobuf-java.min.version>
<com.vmlens.version>1.2.22</com.vmlens.version>
</properties>

<name>flagd</name>
Expand Down Expand Up @@ -175,6 +176,13 @@
<version>2.0.17</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.vmlens</groupId>
<artifactId>api</artifactId>
<version>${com.vmlens.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down Expand Up @@ -288,6 +296,25 @@
<onlyAnalyze>dev.openfeature.contrib.-</onlyAnalyze>
</configuration>
</plugin>
<plugin>
<groupId>com.vmlens</groupId>
<artifactId>vmlens-maven-plugin</artifactId>
<version>${com.vmlens.version}</version>
<executions>
<execution>
<id>test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<includes>
<include>**/*CT.java</include>
</includes>
<failIfNoTests>true</failIfNoTests>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void setEnrichedContext(EvaluationContext context) {
* @return true iff this was the first call to {@code initialize()}
*/
public synchronized boolean initialize() {
if (this.initialized) {
if (this.initialized || this.isShutDown) {
return false;
}
this.initialized = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationC
return ProviderEvaluation.<T>builder()
.errorMessage("flag: " + key + " not found")
.errorCode(ErrorCode.FLAG_NOT_FOUND)
.flagMetadata(getFlagMetadata(storageQueryResult))
.flagMetadata(getFlagMetadata(storageQueryResult, scope))
.build();
}

Expand All @@ -175,7 +175,7 @@ private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationC
return ProviderEvaluation.<T>builder()
.errorMessage("flag: " + key + " is disabled")
.errorCode(ErrorCode.FLAG_NOT_FOUND)
.flagMetadata(getFlagMetadata(storageQueryResult))
.flagMetadata(getFlagMetadata(storageQueryResult, scope))
.build();
}

Expand Down Expand Up @@ -210,7 +210,7 @@ private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationC
.reason(Reason.ERROR.toString())
.errorCode(ErrorCode.FLAG_NOT_FOUND)
.errorMessage("Flag '" + key + "' has no default variant defined, will use code default")
.flagMetadata(getFlagMetadata(storageQueryResult))
.flagMetadata(getFlagMetadata(storageQueryResult, scope))
.build();
}

Expand All @@ -235,11 +235,11 @@ private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationC
.value((T) value)
.variant(resolvedVariant)
.reason(reason)
.flagMetadata(getFlagMetadata(storageQueryResult))
.flagMetadata(getFlagMetadata(storageQueryResult, scope))
.build();
}

private ImmutableMetadata getFlagMetadata(StorageQueryResult storageQueryResult) {
private static ImmutableMetadata getFlagMetadata(StorageQueryResult storageQueryResult, String scope) {
ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder = ImmutableMetadata.builder();
for (Map.Entry<String, Object> entry :
storageQueryResult.getFlagSetMetadata().entrySet()) {
Expand All @@ -260,7 +260,7 @@ private ImmutableMetadata getFlagMetadata(StorageQueryResult storageQueryResult)
return metadataBuilder.build();
}

private void addEntryToMetadataBuilder(
private static void addEntryToMetadataBuilder(
ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder, String key, Object value) {
if (value instanceof Number) {
if (value instanceof Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import lombok.EqualsAndHashCode;
Expand Down Expand Up @@ -39,7 +40,7 @@ public FeatureFlag(
this.variants = variants;
this.targeting = targeting;
if (metadata == null) {
this.metadata = new HashMap<>();
this.metadata = Collections.emptyMap();
} else {
this.metadata = metadata;
}
Expand All @@ -51,7 +52,7 @@ public FeatureFlag(String state, String defaultVariant, Map<String, Object> vari
this.defaultVariant = defaultVariant;
this.variants = variants;
this.targeting = targeting;
this.metadata = new HashMap<>();
this.metadata = Collections.emptyMap();
}

/** Get targeting rule of the flag. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package dev.openfeature.contrib.providers.flagd;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.vmlens.api.AllInterleavings;
import com.vmlens.api.Runner;
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.Value;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class FlagdProviderCT {
private FlagdProvider provider;

@BeforeEach
void setup() throws Exception {
provider = FlagdTestUtils.createInProcessProvider(
Map.of(
"flag",
new FeatureFlag(
"ENABLED",
"a",
Map.of("a", "a", "b", "b", "c", "c"),
"{\n"
+ " \"if\": [\n"
+ " {\n"
+ " \"ends_with\": [\n"
+ " {\n"
+ " \"var\": \"email\"\n"
+ " },\n"
+ " \"@ingen.com\"\n"
+ " ]\n"
+ " },\n"
+ " \"b\",\n"
+ " \"c\"\n"
+ " ]\n"
+ " }",
null
)
)
);
provider.initialize(ImmutableContext.EMPTY);
}

@Test
void concurrentFlagEvaluationsWork() {
var invocationContext = ImmutableContext.EMPTY;

try (var interleavings = new AllInterleavings("Concurrent Flag evaluations")) {
while (interleavings.hasNext()) {
Runner.runParallel(
() -> assertEquals("c",
provider.getStringEvaluation("flag", "z", invocationContext).getValue()),
() -> assertEquals("c",
provider.getStringEvaluation("flag", "z", invocationContext).getValue())
);
}
}
}

@Test
void flagEvaluationsWhileSettingContextWork() {
var invocationContext = ImmutableContext.EMPTY;

OpenFeatureAPI.getInstance().setProviderAndWait(provider);
var client = OpenFeatureAPI.getInstance().getClient();

var context = new ImmutableContext(Map.of("email", new Value("someone@ingen.com")));

try (var interleavings = new AllInterleavings("Concurrently setting client context and evaluating a Flag")) {
while (interleavings.hasNext()) {
Runner.runParallel(
() -> assertTrue(List.of("b", "c")
.contains(provider.getStringEvaluation("flag", "z", invocationContext).getValue())),
() -> client.setEvaluationContext(context)
);
}
}
}

@Test
void settingDifferentContextsWorks() {

OpenFeatureAPI.getInstance().setProviderAndWait(provider);
var client = OpenFeatureAPI.getInstance().getClient();

var clientContext = new ImmutableContext(Map.of("email", new Value("someone@ingen.com")));
var apiContext = new ImmutableContext(Map.of("email", new Value("someone.else@test.com")));

try (var interleavings = new AllInterleavings("Concurrently setting client and api context")) {
while (interleavings.hasNext()) {
Runner.runParallel(
() -> client.setEvaluationContext(clientContext),
() -> OpenFeatureAPI.getInstance().setEvaluationContext(apiContext),
() -> assertTrue(List.of("b", "c")
.contains(provider.getStringEvaluation("flag", "z", ImmutableContext.EMPTY).getValue()))
);
}
}
}
}
Loading
Loading