From 5cd8410fd5b70ba4f2f3de1fc3b92288260aab7b Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Tue, 25 Nov 2025 17:12:16 +0100
Subject: [PATCH 01/11] add flagd dep
Signed-off-by: christian.lutnik
---
pom.xml | 7 ++++
.../dev/openfeature/sdk/EventProvider.java | 4 +-
.../dev/openfeature/sdk/FeatureProvider.java | 3 +-
.../sdk/FeatureProviderStateManagerTest.java | 3 +-
.../sdk/NoBreakingChangesTest.java | 39 +++++++++++++++++++
.../testutils/testProvider/TestProvider.java | 3 +-
src/test/resources/testFlags.json | 14 +++++++
7 files changed, 69 insertions(+), 4 deletions(-)
create mode 100644 src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
create mode 100644 src/test/resources/testFlags.json
diff --git a/pom.xml b/pom.xml
index 6af087035..a315bf85e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -203,6 +203,13 @@
test
+
+ dev.openfeature.contrib.providers
+ flagd
+ 0.11.18
+ test
+
+
diff --git a/src/main/java/dev/openfeature/sdk/EventProvider.java b/src/main/java/dev/openfeature/sdk/EventProvider.java
index 4ccac184e..5b809505d 100644
--- a/src/main/java/dev/openfeature/sdk/EventProvider.java
+++ b/src/main/java/dev/openfeature/sdk/EventProvider.java
@@ -59,7 +59,7 @@ void detach() {
* or timeout period has elapsed.
*/
@Override
- public void shutdown() {
+ public boolean shutdown() {
emitterExecutor.shutdown();
try {
if (!emitterExecutor.awaitTermination(EventSupport.SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
@@ -70,6 +70,8 @@ public void shutdown() {
emitterExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
+
+ return true;
}
/**
diff --git a/src/main/java/dev/openfeature/sdk/FeatureProvider.java b/src/main/java/dev/openfeature/sdk/FeatureProvider.java
index 22819ef10..97bca9615 100644
--- a/src/main/java/dev/openfeature/sdk/FeatureProvider.java
+++ b/src/main/java/dev/openfeature/sdk/FeatureProvider.java
@@ -53,8 +53,9 @@ default void initialize(EvaluationContext evaluationContext) throws Exception {
* caught and logged.
*
*/
- default void shutdown() {
+ default boolean shutdown() {
// Intentionally left blank
+ return false;
}
/**
diff --git a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
index ff3f3a3f8..78c8093c2 100644
--- a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
+++ b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
@@ -199,8 +199,9 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
}
@Override
- public void shutdown() {
+ public boolean shutdown() {
shutdownCalled.incrementAndGet();
+ return true;
}
}
}
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
new file mode 100644
index 000000000..6becb6c3f
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -0,0 +1,39 @@
+package dev.openfeature.sdk;
+
+import org.junit.jupiter.api.Test;
+import dev.openfeature.contrib.providers.flagd.*;
+
+import java.net.URISyntaxException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class NoBreakingChangesTest {
+ @Test
+ void noBreakingChanges() throws URISyntaxException {
+ var testProvider = new FlagdProvider(
+ FlagdOptions.builder()
+ .resolverType(Config.Resolver.FILE)
+ .offlineFlagSourcePath(NoBreakingChangesTest.class.getResource("/testFlags.json").getPath().replaceFirst("/", ""))
+ .build()
+ );
+ var api = new OpenFeatureAPI();
+ api.setProviderAndWait(testProvider);
+
+ var client = api.getClient();
+ var flagFound =client.getBooleanDetails("basic-boolean", false);
+ assertThat(flagFound).isNotNull();
+ assertThat(flagFound.getValue()).isTrue();
+ assertThat(flagFound.getVariant()).isEqualTo("true");
+ assertThat(flagFound.getReason()).isEqualTo(Reason.STATIC.toString());
+
+ var flagNotFound =client.getStringDetails("unknown", "asd");
+ assertThat(flagNotFound).isNotNull();
+ assertThat(flagNotFound.getValue()).isEqualTo("asd");
+ assertThat(flagNotFound.getVariant()).isNull();
+ assertThat(flagNotFound.getReason()).isEqualTo(Reason.ERROR.toString());
+ assertThat(flagNotFound.getErrorCode()).isEqualTo(ErrorCode.FLAG_NOT_FOUND);
+
+ testProvider.shutdown();
+ api.shutdown();
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java b/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
index 383ade483..8a5cddb9b 100644
--- a/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
+++ b/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
@@ -91,9 +91,10 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
}
@Override
- public void shutdown() {
+ public boolean shutdown() {
super.shutdown();
isShutdown.set(true);
+ return true;
}
public boolean isShutdown() {
diff --git a/src/test/resources/testFlags.json b/src/test/resources/testFlags.json
new file mode 100644
index 000000000..ed5005823
--- /dev/null
+++ b/src/test/resources/testFlags.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://flagd.dev/schema/v0/flags.json",
+ "flags": {
+ "basic-boolean": {
+ "state": "ENABLED",
+ "defaultVariant": "true",
+ "variants": {
+ "true": true,
+ "false": false
+ },
+ "targeting": {}
+ }
+ }
+}
From 3c06a11f6ecc9276a6fc5c05e17a07fb24c82989 Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 08:34:59 +0100
Subject: [PATCH 02/11] things
Signed-off-by: christian.lutnik
---
src/main/java/dev/openfeature/sdk/EventProvider.java | 6 ++----
src/main/java/dev/openfeature/sdk/FeatureProvider.java | 3 +--
.../java/dev/openfeature/sdk/DeveloperExperienceTest.java | 4 ++--
.../openfeature/sdk/FeatureProviderStateManagerTest.java | 3 +--
.../java/dev/openfeature/sdk/NoBreakingChangesTest.java | 5 +++--
.../java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java | 4 ++--
.../sdk/testutils/TestStackedEmitCallsProvider.java | 2 +-
.../sdk/testutils/testProvider/TestProvider.java | 3 +--
8 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/src/main/java/dev/openfeature/sdk/EventProvider.java b/src/main/java/dev/openfeature/sdk/EventProvider.java
index 5b809505d..596ef16ca 100644
--- a/src/main/java/dev/openfeature/sdk/EventProvider.java
+++ b/src/main/java/dev/openfeature/sdk/EventProvider.java
@@ -59,7 +59,7 @@ void detach() {
* or timeout period has elapsed.
*/
@Override
- public boolean shutdown() {
+ public void shutdown() {
emitterExecutor.shutdown();
try {
if (!emitterExecutor.awaitTermination(EventSupport.SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
@@ -70,8 +70,6 @@ public boolean shutdown() {
emitterExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
-
- return true;
}
/**
@@ -114,7 +112,7 @@ public Awaitable emit(final ProviderEvent event, final ProviderEventDetails deta
*
* @param details The details of the event
*/
- public Awaitable emitProviderReady(ProviderEventDetails details) {
+ /*public*/ Awaitable emitProviderReady(ProviderEventDetails details) {
return emit(ProviderEvent.PROVIDER_READY, details);
}
diff --git a/src/main/java/dev/openfeature/sdk/FeatureProvider.java b/src/main/java/dev/openfeature/sdk/FeatureProvider.java
index 97bca9615..22819ef10 100644
--- a/src/main/java/dev/openfeature/sdk/FeatureProvider.java
+++ b/src/main/java/dev/openfeature/sdk/FeatureProvider.java
@@ -53,9 +53,8 @@ default void initialize(EvaluationContext evaluationContext) throws Exception {
* caught and logged.
*
*/
- default boolean shutdown() {
+ default void shutdown() {
// Intentionally left blank
- return false;
}
/**
diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
index 19108bde5..26dd8959c 100644
--- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
+++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
@@ -184,7 +184,7 @@ void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() {
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
- provider.emitProviderReady(ProviderEventDetails.builder().build()).await();
- assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+ //provider.emitProviderReady(ProviderEventDetails.builder().build()).await();
+ //assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
}
}
diff --git a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
index 78c8093c2..ff3f3a3f8 100644
--- a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
+++ b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java
@@ -199,9 +199,8 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
}
@Override
- public boolean shutdown() {
+ public void shutdown() {
shutdownCalled.incrementAndGet();
- return true;
}
}
}
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index 6becb6c3f..a051be039 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -3,13 +3,14 @@
import org.junit.jupiter.api.Test;
import dev.openfeature.contrib.providers.flagd.*;
-import java.net.URISyntaxException;
+import java.io.File;
+import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
class NoBreakingChangesTest {
@Test
- void noBreakingChanges() throws URISyntaxException {
+ void noBreakingChanges() {
var testProvider = new FlagdProvider(
FlagdOptions.builder()
.resolverType(Config.Resolver.FILE)
diff --git a/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java b/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java
index f22a0811a..0a23f9cb3 100644
--- a/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java
+++ b/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java
@@ -112,11 +112,11 @@ private void setupMockProvider(ErrorCode errorCode, String errorMessage, Provide
switch (providerState) {
case FATAL:
case ERROR:
- mockProvider.emitProviderReady(details).await();
+ // mockProvider.emitProviderReady(details).await();
mockProvider.emitProviderError(details).await();
break;
case STALE:
- mockProvider.emitProviderReady(details).await();
+ // mockProvider.emitProviderReady(details).await();
mockProvider.emitProviderStale(details).await();
break;
default:
diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java b/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
index d1bf65c57..84d6ddbbb 100644
--- a/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
+++ b/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
@@ -38,7 +38,7 @@ private void onProviderEvent(ProviderEvent providerEvent) {
* This line deadlocked in the original implementation without the emitterExecutor see
* https://github.com/open-feature/java-sdk/issues/1299
*/
- emitProviderReady(ProviderEventDetails.builder().build());
+ //emitProviderReady(ProviderEventDetails.builder().build());
}
}
}
diff --git a/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java b/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
index 8a5cddb9b..383ade483 100644
--- a/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
+++ b/src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
@@ -91,10 +91,9 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
}
@Override
- public boolean shutdown() {
+ public void shutdown() {
super.shutdown();
isShutdown.set(true);
- return true;
}
public boolean isShutdown() {
From a7e9f54bc6b21c83f37881423428df39fa0de444 Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:16:06 +0100
Subject: [PATCH 03/11] make test work
Signed-off-by: christian.lutnik
---
.../sdk/DeveloperExperienceTest.java | 4 +-
.../sdk/NoBreakingChangesTest.java | 70 ++++++++++++++++---
.../sdk/e2e/steps/ProviderSteps.java | 4 +-
.../TestStackedEmitCallsProvider.java | 3 +-
4 files changed, 64 insertions(+), 17 deletions(-)
diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
index 26dd8959c..a29a0fb0f 100644
--- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
+++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
@@ -184,7 +184,7 @@ void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() {
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
- //provider.emitProviderReady(ProviderEventDetails.builder().build()).await();
- //assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
+ // provider.emitProviderReady(ProviderEventDetails.builder().build()).await();
+ // assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
}
}
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index a051be039..e87de10a2 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -1,33 +1,81 @@
package dev.openfeature.sdk;
+import dev.openfeature.contrib.providers.flagd.Config;
+import dev.openfeature.contrib.providers.flagd.FlagdOptions;
+import dev.openfeature.contrib.providers.flagd.FlagdProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import dev.openfeature.contrib.providers.flagd.*;
-import java.io.File;
-import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;
class NoBreakingChangesTest {
+
+ private AtomicBoolean isTestRunning;
+ private ConcurrentLinkedQueue uncaughtExceptions;
+ private Thread threadWatcher;
+
+ @BeforeEach
+ void setup() {
+ final var isRunning = new AtomicBoolean(true);
+ final var uncaught = new ConcurrentLinkedQueue();
+ uncaughtExceptions = uncaught;
+ isTestRunning = isRunning;
+
+ threadWatcher = new Thread(() -> {
+ var seenThreads = new HashSet();
+ while (isRunning.get()) {
+ var stacks = Thread.getAllStackTraces();
+ for (var entry : stacks.entrySet()) {
+ var thread = entry.getKey();
+ if (seenThreads.add(thread)) {
+ thread.setUncaughtExceptionHandler((thread1, throwable) -> {
+ uncaught.add(throwable);
+ });
+ }
+ }
+ }
+ });
+ threadWatcher.setDaemon(true);
+ threadWatcher.start();
+ }
+
+ @AfterEach
+ void teardown() throws InterruptedException {
+ try {
+ Thread.sleep(1000); // wait a bit for any uncaught exceptions to be reported
+
+ isTestRunning.set(false);
+ threadWatcher.join(1000);
+ } finally {
+ assertThat(uncaughtExceptions).isEmpty();
+ }
+ }
+
@Test
void noBreakingChanges() {
- var testProvider = new FlagdProvider(
- FlagdOptions.builder()
- .resolverType(Config.Resolver.FILE)
- .offlineFlagSourcePath(NoBreakingChangesTest.class.getResource("/testFlags.json").getPath().replaceFirst("/", ""))
- .build()
- );
+ var testProvider = new FlagdProvider(FlagdOptions.builder()
+ .resolverType(Config.Resolver.FILE)
+ .offlineFlagSourcePath(NoBreakingChangesTest.class
+ .getResource("/testFlags.json")
+ .getPath()
+ .replaceFirst("/", ""))
+ .build());
var api = new OpenFeatureAPI();
api.setProviderAndWait(testProvider);
var client = api.getClient();
- var flagFound =client.getBooleanDetails("basic-boolean", false);
+ var flagFound = client.getBooleanDetails("basic-boolean", false);
assertThat(flagFound).isNotNull();
assertThat(flagFound.getValue()).isTrue();
assertThat(flagFound.getVariant()).isEqualTo("true");
assertThat(flagFound.getReason()).isEqualTo(Reason.STATIC.toString());
- var flagNotFound =client.getStringDetails("unknown", "asd");
+ var flagNotFound = client.getStringDetails("unknown", "asd");
assertThat(flagNotFound).isNotNull();
assertThat(flagNotFound.getValue()).isEqualTo("asd");
assertThat(flagNotFound.getVariant()).isNull();
diff --git a/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java b/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java
index 0a23f9cb3..f089e4ab3 100644
--- a/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java
+++ b/src/test/java/dev/openfeature/sdk/e2e/steps/ProviderSteps.java
@@ -112,11 +112,11 @@ private void setupMockProvider(ErrorCode errorCode, String errorMessage, Provide
switch (providerState) {
case FATAL:
case ERROR:
- // mockProvider.emitProviderReady(details).await();
+ // mockProvider.emitProviderReady(details).await();
mockProvider.emitProviderError(details).await();
break;
case STALE:
- // mockProvider.emitProviderReady(details).await();
+ // mockProvider.emitProviderReady(details).await();
mockProvider.emitProviderStale(details).await();
break;
default:
diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java b/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
index 84d6ddbbb..350b6d226 100644
--- a/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
+++ b/src/test/java/dev/openfeature/sdk/testutils/TestStackedEmitCallsProvider.java
@@ -5,7 +5,6 @@
import dev.openfeature.sdk.Metadata;
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.ProviderEvent;
-import dev.openfeature.sdk.ProviderEventDetails;
import dev.openfeature.sdk.Value;
import java.util.function.Consumer;
@@ -38,7 +37,7 @@ private void onProviderEvent(ProviderEvent providerEvent) {
* This line deadlocked in the original implementation without the emitterExecutor see
* https://github.com/open-feature/java-sdk/issues/1299
*/
- //emitProviderReady(ProviderEventDetails.builder().build());
+ // emitProviderReady(ProviderEventDetails.builder().build());
}
}
}
From a1dc8c22de952895b748a05a22e2992662396e4c Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:20:37 +0100
Subject: [PATCH 04/11] add github actions
Signed-off-by: christian.lutnik
---
.github/workflows/pullrequest.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 1c8d8a7bc..97cd441a4 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -45,7 +45,10 @@ jobs:
${{ runner.os }}${{ matrix.build.java }}-maven-
- name: Verify with Maven
- run: mvn --batch-mode --update-snapshots --activate-profiles e2e,${{ matrix.build.profile }} verify
+ run: mvn --batch-mode --update-snapshots --activate-profiles e2e,${{ matrix.build.profile }} install
+
+ - name: Verify no breaking changes
+ run: mvn verify
- if: matrix.build.java == '17'
name: Upload coverage to Codecov
From bdc507b81620d2cf8c6d063db98ad00220edefdf Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:26:20 +0100
Subject: [PATCH 05/11] add github actions
Signed-off-by: christian.lutnik
---
.github/workflows/pullrequest.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 97cd441a4..25b12710c 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -13,8 +13,8 @@ jobs:
build:
- java: 17
profile: codequality
- - java: 11
- profile: java11
+ #- java: 11
+ # profile: java11
name: with Java ${{ matrix.build.java }}
runs-on: ${{ matrix.os}}
steps:
From 555f3c869dcee9e2cea0d092051a23f1f9e52cf9 Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:32:11 +0100
Subject: [PATCH 06/11] add github actions
Signed-off-by: christian.lutnik
---
src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index e87de10a2..35a402464 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -63,7 +63,7 @@ void noBreakingChanges() {
.offlineFlagSourcePath(NoBreakingChangesTest.class
.getResource("/testFlags.json")
.getPath()
- .replaceFirst("/", ""))
+ )
.build());
var api = new OpenFeatureAPI();
api.setProviderAndWait(testProvider);
From 362df55c344e00d4f6d095755f44a0c5f9820c4f Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:43:37 +0100
Subject: [PATCH 07/11] add github actions
Signed-off-by: christian.lutnik
---
.../sdk/NoBreakingChangesTest.java | 38 ++++++++++++++++---
1 file changed, 33 insertions(+), 5 deletions(-)
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index 35a402464..287c19ad1 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -7,6 +7,10 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.util.HashSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,13 +61,37 @@ void teardown() throws InterruptedException {
}
@Test
- void noBreakingChanges() {
+ void noBreakingChanges() throws IOException {
+
+ var file = new File("testFlags.json");
+ file.mkdirs();
+ file.createNewFile();
+ file.deleteOnExit();
+
+ System.err.println("Using flag file at: " + file.getAbsolutePath());
+
+ var writer = new BufferedWriter(new FileWriter(file));
+ writer.write("{\n" +
+ " \"$schema\": \"https://flagd.dev/schema/v0/flags.json\",\n" +
+ " \"flags\": {\n" +
+ " \"basic-boolean\": {\n" +
+ " \"state\": \"ENABLED\",\n" +
+ " \"defaultVariant\": \"true\",\n" +
+ " \"variants\": {\n" +
+ " \"true\": true,\n" +
+ " \"false\": false\n" +
+ " },\n" +
+ " \"targeting\": {}\n" +
+ " }\n" +
+ " }\n" +
+ "}\n");
+ writer.flush();
+
+ System.err.println("written to file");
+
var testProvider = new FlagdProvider(FlagdOptions.builder()
.resolverType(Config.Resolver.FILE)
- .offlineFlagSourcePath(NoBreakingChangesTest.class
- .getResource("/testFlags.json")
- .getPath()
- )
+ .offlineFlagSourcePath(file.getAbsolutePath())
.build());
var api = new OpenFeatureAPI();
api.setProviderAndWait(testProvider);
From e857b472d62d5047bfee86b3246597586f26caec Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:48:12 +0100
Subject: [PATCH 08/11] add github actions
Signed-off-by: christian.lutnik
---
src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index 287c19ad1..46af14073 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -64,7 +64,6 @@ void teardown() throws InterruptedException {
void noBreakingChanges() throws IOException {
var file = new File("testFlags.json");
- file.mkdirs();
file.createNewFile();
file.deleteOnExit();
From 6c7dbbe02290931b3133922162d37b3cae1a37e7 Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 10:56:54 +0100
Subject: [PATCH 09/11] refactor test
Signed-off-by: christian.lutnik
---
.../sdk/NoBreakingChangesTest.java | 108 +++++++-----------
1 file changed, 39 insertions(+), 69 deletions(-)
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index 46af14073..88bb72f87 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -1,21 +1,15 @@
package dev.openfeature.sdk;
+import static org.assertj.core.api.Assertions.assertThat;
+
import dev.openfeature.contrib.providers.flagd.Config;
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
import java.util.HashSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
class NoBreakingChangesTest {
@@ -48,68 +42,44 @@ void setup() {
threadWatcher.start();
}
- @AfterEach
- void teardown() throws InterruptedException {
+ @Test
+ void noBreakingChanges() throws InterruptedException {
try {
- Thread.sleep(1000); // wait a bit for any uncaught exceptions to be reported
+ var testProvider = new FlagdProvider(FlagdOptions.builder()
+ .resolverType(Config.Resolver.FILE)
+ .offlineFlagSourcePath(NoBreakingChangesTest.class
+ .getResource("/testFlags.json")
+ .getPath())
+ .build());
+ var api = new OpenFeatureAPI();
+ api.setProviderAndWait(testProvider);
+
+ var client = api.getClient();
+ var flagFound = client.getBooleanDetails("basic-boolean", false);
+ assertThat(flagFound).isNotNull();
+ assertThat(flagFound.getValue()).isTrue();
+ assertThat(flagFound.getVariant()).isEqualTo("true");
+ assertThat(flagFound.getReason()).isEqualTo(Reason.STATIC.toString());
+
+ var flagNotFound = client.getStringDetails("unknown", "asd");
+ assertThat(flagNotFound).isNotNull();
+ assertThat(flagNotFound.getValue()).isEqualTo("asd");
+ assertThat(flagNotFound.getVariant()).isNull();
+ assertThat(flagNotFound.getReason()).isEqualTo(Reason.ERROR.toString());
+ assertThat(flagNotFound.getErrorCode()).isEqualTo(ErrorCode.FLAG_NOT_FOUND);
+
+ testProvider.shutdown();
+ api.shutdown();
- isTestRunning.set(false);
- threadWatcher.join(1000);
} finally {
- assertThat(uncaughtExceptions).isEmpty();
- }
- }
+ try {
+ Thread.sleep(1000); // wait a bit for any uncaught exceptions to be reported
- @Test
- void noBreakingChanges() throws IOException {
-
- var file = new File("testFlags.json");
- file.createNewFile();
- file.deleteOnExit();
-
- System.err.println("Using flag file at: " + file.getAbsolutePath());
-
- var writer = new BufferedWriter(new FileWriter(file));
- writer.write("{\n" +
- " \"$schema\": \"https://flagd.dev/schema/v0/flags.json\",\n" +
- " \"flags\": {\n" +
- " \"basic-boolean\": {\n" +
- " \"state\": \"ENABLED\",\n" +
- " \"defaultVariant\": \"true\",\n" +
- " \"variants\": {\n" +
- " \"true\": true,\n" +
- " \"false\": false\n" +
- " },\n" +
- " \"targeting\": {}\n" +
- " }\n" +
- " }\n" +
- "}\n");
- writer.flush();
-
- System.err.println("written to file");
-
- var testProvider = new FlagdProvider(FlagdOptions.builder()
- .resolverType(Config.Resolver.FILE)
- .offlineFlagSourcePath(file.getAbsolutePath())
- .build());
- var api = new OpenFeatureAPI();
- api.setProviderAndWait(testProvider);
-
- var client = api.getClient();
- var flagFound = client.getBooleanDetails("basic-boolean", false);
- assertThat(flagFound).isNotNull();
- assertThat(flagFound.getValue()).isTrue();
- assertThat(flagFound.getVariant()).isEqualTo("true");
- assertThat(flagFound.getReason()).isEqualTo(Reason.STATIC.toString());
-
- var flagNotFound = client.getStringDetails("unknown", "asd");
- assertThat(flagNotFound).isNotNull();
- assertThat(flagNotFound.getValue()).isEqualTo("asd");
- assertThat(flagNotFound.getVariant()).isNull();
- assertThat(flagNotFound.getReason()).isEqualTo(Reason.ERROR.toString());
- assertThat(flagNotFound.getErrorCode()).isEqualTo(ErrorCode.FLAG_NOT_FOUND);
-
- testProvider.shutdown();
- api.shutdown();
+ isTestRunning.set(false);
+ threadWatcher.join(1000);
+ } finally {
+ assertThat(uncaughtExceptions).isEmpty();
+ }
+ }
}
}
From b9d070defca7376264dcd4cb0cece9533a38b890 Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 11:01:27 +0100
Subject: [PATCH 10/11] revert github action
Signed-off-by: christian.lutnik
---
.github/workflows/pullrequest.yml | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 25b12710c..1c8d8a7bc 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -13,8 +13,8 @@ jobs:
build:
- java: 17
profile: codequality
- #- java: 11
- # profile: java11
+ - java: 11
+ profile: java11
name: with Java ${{ matrix.build.java }}
runs-on: ${{ matrix.os}}
steps:
@@ -45,10 +45,7 @@ jobs:
${{ runner.os }}${{ matrix.build.java }}-maven-
- name: Verify with Maven
- run: mvn --batch-mode --update-snapshots --activate-profiles e2e,${{ matrix.build.profile }} install
-
- - name: Verify no breaking changes
- run: mvn verify
+ run: mvn --batch-mode --update-snapshots --activate-profiles e2e,${{ matrix.build.profile }} verify
- if: matrix.build.java == '17'
name: Upload coverage to Codecov
From 1db8a90d647343bc5aaf1fd4ae8d5c7a77fca620 Mon Sep 17 00:00:00 2001
From: "christian.lutnik"
Date: Thu, 27 Nov 2025 11:08:24 +0100
Subject: [PATCH 11/11] refactor test
Signed-off-by: christian.lutnik
---
.../sdk/NoBreakingChangesTest.java | 21 +++++++++++--------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
index 88bb72f87..bc921747d 100644
--- a/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
+++ b/src/test/java/dev/openfeature/sdk/NoBreakingChangesTest.java
@@ -28,8 +28,7 @@ void setup() {
var seenThreads = new HashSet();
while (isRunning.get()) {
var stacks = Thread.getAllStackTraces();
- for (var entry : stacks.entrySet()) {
- var thread = entry.getKey();
+ for (var thread : stacks.keySet()) {
if (seenThreads.add(thread)) {
thread.setUncaughtExceptionHandler((thread1, throwable) -> {
uncaught.add(throwable);
@@ -72,14 +71,18 @@ void noBreakingChanges() throws InterruptedException {
api.shutdown();
} finally {
- try {
- Thread.sleep(1000); // wait a bit for any uncaught exceptions to be reported
+ assertNoUncaughtExceptions();
+ }
+ }
- isTestRunning.set(false);
- threadWatcher.join(1000);
- } finally {
- assertThat(uncaughtExceptions).isEmpty();
- }
+ private void assertNoUncaughtExceptions() throws InterruptedException {
+ try {
+ Thread.sleep(1000); // wait a bit for any uncaught exceptions to be reported
+
+ isTestRunning.set(false);
+ threadWatcher.join(1000);
+ } finally {
+ assertThat(uncaughtExceptions).isEmpty();
}
}
}