From 3a0bc651935bb19ce857a0038bc676540fbfcf6e Mon Sep 17 00:00:00 2001 From: Samuel Vazquez Date: Mon, 17 Apr 2023 13:42:32 -0700 Subject: [PATCH 1/4] feat: completed all promises even if some fail --- .../extensions/CompletableFutureExtensions.kt | 5 +- .../execution/EntitiesDataFetcher.kt | 4 +- .../FederatedTypePromiseResolverExecutor.kt | 24 ++++- .../extensions/CompletableFutureExtensions.kt | 24 ++++- ...ederatedTypePromiseResolverExecutorTest.kt | 42 ++++++++- .../CompletableFutureExtensionsKtTest.kt | 91 +++++++++++++++++++ 6 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt index 1bf7c83938..326ae8eed9 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt @@ -27,8 +27,9 @@ import org.dataloader.DataLoader import java.util.concurrent.CompletableFuture /** - * Check if all futures collected on [KotlinDataLoaderRegistry.dispatchAll] were handled and we have more futures than we - * had when we started to dispatch, if so, means that [DataLoader]s were chained + * Check if all futures collected on [KotlinDataLoaderRegistry.dispatchAll] were handled + * and if we have more futures than we had when we started to dispatch, if so, + * means that [DataLoader]s were chained so we need to dispatch the dataLoaderRegistry. */ fun CompletableFuture.dispatchIfNeeded( environment: DataFetchingEnvironment diff --git a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/EntitiesDataFetcher.kt b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/EntitiesDataFetcher.kt index 5c5138abfa..fbd345d11d 100644 --- a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/EntitiesDataFetcher.kt +++ b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/EntitiesDataFetcher.kt @@ -20,7 +20,7 @@ import com.expediagroup.graphql.generator.federation.exception.InvalidFederatedR import com.expediagroup.graphql.generator.federation.execution.resolverexecutor.FederatedTypePromiseResolverExecutor import com.expediagroup.graphql.generator.federation.execution.resolverexecutor.FederatedTypeSuspendResolverExecutor import com.expediagroup.graphql.generator.federation.execution.resolverexecutor.ResolvableEntity -import com.expediagroup.graphql.generator.federation.extensions.collectAll +import com.expediagroup.graphql.generator.federation.extensions.joinAll import com.expediagroup.graphql.generator.federation.extensions.toDataFetcherResult import graphql.execution.DataFetcherResult import graphql.schema.DataFetcher @@ -91,7 +91,7 @@ open class EntitiesDataFetcher( ) return promises - .collectAll() + .joinAll() .thenApply { results -> results.asSequence() .flatten() diff --git a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt index aa3e0ffc91..bbd4a612da 100644 --- a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt +++ b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2023 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,8 @@ package com.expediagroup.graphql.generator.federation.execution.resolverexecutor import com.expediagroup.graphql.generator.federation.exception.FederatedRequestFailure import com.expediagroup.graphql.generator.federation.execution.FederatedTypePromiseResolver -import com.expediagroup.graphql.generator.federation.extensions.collectAll +import com.expediagroup.graphql.generator.federation.extensions.allSettled +import com.expediagroup.graphql.generator.federation.extensions.joinAll import graphql.schema.DataFetchingEnvironment import java.util.concurrent.CompletableFuture @@ -29,7 +30,7 @@ object FederatedTypePromiseResolverExecutor : TypeResolverExecutor>> = resolvableEntities.map { resolvableEntity -> resolveEntity(resolvableEntity, environment) - }.collectAll() + }.joinAll() @Suppress("TooGenericExceptionCaught") private fun resolveEntity( @@ -39,6 +40,7 @@ object FederatedTypePromiseResolverExecutor : TypeResolverExecutor>::index) val representations = resolvableEntity.indexedRepresentations.map(IndexedValue>::value) val resultsPromise = representations.map { representation -> + // TODO decide if keeping this, nothing stops users to synchronously throw an exception try { resolvableEntity.resolver.resolve(environment, representation) } catch (e: Exception) { @@ -49,9 +51,21 @@ object FederatedTypePromiseResolverExecutor : TypeResolverExecutor - indexes.zip(results).toMap() + indexes.zip( + results.mapIndexed { index, result -> + try { + result.getOrThrow() + } catch (e: Exception) { + FederatedRequestFailure( + "Exception was thrown while trying to resolve federated type, representation=${representations[index]}", + e + ) + } + } + ).toMap() } } } diff --git a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt index 682b77e602..1db1a614a2 100644 --- a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt +++ b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt @@ -20,10 +20,32 @@ import java.util.concurrent.CompletableFuture /** * Returns a new CompletableFuture of a list with the resolved values of the given CompletableFutures. * the returned completableFuture will complete when all given CompletableFutures complete. + * If any of the given CompletableFutures complete exceptionally, then the returned CompletableFuture also does so, + * with a CompletionException holding this exception as its cause. */ -internal fun List>.collectAll(): CompletableFuture> = +internal fun List>.joinAll(): CompletableFuture> = CompletableFuture.allOf( *toTypedArray() ).thenApply { map(CompletableFuture::join) } + +@Suppress("TooGenericExceptionCaught") +internal fun List>.allSettled(): CompletableFuture>> { + val resultFutures = map { future -> + CompletableFuture.supplyAsync { + try { + Result.success(future.get()) + } catch (e: Exception) { + Result.failure(e) + } + } + } + + return CompletableFuture.allOf( + *resultFutures.toTypedArray() + ).thenApply { + resultFutures.map(CompletableFuture>::join) + } +} + diff --git a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutorTest.kt b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutorTest.kt index 6b3767cafc..53883147dc 100644 --- a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutorTest.kt +++ b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2023 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import io.mockk.verify import org.junit.jupiter.api.Test import reactor.kotlin.core.publisher.toMono import java.time.Duration +import java.util.concurrent.CompletableFuture import kotlin.test.assertEquals import kotlin.test.assertIs @@ -82,7 +83,7 @@ class FederatedTypePromiseResolverExecutorTest { } @Test - fun `resolver maps the value to a failure when the federated resolver throws an exception`() { + fun `resolver maps the value to a failure when the federated resolver throws an exception synchronously`() { val representation1 = mapOf("__typename" to "MyType", "id" to 1) val representation2 = mapOf("__typename" to "MyType", "id" to 2) @@ -115,4 +116,41 @@ class FederatedTypePromiseResolverExecutorTest { mockResolver.resolve(any(), representation2) } } + + @Test + fun `resolver maps the value to a failure when the federated resolver throws an exception asynchronously`() { + val representation1 = mapOf("__typename" to "MyType", "id" to 1) + val representation2 = mapOf("__typename" to "MyType", "id" to 2) + + val indexedRequests: List>> = listOf( + IndexedValue(1, representation1), + IndexedValue(2, representation2) + ) + + val mockResolver = mockk> { + every { typeName } returns "MyType" + every { resolve(any(), representation1) } returns "MyType1".toMono().delayElement(Duration.ofMillis(100)).toFuture() + every { resolve(any(), representation2) } returns CompletableFuture.supplyAsync { + throw Exception("async exception") + } + } + + val resolvableEntity = ResolvableEntity("MyType", indexedRequests, mockResolver) + val environment = mockk { + every { graphQlContext } returns GraphQLContext.newContext().build() + } + + val result = FederatedTypePromiseResolverExecutor.execute(listOf(resolvableEntity), environment).get() + assertEquals(1, result.size) + + val resolverResults = result[0] + + assertEquals("MyType1", resolverResults[1]) + assertIs(resolverResults[2]) + + verify(exactly = 1) { + mockResolver.resolve(any(), representation1) + mockResolver.resolve(any(), representation2) + } + } } diff --git a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt new file mode 100644 index 0000000000..e4ae7308f3 --- /dev/null +++ b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2023 Expedia, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.graphql.generator.federation.extensions + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import reactor.kotlin.core.publisher.toMono +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionException +import java.util.concurrent.ExecutionException +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue + +class CompletableFutureExtensionsKtTest { + @Test + fun `joinAll asynchronously collects a list of completable futures of elements into a completable future list of elements`() { + val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() + val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val thirdPromise = CompletableFuture.completedFuture("third promise") + + + val result = listOf(firstPromise, secondPromise, thirdPromise).joinAll().join() + + assertEquals(3, result.size) + assertEquals("first promise", result[0]) + assertEquals("second promise", result[1]) + assertEquals("third promise", result[2]) + } + + @Test + fun `joinAll throws an exception with a completableFuture completes exceptionally`() { + val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() + val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val thirdPromise = CompletableFuture.supplyAsync { + throw Exception("async exception") + } + + assertThrows { + listOf(firstPromise, secondPromise, thirdPromise).joinAll().join() + } + } + + @Test + fun `allSettled asynchronously collects a list of completable futures of elements into a completable future list of elements`() { + val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() + val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val thirdPromise = CompletableFuture.completedFuture("third promise") + + + val result = listOf(firstPromise, secondPromise, thirdPromise).allSettled().join() + + assertEquals(3, result.size) + assertEquals("first promise", result[0].getOrNull()) + assertEquals("second promise", result[1].getOrNull()) + assertEquals("third promise", result[2].getOrNull()) + } + + @Test + fun `allSettled asynchronously collects a list of completable futures of elements even if a completable future completes exceptionally`() { + val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() + val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val thirdPromise = CompletableFuture.supplyAsync { + throw Exception("async exception") + } + + val result = listOf(firstPromise, secondPromise, thirdPromise).allSettled().join() + + assertEquals(3, result.size) + assertEquals("first promise", result[0].getOrNull()) + assertEquals("second promise", result[1].getOrNull()) + assertTrue(result[2].isFailure) + assertIs(result[2].exceptionOrNull()) + assertEquals("async exception", result[2].exceptionOrNull()?.cause?.message) + } +} From 25c29f1cf609a083c6359e6f7d4007dac43d2c23 Mon Sep 17 00:00:00 2001 From: Samuel Vazquez Date: Mon, 17 Apr 2023 15:14:49 -0700 Subject: [PATCH 2/4] feat: allSettle promises --- .../FederatedTypePromiseResolverExecutor.kt | 59 ++++++++++--------- .../extensions/CompletableFutureExtensions.kt | 19 ++++-- .../CompletableFutureExtensionsKtTest.kt | 2 - 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt index bbd4a612da..ea198545c5 100644 --- a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt +++ b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/resolverexecutor/FederatedTypePromiseResolverExecutor.kt @@ -28,9 +28,11 @@ object FederatedTypePromiseResolverExecutor : TypeResolverExecutor>>, environment: DataFetchingEnvironment ): CompletableFuture>> = - resolvableEntities.map { resolvableEntity -> - resolveEntity(resolvableEntity, environment) - }.joinAll() + resolvableEntities + .map { resolvableEntity -> + resolveEntity(resolvableEntity, environment) + } + .joinAll() @Suppress("TooGenericExceptionCaught") private fun resolveEntity( @@ -39,33 +41,36 @@ object FederatedTypePromiseResolverExecutor : TypeResolverExecutor> { val indexes = resolvableEntity.indexedRepresentations.map(IndexedValue>::index) val representations = resolvableEntity.indexedRepresentations.map(IndexedValue>::value) - val resultsPromise = representations.map { representation -> - // TODO decide if keeping this, nothing stops users to synchronously throw an exception - try { - resolvableEntity.resolver.resolve(environment, representation) - } catch (e: Exception) { - CompletableFuture.completedFuture( - FederatedRequestFailure( - "Exception was thrown while trying to resolve federated type, representation=$representation", - e - ) - ) - } - }.allSettled() - - return resultsPromise.thenApply { results -> - indexes.zip( - results.mapIndexed { index, result -> - try { - result.getOrThrow() - } catch (e: Exception) { + return representations + .map { representation -> + // synchronous exceptions while returning CompletableFuture + // as nothing stops users to synchronously throw an exception + try { + resolvableEntity.resolver.resolve(environment, representation) + } catch (e: Exception) { + CompletableFuture.completedFuture( FederatedRequestFailure( - "Exception was thrown while trying to resolve federated type, representation=${representations[index]}", + "Exception was thrown while trying to resolve federated type, representation=$representation", e ) - } + ) } - ).toMap() - } + } + .allSettled() + .thenApply { results -> + indexes.zip( + results.mapIndexed { index, result -> + // asynchronous exceptions while completing CompletableFuture + try { + result.getOrThrow() + } catch (e: Exception) { + FederatedRequestFailure( + "Exception was thrown while trying to resolve federated type, representation=${representations[index]}", + e + ) + } + } + ).toMap() + } } } diff --git a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt index 1db1a614a2..4194878765 100644 --- a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt +++ b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt @@ -16,12 +16,15 @@ package com.expediagroup.graphql.generator.federation.extensions import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionException /** - * Returns a new CompletableFuture of a list with the resolved values of the given CompletableFutures. - * the returned completableFuture will complete when all given CompletableFutures complete. - * If any of the given CompletableFutures complete exceptionally, then the returned CompletableFuture also does so, - * with a CompletionException holding this exception as its cause. + * Returns a [CompletableFuture] that completes when all the input futures have completed, + * with a list of the resolved values obtained from the completed futures. + * If any of the input futures complete exceptionally, then the returned [CompletableFuture] also completes exceptionally + * with a [java.util.concurrent.CompletionException] holding the exception as its cause. + * + * @return a [CompletableFuture] that completes with a list of resolved values. */ internal fun List>.joinAll(): CompletableFuture> = CompletableFuture.allOf( @@ -30,6 +33,13 @@ internal fun List>.joinAll(): CompletableFut map(CompletableFuture::join) } +/** + * Returns a [CompletableFuture] that completes when all the input futures have completed, + * with a list of [Result] objects that indicate whether each future completed successfully or with an error. + * If a future completed with an error, the corresponding [Result] object will contain the exception that was thrown. + * + * @return a [CompletableFuture] that completes with a list of [Result] objects. + */ @Suppress("TooGenericExceptionCaught") internal fun List>.allSettled(): CompletableFuture>> { val resultFutures = map { future -> @@ -48,4 +58,3 @@ internal fun List>.allSettled(): Completable resultFutures.map(CompletableFuture>::join) } } - diff --git a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt index e4ae7308f3..9fef4239e1 100644 --- a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt +++ b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt @@ -34,7 +34,6 @@ class CompletableFutureExtensionsKtTest { val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() val thirdPromise = CompletableFuture.completedFuture("third promise") - val result = listOf(firstPromise, secondPromise, thirdPromise).joinAll().join() assertEquals(3, result.size) @@ -62,7 +61,6 @@ class CompletableFutureExtensionsKtTest { val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() val thirdPromise = CompletableFuture.completedFuture("third promise") - val result = listOf(firstPromise, secondPromise, thirdPromise).allSettled().join() assertEquals(3, result.size) From 637eb2fa5c1375a9648ab7c4131f839c36965999 Mon Sep 17 00:00:00 2001 From: Samuel Vazquez Date: Mon, 17 Apr 2023 16:27:44 -0700 Subject: [PATCH 3/4] feat: remove import --- .../federation/extensions/CompletableFutureExtensions.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt index 4194878765..00b9812221 100644 --- a/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt +++ b/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2023 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package com.expediagroup.graphql.generator.federation.extensions import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionException /** * Returns a [CompletableFuture] that completes when all the input futures have completed, From 05b5e7842d9f649034a8bcbaeecc66d3f102abd9 Mon Sep 17 00:00:00 2001 From: Samuel Vazquez Date: Mon, 17 Apr 2023 22:39:42 -0700 Subject: [PATCH 4/4] feat: stream, coroutine and promise --- .../CompletableFutureExtensionsKtTest.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt index 9fef4239e1..2d92c22e99 100644 --- a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt +++ b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/extensions/CompletableFutureExtensionsKtTest.kt @@ -16,6 +16,11 @@ package com.expediagroup.graphql.generator.federation.extensions +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.future.asCompletableFuture import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import reactor.kotlin.core.publisher.toMono @@ -27,11 +32,15 @@ import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertTrue +@OptIn(DelicateCoroutinesApi::class) class CompletableFutureExtensionsKtTest { @Test fun `joinAll asynchronously collects a list of completable futures of elements into a completable future list of elements`() { val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() - val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val secondPromise = GlobalScope.async { + delay(400) + "second promise" + }.asCompletableFuture() val thirdPromise = CompletableFuture.completedFuture("third promise") val result = listOf(firstPromise, secondPromise, thirdPromise).joinAll().join() @@ -45,7 +54,10 @@ class CompletableFutureExtensionsKtTest { @Test fun `joinAll throws an exception with a completableFuture completes exceptionally`() { val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() - val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val secondPromise = GlobalScope.async { + delay(400) + "second promise" + }.asCompletableFuture() val thirdPromise = CompletableFuture.supplyAsync { throw Exception("async exception") } @@ -58,7 +70,10 @@ class CompletableFutureExtensionsKtTest { @Test fun `allSettled asynchronously collects a list of completable futures of elements into a completable future list of elements`() { val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() - val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val secondPromise = GlobalScope.async { + delay(400) + "second promise" + }.asCompletableFuture() val thirdPromise = CompletableFuture.completedFuture("third promise") val result = listOf(firstPromise, secondPromise, thirdPromise).allSettled().join() @@ -72,7 +87,10 @@ class CompletableFutureExtensionsKtTest { @Test fun `allSettled asynchronously collects a list of completable futures of elements even if a completable future completes exceptionally`() { val firstPromise = "first promise".toMono().delayElement(Duration.ofMillis(500)).toFuture() - val secondPromise = "second promise".toMono().delayElement(Duration.ofMillis(400)).toFuture() + val secondPromise = GlobalScope.async { + delay(400) + "second promise" + }.asCompletableFuture() val thirdPromise = CompletableFuture.supplyAsync { throw Exception("async exception") }