diff --git a/products/feature-flagging/feature-flagging-api/build.gradle.kts b/products/feature-flagging/feature-flagging-api/build.gradle.kts index af7012b8cc1..b20efb17bd7 100644 --- a/products/feature-flagging/feature-flagging-api/build.gradle.kts +++ b/products/feature-flagging/feature-flagging-api/build.gradle.kts @@ -52,7 +52,6 @@ dependencies { testImplementation(libs.bundles.junit5) testImplementation(libs.bundles.mockito) testImplementation(libs.moshi) - testImplementation("org.awaitility:awaitility:4.3.0") // The main source set gets the bootstrap/config types as compileOnly, so the JMH source set // needs them on its own compile and runtime classpath to drive the hook end to end. diff --git a/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/ProviderTest.java b/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/ProviderTest.java index 9623a4a479a..d7adf645e74 100644 --- a/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/ProviderTest.java +++ b/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/ProviderTest.java @@ -1,16 +1,16 @@ package datadog.trace.api.openfeature; import static datadog.trace.api.openfeature.Provider.METADATA; -import static java.time.Duration.ofSeconds; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -39,6 +39,7 @@ import dev.openfeature.sdk.exceptions.ProviderNotReadyError; import java.lang.reflect.Field; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -47,18 +48,13 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) public class ProviderTest { - @Captor private ArgumentCaptor eventDetailsCaptor; + private static final long EVENT_TIMEOUT_SECONDS = 10; private ExecutorService executor; @@ -77,15 +73,18 @@ public void tearDown() { } @Test - public void testSetProvider() { + public void testSetProvider() throws Exception { final OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + final CompletableFuture readyEvent = new CompletableFuture<>(); + api.onProviderReady(readyEvent::complete); api.setProvider(new Provider()); final Client client = api.getClient(); assertThat(client.getProviderState(), equalTo(ProviderState.NOT_READY)); FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class)); - await().atMost(ofSeconds(1)).until(() -> client.getProviderState() == ProviderState.READY); + readyEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS); + assertThat(client.getProviderState(), equalTo(ProviderState.READY)); } @Test @@ -97,35 +96,31 @@ public void testSetProviderAndWait() throws Exception { assertThat(client.getProviderState(), equalTo(ProviderState.NOT_READY)); FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class)); - await().atMost(ofSeconds(1)).until(() -> client.getProviderState() == ProviderState.READY); - provider.get(1, SECONDS); + provider.get(EVENT_TIMEOUT_SECONDS, SECONDS); + assertThat(client.getProviderState(), equalTo(ProviderState.READY)); } @Test - public void testSetProviderAndWaitTimeoutRecoversWhenConfigurationArrives() { - final Consumer readyEvent = mock(Consumer.class); + public void testSetProviderAndWaitTimeoutRecoversWhenConfigurationArrives() throws Exception { + final CompletableFuture readyEvent = new CompletableFuture<>(); + final Consumer readyEventHandler = completingHandler(readyEvent); final OpenFeatureAPI api = OpenFeatureAPI.getInstance(); final Client client = api.getClient(); - client.on(ProviderEvent.PROVIDER_READY, readyEvent); + client.on(ProviderEvent.PROVIDER_READY, readyEventHandler); assertThrows( ProviderNotReadyError.class, () -> api.setProviderAndWait(new Provider(new Options().initTimeout(10, MILLISECONDS)))); assertThat(client.getProviderState(), equalTo(ProviderState.ERROR)); - verify(readyEvent, times(0)).accept(any()); + assertFalse(readyEvent.isDone()); FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class)); - await() - .atMost(ofSeconds(1)) - .untilAsserted( - () -> { - assertThat(client.getProviderState(), equalTo(ProviderState.READY)); - verify(readyEvent, times(1)).accept(eventDetailsCaptor.capture()); - final EventDetails eventDetails = eventDetailsCaptor.getValue(); - assertThat(eventDetails.getProviderName(), equalTo(METADATA)); - }); + final EventDetails eventDetails = readyEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS); + assertThat(client.getProviderState(), equalTo(ProviderState.READY)); + assertThat(eventDetails.getProviderName(), equalTo(METADATA)); + verify(readyEventHandler, times(1)).accept(any()); } @Test @@ -266,49 +261,41 @@ public ProviderEvaluation evaluate( } @Test - public void testNullConfigurationAfterReadyTransitionsToErrorAndRecovers() { + public void testNullConfigurationAfterReadyTransitionsToErrorAndRecovers() throws Exception { final OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - api.setProvider(new Provider()); - final Client client = api.getClient(); - FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class)); - await().atMost(ofSeconds(1)).until(() -> client.getProviderState() == ProviderState.READY); + api.setProviderAndWait(new Provider()); + final Client client = api.getClient(); + assertThat(client.getProviderState(), equalTo(ProviderState.READY)); - final Consumer errorEvent = mock(Consumer.class); - final Consumer readyEvent = mock(Consumer.class); - final Consumer configChangedEvent = mock(Consumer.class); - client.on(ProviderEvent.PROVIDER_ERROR, errorEvent); - client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, configChangedEvent); + final CompletableFuture errorEvent = new CompletableFuture<>(); + final CompletableFuture readyEvent = new CompletableFuture<>(); + final CompletableFuture configChangedEvent = new CompletableFuture<>(); + final Consumer errorEventHandler = completingHandler(errorEvent); + final Consumer readyEventHandler = completingHandler(readyEvent); + final Consumer configChangedEventHandler = completingHandler(configChangedEvent); + client.on(ProviderEvent.PROVIDER_ERROR, errorEventHandler); + client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, configChangedEventHandler); FeatureFlaggingGateway.dispatch((ServerConfiguration) null); - await() - .atMost(ofSeconds(1)) - .untilAsserted( - () -> { - assertThat(client.getProviderState(), equalTo(ProviderState.ERROR)); - verify(errorEvent, times(1)).accept(eventDetailsCaptor.capture()); - final EventDetails eventDetails = eventDetailsCaptor.getValue(); - assertThat(eventDetails.getProviderName(), equalTo(METADATA)); - }); + final EventDetails eventDetails = errorEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS); + assertThat(client.getProviderState(), equalTo(ProviderState.ERROR)); + assertThat(eventDetails.getProviderName(), equalTo(METADATA)); final FlagEvaluationDetails evalDetails = client.getStringDetails("missing", "default"); assertThat(evalDetails.getValue(), equalTo("default")); assertThat(evalDetails.getErrorCode(), equalTo(ErrorCode.PROVIDER_NOT_READY)); - client.on(ProviderEvent.PROVIDER_READY, readyEvent); + client.on(ProviderEvent.PROVIDER_READY, readyEventHandler); FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class)); - await() - .atMost(ofSeconds(1)) - .untilAsserted( - () -> { - assertThat(client.getProviderState(), equalTo(ProviderState.READY)); - verify(readyEvent, times(1)).accept(any()); - }); + readyEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS); + assertThat(client.getProviderState(), equalTo(ProviderState.READY)); FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class)); - await() - .atMost(ofSeconds(1)) - .untilAsserted(() -> verify(configChangedEvent, times(1)).accept(any())); + configChangedEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS); + verify(errorEventHandler, times(1)).accept(any()); + verify(readyEventHandler, times(1)).accept(any()); + verify(configChangedEventHandler, times(1)).accept(any()); } @Test @@ -479,6 +466,20 @@ private static String initializationState(final Provider provider) throws Except return state.get().toString(); } + @SuppressWarnings("unchecked") + private static Consumer completingHandler( + final CompletableFuture event) { + final Consumer handler = mock(Consumer.class); + doAnswer( + invocation -> { + event.complete(invocation.getArgument(0)); + return null; + }) + .when(handler) + .accept(any()); + return handler; + } + private static FlagEvaluationWriter capturingWriter(final AtomicReference ref) { return new FlagEvaluationWriter() { @Override