Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public final class Config {
static final String DEFAULT_HOST = "localhost";

static final int DEFAULT_DEADLINE = 500;
static final int DEFAULT_MAX_RETRY_BACKOFF_MS = 12000;
static final int DEFAULT_MAX_RETRY_BACKOFF_MS = 5000;
static final int DEFAULT_STREAM_DEADLINE_MS = 10 * 60 * 1000;
static final int DEFAULT_STREAM_RETRY_GRACE_PERIOD = 5;
static final int DEFAULT_STREAM_RETRY_GRACE_PERIOD = 10;
static final int DEFAULT_MAX_CACHE_SIZE = 1000;
static final int DEFAULT_OFFLINE_POLL_MS = 5000;
static final long DEFAULT_KEEP_ALIVE = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public class ChannelBuilder {
Collections.singletonMap("service", "flagd.evaluation.v2.Service")));
put("retryPolicy", new HashMap() {
{
// 1 + 2 + 4
put("maxAttempts", 3.0); // types used here are important, need to be doubles
// total attempts = initial + 3 retries (backoff 1s, 2s, 4s)
put("maxAttempts", 4.0); // types used here are important, need to be doubles
put("initialBackoff", "1s");
put(
"maxBackoff",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"events",
"contextEnrichment",
"fractional-v1",
"fractional-v3",
"deprecated"
})
@Testcontainers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps")
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
@IncludeTags("in-process")
@ExcludeTags({"unixsocket", "fractional-v1", "deprecated"})
@ExcludeTags({"unixsocket", "fractional-v1", "fractional-v3", "deprecated"})
@Testcontainers
public class RunInProcessTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps")
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
@IncludeTags({"rpc"})
@ExcludeTags({"unixsocket", "fractional-v1", "deprecated"})
@ExcludeTags({"unixsocket", "fractional-v1", "fractional-v3", "deprecated"})
@Testcontainers
public class RunRpcTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ public class State {
/** The container borrowed from {@link ContainerPool} for this scenario. */
public ContainerEntry containerEntry;

public ConcurrentLinkedQueue<Event> events = new ConcurrentLinkedQueue<>();
// events not yet consumed by a positive assertion; drained as they are matched
public ConcurrentLinkedQueue<Event> assertedEvents = new ConcurrentLinkedQueue<>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i'm understanding this correctly but would notAssertedEvents be a better name then?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for "negative" assertions - ie: make sure some event X was NOT fired - so we keep an un-drained list of all of them.

// complete log of every event emitted, used for "never fired" assertions
public ConcurrentLinkedQueue<Event> allEvents = new ConcurrentLinkedQueue<>();
public Optional<Event> lastEvent;
public FlagSteps.Flag flag;
public MutableContext context = new MutableContext();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.openfeature.contrib.providers.flagd.e2e.steps;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

import dev.openfeature.contrib.providers.flagd.e2e.State;
Expand All @@ -24,7 +25,9 @@ public EventSteps(State state) {
public void a_stale_event_handler(String eventType) {
state.client.on(mapEventType(eventType), eventDetails -> {
log.info("{} event tracked", eventType);
state.events.add(new Event(eventType, eventDetails));
Event event = new Event(eventType, eventDetails);
state.allEvents.add(event);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
state.assertedEvents.add(event);
});
}

Expand Down Expand Up @@ -53,20 +56,29 @@ public void eventHandlerShouldBeExecuted(String eventType) {
eventHandlerShouldBeExecutedWithin(eventType, EVENT_TIMEOUT_MS);
}

@Then("the {} event handler should not have been executed")
public void eventHandlerShouldNotHaveBeenExecuted(String eventType) {
// checks the full log, not assertedEvents: preceding positive
// assertions may have drained an intervening event of this type
assertThat(state.allEvents.stream().anyMatch(event -> event.type.equals(eventType)))
.as("no %s event should have fired", eventType)
.isFalse();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Then("the {} event handler should have been executed within {int}ms")
public void eventHandlerShouldBeExecutedWithin(String eventType, int ms) {
log.info("waiting for eventtype: {}", eventType);
await().alias("waiting for eventtype " + eventType)
.atMost(ms, MILLISECONDS)
.pollInterval(10, MILLISECONDS)
.until(() -> state.events.stream().anyMatch(event -> event.type.equals(eventType)));
.until(() -> state.assertedEvents.stream().anyMatch(event -> event.type.equals(eventType)));
// Drain all events up to and including the first match. This ensures that
// older events (e.g. a READY from before a disconnect) cannot satisfy a
// later assertion that expects a *new* event of the same type, while still
// preserving events that arrived *after* the match for subsequent steps.
Event matched = null;
while (!state.events.isEmpty()) {
Event head = state.events.poll();
while (!state.assertedEvents.isEmpty()) {
Event head = state.assertedEvents.poll();
if (head != null && head.type.equals(eventType)) {
matched = head;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ public void setupProvider(String providerType) throws InterruptedException {
state.builder
.deadline(1000)
.keepAlive(0)
.retryGracePeriod(2)
.retryGracePeriod(5)
.retryBackoffMs(500)
.retryBackoffMaxMs(2000);
.retryBackoffMaxMs(500);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since i saw that on slack, should just scale the real values by a factor?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I actually want to do in a follow-up, is get all these specific adjustments FROM gherkin.

Right now they are just hard coded in the test implementation which is annoying. I don't think we'll do a scaling thing because that would be complicated or a new config - but getting it from the gherkin will be much better... I just didn't want to do that here.

boolean wait = true;

switch (providerType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ private static String mapOptionNames(String option) {
propertyMapper.put("resolver", "resolverType");
propertyMapper.put("deadlineMs", "deadline");
propertyMapper.put("keepAliveTime", "keepAlive");
propertyMapper.put("retryBackoffMaxMs", "keepAlive");
propertyMapper.put("cache", "cacheType");

if (propertyMapper.get(option) != null) {
Expand Down
10 changes: 6 additions & 4 deletions providers/flagd/src/test/resources/junit-platform.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ cucumber.execution.parallel.enabled=true
# Override pool size via -Dflagd.e2e.pool.size=N if needed.
cucumber.execution.parallel.config.strategy=dynamic
cucumber.execution.parallel.config.dynamic.factor=1
# Scenarios tagged @env-var mutate System env vars globally.
# Serialise them behind an exclusive resource lock so concurrent scenarios
# don't clobber each other's environment variable state.
cucumber.execution.exclusive-resources.env-var.read-write=ENV_VARS
# Scenarios tagged @env-var mutate System env vars globally, which every config
# scenario reads when building FlagdOptions defaults. A plain ENV_VARS lock only
# serialises @env-var scenarios against each other, so a concurrent default-reading
# scenario could still observe a mutated var. Acquire the JUnit global lock so
# @env-var scenarios run in full isolation (no other scenario runs concurrently).
cucumber.execution.exclusive-resources.env-var.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY
# Scenarios tagged @grace involve container restart + reconnection timing.
# Running two concurrent restarts under parallel load can push the
# reconnection past the 12-second EVENT_TIMEOUT_MS threshold. Serialise
Expand Down
2 changes: 1 addition & 1 deletion providers/flagd/test-harness
Loading