diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 153fd0a5..de88ba99 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -20,9 +20,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@96f53100ba2a5449eb71d2e6604bbcd94b9449b5 + - uses: actions/checkout@8b5e8b768746b50394015010d25e690bfab9dfbc - name: Set up JDK 8 - uses: actions/setup-java@5b86b67f5bb794ee4de9464f70b700b9445b03a8 + uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 with: java-version: '8' distribution: 'temurin' @@ -49,7 +49,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Upload coverage to Codecov - uses: codecov/codecov-action@e1dd05cde2ed37d100f658b34ea423728ba1812e + uses: codecov/codecov-action@04adcebd9b38cae0bb0fd4a05e3497ce0bce41a0 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos flags: unittests # optional diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index dd5d05b2..3173d904 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -10,17 +10,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the code - uses: actions/checkout@96f53100ba2a5449eb71d2e6604bbcd94b9449b5 + uses: actions/checkout@8b5e8b768746b50394015010d25e690bfab9dfbc - name: Set up JDK 8 - uses: actions/setup-java@5b86b67f5bb794ee4de9464f70b700b9445b03a8 + uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 with: java-version: '8' distribution: 'temurin' cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@e683046da183a09174d531cc43713853e27debb3 + uses: github/codeql-action/init@07d42ec34e55d7e17f411bffdf54e254effd68ae with: languages: java @@ -36,7 +36,7 @@ jobs: run: mvn --batch-mode --update-snapshots --activate-profiles e2e verify - name: Upload coverage to Codecov - uses: codecov/codecov-action@e1dd05cde2ed37d100f658b34ea423728ba1812e + uses: codecov/codecov-action@04adcebd9b38cae0bb0fd4a05e3497ce0bce41a0 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos flags: unittests # optional @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e683046da183a09174d531cc43713853e27debb3 + uses: github/codeql-action/analyze@07d42ec34e55d7e17f411bffdf54e254effd68ae diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a77578c..d433b047 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: # Release-please creates a PR that tracks all changes steps: - - uses: google-github-actions/release-please-action@ca6063f4ed81b55db15b8c42d1b6f7925866342d + - uses: google-github-actions/release-please-action@01b3219546e6e7c4cfdaece8cd06efa464f49e2a id: release with: command: manifest @@ -29,10 +29,10 @@ jobs: # These steps are only run if this was a merged release-please PR - name: checkout if: ${{ steps.release.outputs.releases_created }} - uses: actions/checkout@96f53100ba2a5449eb71d2e6604bbcd94b9449b5 + uses: actions/checkout@8b5e8b768746b50394015010d25e690bfab9dfbc - name: Set up JDK 8 if: ${{ steps.release.outputs.releases_created }} - uses: actions/setup-java@5b86b67f5bb794ee4de9464f70b700b9445b03a8 + uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 with: java-version: '8' distribution: 'temurin' diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index e4a66ac5..f5558e99 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -29,16 +29,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@96f53100ba2a5449eb71d2e6604bbcd94b9449b5 + uses: actions/checkout@8b5e8b768746b50394015010d25e690bfab9dfbc # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e683046da183a09174d531cc43713853e27debb3 + uses: github/codeql-action/init@07d42ec34e55d7e17f411bffdf54e254effd68ae with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@e683046da183a09174d531cc43713853e27debb3 + uses: github/codeql-action/autobuild@07d42ec34e55d7e17f411bffdf54e254effd68ae - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e683046da183a09174d531cc43713853e27debb3 + uses: github/codeql-action/analyze@07d42ec34e55d7e17f411bffdf54e254effd68ae diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 287b4c71..4a8b7113 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"1.5.0"} \ No newline at end of file +{".":"1.6.0"} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 51917159..bb5e01d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## [1.6.0](https://github.com/open-feature/java-sdk/compare/v1.5.0...v1.6.0) (2023-09-03) + + +### ๐Ÿ› Bug Fixes + +* **deps:** update dependency org.slf4j:slf4j-api to v2.0.9 ([#586](https://github.com/open-feature/java-sdk/issues/586)) ([84f72ac](https://github.com/open-feature/java-sdk/commit/84f72ac70d3b64975ee22ec73cf863db8c8a06af)) + + +### โœจ New Features + +* add method to set provider and block during init ([#563](https://github.com/open-feature/java-sdk/issues/563)) ([506e89f](https://github.com/open-feature/java-sdk/commit/506e89fd348f107df0065c5e0b218abdded0efec)) + + +### ๐Ÿงน Chore + +* **deps:** update actions/checkout digest to 7739b9b ([#564](https://github.com/open-feature/java-sdk/issues/564)) ([adfb587](https://github.com/open-feature/java-sdk/commit/adfb58764a2073e6ac381d0c60199f27ba01d398)) +* **deps:** update actions/checkout digest to 8b5e8b7 ([#585](https://github.com/open-feature/java-sdk/issues/585)) ([9ae4407](https://github.com/open-feature/java-sdk/commit/9ae440786f6f56f73e85f163d71b75641950fc30)) +* **deps:** update actions/checkout digest to 97a652b ([#580](https://github.com/open-feature/java-sdk/issues/580)) ([816950a](https://github.com/open-feature/java-sdk/commit/816950a89589b6cbc2a702305681082075e90b1b)) +* **deps:** update actions/checkout digest to f43a0e5 ([#572](https://github.com/open-feature/java-sdk/issues/572)) ([46bbdcc](https://github.com/open-feature/java-sdk/commit/46bbdccf0f05a08eb31011cb26818ef2179f4c62)) +* **deps:** update actions/setup-java digest to 4075bfc ([#583](https://github.com/open-feature/java-sdk/issues/583)) ([8a38d12](https://github.com/open-feature/java-sdk/commit/8a38d12360ff7c7190113bfed9c01d8841af2cfe)) +* **deps:** update codecov/codecov-action digest to 04adceb ([#581](https://github.com/open-feature/java-sdk/issues/581)) ([30942af](https://github.com/open-feature/java-sdk/commit/30942afe842c5a868eace9653428469ee79791cf)) +* **deps:** update codecov/codecov-action digest to 6991c70 ([#575](https://github.com/open-feature/java-sdk/issues/575)) ([300c505](https://github.com/open-feature/java-sdk/commit/300c5054b93e8c0540e67450b707ac93880d0524)) +* **deps:** update codecov/codecov-action digest to 8ccb892 ([#570](https://github.com/open-feature/java-sdk/issues/570)) ([5d4230f](https://github.com/open-feature/java-sdk/commit/5d4230f0fd61570dfec5777343d0278a66a3cf0a)) +* **deps:** update codecov/codecov-action digest to c17956f ([#568](https://github.com/open-feature/java-sdk/issues/568)) ([666f784](https://github.com/open-feature/java-sdk/commit/666f784c2ea1ad2a027b1e4f7c011b7ff3011599)) +* **deps:** update codecov/codecov-action digest to de1b515 ([#573](https://github.com/open-feature/java-sdk/issues/573)) ([49e49cc](https://github.com/open-feature/java-sdk/commit/49e49cc9e4d4eacbc826b9893519e23758147f44)) +* **deps:** update github/codeql-action digest to 07d42ec ([#584](https://github.com/open-feature/java-sdk/issues/584)) ([91f7552](https://github.com/open-feature/java-sdk/commit/91f75527459695ff44126e0fec87c8848255208e)) +* **deps:** update github/codeql-action digest to 1009124 ([#576](https://github.com/open-feature/java-sdk/issues/576)) ([7b1eb1c](https://github.com/open-feature/java-sdk/commit/7b1eb1c035f35421a6527a4382e4dc837eb4b9b6)) +* **deps:** update github/codeql-action digest to 14877a1 ([#567](https://github.com/open-feature/java-sdk/issues/567)) ([9e14633](https://github.com/open-feature/java-sdk/commit/9e146330dfe6e25b6697a31c533a3f902bd32adb)) +* **deps:** update github/codeql-action digest to 8ecc33d ([#579](https://github.com/open-feature/java-sdk/issues/579)) ([2a0da51](https://github.com/open-feature/java-sdk/commit/2a0da511a75bc06ba4fb0b6761ddc2d121c024ca)) +* **deps:** update github/codeql-action digest to 9a53fd0 ([#569](https://github.com/open-feature/java-sdk/issues/569)) ([e423726](https://github.com/open-feature/java-sdk/commit/e4237262548e1af14273ac35b3609b9b684465ac)) +* **deps:** update github/codeql-action digest to b88b550 ([#578](https://github.com/open-feature/java-sdk/issues/578)) ([3142c43](https://github.com/open-feature/java-sdk/commit/3142c43c0565c1d86e46dab7215c9bbefb4fb59e)) +* **deps:** update github/codeql-action digest to c5acfe3 ([#582](https://github.com/open-feature/java-sdk/issues/582)) ([a4113c6](https://github.com/open-feature/java-sdk/commit/a4113c6308c557055e967e66f41f0a0e6ba62d56)) +* **deps:** update github/codeql-action digest to e426271 ([#566](https://github.com/open-feature/java-sdk/issues/566)) ([04adc3e](https://github.com/open-feature/java-sdk/commit/04adc3eb4d03417233a109e5d653925fbc01319a)) +* **deps:** update github/codeql-action digest to ff9cb43 ([#574](https://github.com/open-feature/java-sdk/issues/574)) ([dffa593](https://github.com/open-feature/java-sdk/commit/dffa59344089847628e8e5b22defbaa5b69c6d5e)) +* **deps:** update google-github-actions/release-please-action digest to 01b3219 ([#571](https://github.com/open-feature/java-sdk/issues/571)) ([e260022](https://github.com/open-feature/java-sdk/commit/e260022cf5a320239228ad9d793c830a845bc51e)) + ## [1.5.0](https://github.com/open-feature/java-sdk/compare/v1.4.3...v1.5.0) (2023-08-16) diff --git a/README.md b/README.md index 34f5ee44..6f3afdfd 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ - - Release + + Release @@ -59,7 +59,7 @@ Note that this library is intended to be used in server-side contexts and has no dev.openfeature sdk - 1.5.0 + 1.6.0 ``` @@ -84,7 +84,7 @@ If you would like snapshot builds, this is the relevant repository information: ```groovy dependencies { - implementation 'dev.openfeature:sdk:1.5.0' + implementation 'dev.openfeature:sdk:1.6.0' } ``` diff --git a/pom.xml b/pom.xml index f0133467..56375a74 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ dev.openfeature sdk - 1.5.0 + 1.6.0 UTF-8 @@ -60,7 +60,7 @@ org.slf4j slf4j-api - 2.0.7 + 2.0.9 diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java index 94047b8c..47c09388 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java @@ -100,11 +100,12 @@ public EvaluationContext getEvaluationContext() { public void setProvider(FeatureProvider provider) { try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { providerRepository.setProvider( - provider, - (p) -> attachEventProvider(p), - (p) -> emitReady(p), - (p) -> detachEventProvider(p), - (p, message) -> emitError(p, message)); + provider, + this::attachEventProvider, + this::emitReady, + this::detachEventProvider, + this::emitError, + false); } } @@ -117,11 +118,45 @@ public void setProvider(FeatureProvider provider) { public void setProvider(String clientName, FeatureProvider provider) { try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { providerRepository.setProvider(clientName, - provider, - this::attachEventProvider, - this::emitReady, - this::detachEventProvider, - this::emitError); + provider, + this::attachEventProvider, + this::emitReady, + this::detachEventProvider, + this::emitError, + false); + } + } + + /** + * Set the default provider and wait for initialization to finish. + */ + public void setProviderAndWait(FeatureProvider provider) { + try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { + providerRepository.setProvider( + provider, + this::attachEventProvider, + this::emitReady, + this::detachEventProvider, + this::emitError, + true); + } + } + + /** + * Add a provider for a named client and wait for initialization to finish. + * + * @param clientName The name of the client. + * @param provider The provider to set. + */ + public void setProviderAndWait(String clientName, FeatureProvider provider) { + try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { + providerRepository.setProvider(clientName, + provider, + this::attachEventProvider, + this::emitReady, + this::detachEventProvider, + this::emitError, + true); } } diff --git a/src/main/java/dev/openfeature/sdk/ProviderRepository.java b/src/main/java/dev/openfeature/sdk/ProviderRepository.java index 0ff3b70b..b7d57049 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderRepository.java +++ b/src/main/java/dev/openfeature/sdk/ProviderRepository.java @@ -43,8 +43,8 @@ public FeatureProvider getProvider(String name) { public List getClientNamesForProvider(FeatureProvider provider) { return providers.entrySet().stream() - .filter(entry -> entry.getValue().equals(provider)) - .map(entry -> entry.getKey()).collect(Collectors.toList()); + .filter(entry -> entry.getValue().equals(provider)) + .map(entry -> entry.getKey()).collect(Collectors.toList()); } public Set getAllBoundClientNames() { @@ -62,58 +62,76 @@ public void setProvider(FeatureProvider provider, Consumer afterSet, Consumer afterInit, Consumer afterShutdown, - BiConsumer afterError) { + BiConsumer afterError, + boolean waitForInit) { if (provider == null) { throw new IllegalArgumentException("Provider cannot be null"); } - initializeProvider(null, provider, afterSet, afterInit, afterShutdown, afterError); + prepareAndInitializeProvider(null, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit); } /** * Add a provider for a named client. * - * @param clientName The name of the client. - * @param provider The provider to set. + * @param clientName The name of the client. + * @param provider The provider to set. + * @param waitForInit When true, wait for initialization to finish, then returns. + * Otherwise, initialization happens in the background. */ public void setProvider(String clientName, - FeatureProvider provider, - Consumer afterSet, - Consumer afterInit, - Consumer afterShutdown, - BiConsumer afterError) { + FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { if (provider == null) { throw new IllegalArgumentException("Provider cannot be null"); } if (clientName == null) { throw new IllegalArgumentException("clientName cannot be null"); } - initializeProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError); + prepareAndInitializeProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit); } - private void initializeProvider(@Nullable String clientName, - FeatureProvider newProvider, - Consumer afterSet, - Consumer afterInit, - Consumer afterShutdown, - BiConsumer afterError) { + private void prepareAndInitializeProvider(@Nullable String clientName, + FeatureProvider newProvider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { + // provider is set immediately, on this thread FeatureProvider oldProvider = clientName != null - ? this.providers.put(clientName, newProvider) - : this.defaultProvider.getAndSet(newProvider); + ? this.providers.put(clientName, newProvider) + : this.defaultProvider.getAndSet(newProvider); afterSet.accept(newProvider); - taskExecutor.submit(() -> { - // initialization happens in a different thread - try { - if (ProviderState.NOT_READY.equals(newProvider.getState())) { - newProvider.initialize(OpenFeatureAPI.getInstance().getEvaluationContext()); - afterInit.accept(newProvider); - } - shutDownOld(oldProvider, afterShutdown); - } catch (Exception e) { - log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e); - afterError.accept(newProvider, e.getMessage()); + if (waitForInit) { + initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider); + } else { + taskExecutor.submit(() -> { + // initialization happens in a different thread if we're not waiting it + initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider); + }); + } + } + + private void initializeProvider(FeatureProvider newProvider, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + FeatureProvider oldProvider) { + try { + if (ProviderState.NOT_READY.equals(newProvider.getState())) { + newProvider.initialize(OpenFeatureAPI.getInstance().getEvaluationContext()); + afterInit.accept(newProvider); } - }); + shutDownOld(oldProvider, afterShutdown); + } catch (Exception e) { + log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e); + afterError.accept(newProvider, e.getMessage()); + } } private void shutDownOld(FeatureProvider oldProvider,Consumer afterShutdown) { @@ -157,7 +175,7 @@ public void shutdown() { }, (FeatureProvider fp, String message) -> { - }); + }, false); this.providers.clear(); taskExecutor.shutdown(); } diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java index eb41fd95..35eb0769 100644 --- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java @@ -17,6 +17,10 @@ import java.util.List; import java.util.Map; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; +import dev.openfeature.sdk.testutils.TestEventsProvider; +import lombok.SneakyThrows; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,6 +73,34 @@ void getApiInstance() { assertThat(api.getProvider()).isEqualTo(mockProvider); } + @SneakyThrows + @Specification(number="1.1.8", text="The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw.") + @Test void providerAndWait() { + FeatureProvider provider = new TestEventsProvider(500); + OpenFeatureAPI.getInstance().setProviderAndWait(provider); + assertThat(api.getProvider().getState()).isEqualTo(ProviderState.READY); + + provider = new TestEventsProvider(500); + String providerName = "providerAndWait"; + OpenFeatureAPI.getInstance().setProviderAndWait(providerName, provider); + assertThat(api.getProvider(providerName).getState()).isEqualTo(ProviderState.READY); + } + + @Specification(number="2.4.5", text="The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready.") + @Test void shouldReturnNotReadyIfNotInitialized() { + FeatureProvider provider = new InMemoryProvider(new HashMap<>()) { + @Override + public void initialize(EvaluationContext evaluationContext) throws Exception { + Awaitility.await().wait(3000); + } + }; + String providerName = "shouldReturnNotReadyIfNotInitialized"; + OpenFeatureAPI.getInstance().setProvider(providerName, provider); + assertThat(api.getProvider(providerName).getState()).isEqualTo(ProviderState.NOT_READY); + Client client = OpenFeatureAPI.getInstance().getClient(providerName); + assertEquals(ErrorCode.PROVIDER_NOT_READY, client.getBooleanDetails("return_error_when_not_initialized", false).getErrorCode()); + } + @Specification(number="1.1.5", text="The API MUST provide a function for retrieving the metadata field of the configured provider.") @Test void provider_metadata() { FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider()); @@ -291,4 +323,5 @@ void getApiInstance() { @Specification(number="1.4.11", text="The client SHOULD provide asynchronous or non-blocking mechanisms for flag evaluation.") @Test void one_thread_per_request_model() {} + } diff --git a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java index 5b6dac1b..0d4ae5d6 100644 --- a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java +++ b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java @@ -55,7 +55,7 @@ class DefaultProvider { @DisplayName("should reject null as default provider") void shouldRejectNullAsDefaultProvider() { assertThatCode(() -> providerRepository.setProvider(null, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError())).isInstanceOf(IllegalArgumentException.class); + mockAfterShutdown(), mockAfterError(), false)).isInstanceOf(IllegalArgumentException.class); } @Test @@ -76,7 +76,7 @@ void shouldImmediatelyReturnWhenCallingTheProviderMutator() throws Exception { .atMost(Duration.ofSeconds(1)) .until(() -> { providerRepository.setProvider(featureProvider, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError()); + mockAfterShutdown(), mockAfterError(), false); verify(featureProvider, timeout(TIMEOUT)).initialize(any()); return true; }); @@ -101,7 +101,7 @@ class NamedProvider { @DisplayName("should reject null as named provider") void shouldRejectNullAsNamedProvider() { assertThatCode(() -> providerRepository.setProvider(CLIENT_NAME, null, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError())) + mockAfterShutdown(), mockAfterError(), false)) .isInstanceOf(IllegalArgumentException.class); } @@ -110,7 +110,7 @@ void shouldRejectNullAsNamedProvider() { void shouldRejectNullAsDefaultProvider() { NoOpProvider provider = new NoOpProvider(); assertThatCode(() -> providerRepository.setProvider(null, provider, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError())) + mockAfterShutdown(), mockAfterError(), false)) .isInstanceOf(IllegalArgumentException.class); } @@ -126,7 +126,7 @@ void shouldImmediatelyReturnWhenCallingTheNamedClientProviderMutator() throws Ex .atMost(Duration.ofSeconds(1)) .until(() -> { providerRepository.setProvider("named client", featureProvider, mockAfterSet(), - mockAfterInit(), mockAfterShutdown(), mockAfterError()); + mockAfterInit(), mockAfterShutdown(), mockAfterError(), false); verify(featureProvider, timeout(TIMEOUT)).initialize(any()); return true; }); @@ -161,7 +161,7 @@ void shouldImmediatelyReturnWhenCallingTheProviderMutator() throws Exception { .atMost(Duration.ofSeconds(1)) .until(() -> { providerRepository.setProvider(newProvider, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError()); + mockAfterShutdown(), mockAfterError(), false); verify(newProvider, timeout(TIMEOUT)).initialize(any()); return true; }); @@ -194,7 +194,7 @@ void shouldImmediatelyReturnWhenCallingTheProviderMutator() throws Exception { Future providerMutation = executorService .submit(() -> providerRepository.setProvider(CLIENT_NAME, newProvider, mockAfterSet(), - mockAfterInit(), mockAfterShutdown(), mockAfterError())); + mockAfterInit(), mockAfterShutdown(), mockAfterError(), false)); await() .alias("wait for provider mutator to return") @@ -311,7 +311,7 @@ void shouldShutdownAllFeatureProvidersOnShutdown() { private void setFeatureProvider(FeatureProvider provider) { providerRepository.setProvider(provider, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), - mockAfterError()); + mockAfterError(), false); waitForSettingProviderHasBeenCompleted(ProviderRepository::getProvider, provider); } @@ -320,13 +320,13 @@ private void setFeatureProvider(FeatureProvider provider, Consumer afterInit, Consumer afterShutdown, BiConsumer afterError) { providerRepository.setProvider(provider, afterSet, afterInit, afterShutdown, - afterError); + afterError, false); waitForSettingProviderHasBeenCompleted(ProviderRepository::getProvider, provider); } private void setFeatureProvider(String namedProvider, FeatureProvider provider) { providerRepository.setProvider(namedProvider, provider, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), - mockAfterError()); + mockAfterError(), false); waitForSettingProviderHasBeenCompleted(repository -> repository.getProvider(namedProvider), provider); } diff --git a/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java b/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java index 903f0bf8..459fcefe 100644 --- a/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java +++ b/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java @@ -56,11 +56,7 @@ public class StepDefinitions { public static void setup() { Map> flags = buildFlags(); InMemoryProvider provider = new InMemoryProvider(flags); - OpenFeatureAPI.getInstance().setProvider(provider); - - // TODO: setProvider with wait for init, pending https://github.com/open-feature/ofep/pull/80 - Thread.sleep(500); - + OpenFeatureAPI.getInstance().setProviderAndWait(provider); client = OpenFeatureAPI.getInstance().getClient(); } diff --git a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java index 4cfbb812..ffdc3182 100644 --- a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java +++ b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java @@ -6,12 +6,13 @@ import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; import dev.openfeature.sdk.exceptions.TypeMismatchError; import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.omg.CORBA.DynAnyPackage.TypeMismatch; +import java.util.HashMap; import java.util.Map; import static dev.openfeature.sdk.Structure.mapToStructure; @@ -36,11 +37,7 @@ static void beforeAll() { Map> flags = buildFlags(); provider = spy(new InMemoryProvider(flags)); OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {}); - OpenFeatureAPI.getInstance().setProvider(provider); - - // TODO: setProvider with wait for init, pending https://github.com/open-feature/ofep/pull/80 - Thread.sleep(500); - + OpenFeatureAPI.getInstance().setProviderAndWait(provider); client = OpenFeatureAPI.getInstance().getClient(); provider.updateFlags(flags); provider.updateFlag("addedFlag", Flag.builder() @@ -99,4 +96,13 @@ void typeMismatch() { provider.getBooleanEvaluation("string-flag", false, new ImmutableContext()); }); } + + @SneakyThrows + @Test + void shouldThrowIfNotInitialized() { + InMemoryProvider inMemoryProvider = new InMemoryProvider(new HashMap<>()); + + // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client + assertThrows(ProviderNotReadyError.class, ()-> inMemoryProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); + } } \ No newline at end of file diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java b/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java index 3fcb5888..25650bf6 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java +++ b/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java @@ -15,19 +15,19 @@ public class TestEventsProvider extends EventProvider { private String initErrorMessage; private ProviderState state = ProviderState.NOT_READY; private boolean shutDown = false; - private int initTimeout = 0; + private int initTimeoutMs = 0; @Override public ProviderState getState() { return this.state; } - public TestEventsProvider(int initTimeout) { - this.initTimeout = initTimeout; + public TestEventsProvider(int initTimeoutMs) { + this.initTimeoutMs = initTimeoutMs; } - public TestEventsProvider(int initTimeout, boolean initError, String initErrorMessage) { - this.initTimeout = initTimeout; + public TestEventsProvider(int initTimeoutMs, boolean initError, String initErrorMessage) { + this.initTimeoutMs = initTimeoutMs; this.initError = initError; this.initErrorMessage = initErrorMessage; } @@ -53,7 +53,7 @@ public void shutdown() { public void initialize(EvaluationContext evaluationContext) throws Exception { if (ProviderState.NOT_READY.equals(state)) { // wait half the TIMEOUT, otherwise some init/errors can be fired before we add handlers - Thread.sleep(initTimeout); + Thread.sleep(initTimeoutMs); if (this.initError) { this.state = ProviderState.ERROR; throw new Exception(initErrorMessage); diff --git a/version.txt b/version.txt index bc80560f..dc1e644a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.0 +1.6.0