From 32fbf02081a54c8d5ffe792f8dfe1da4b228c9b2 Mon Sep 17 00:00:00 2001 From: Adam Lehenbauer Date: Mon, 10 Feb 2025 14:09:44 -0500 Subject: [PATCH] Fix kotlin child workflow execute varargs (#2395) * Fix kotlin child workflow execute varargs * Fix untyped activity execute kotlin varargs --- .../io/temporal/workflow/ActivityStubExt.kt | 4 +- .../temporal/workflow/ChildWorkflowStubExt.kt | 4 +- .../workflow/KotlinActivityStubExtTest.kt | 118 ++++++++++++++++++ .../KotlinChildWorkflowStubExtTest.kt | 103 +++++++++++++++ 4 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinActivityStubExtTest.kt create mode 100644 temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinChildWorkflowStubExtTest.kt diff --git a/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ActivityStubExt.kt b/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ActivityStubExt.kt index 26b42e3918..ca56b8697e 100644 --- a/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ActivityStubExt.kt +++ b/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ActivityStubExt.kt @@ -33,7 +33,7 @@ import kotlin.reflect.typeOf */ @OptIn(ExperimentalStdlibApi::class) inline fun ActivityStub.execute(activityName: String, vararg args: Any?): T { - return execute(activityName, T::class.java, typeOf().javaType, args) + return execute(activityName, T::class.java, typeOf().javaType, *args) } /** @@ -49,5 +49,5 @@ inline fun ActivityStub.executeAsync( activityName: String, vararg args: Any? ): Promise { - return executeAsync(activityName, T::class.java, typeOf().javaType, args) + return executeAsync(activityName, T::class.java, typeOf().javaType, *args) } diff --git a/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ChildWorkflowStubExt.kt b/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ChildWorkflowStubExt.kt index 399f8ba624..dab9547a45 100644 --- a/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ChildWorkflowStubExt.kt +++ b/temporal-kotlin/src/main/kotlin/io/temporal/workflow/ChildWorkflowStubExt.kt @@ -28,7 +28,7 @@ import kotlin.reflect.typeOf */ @OptIn(ExperimentalStdlibApi::class) inline fun ChildWorkflowStub.execute(vararg args: Any?): T { - return execute(T::class.java, typeOf().javaType, args) + return execute(T::class.java, typeOf().javaType, *args) } /** @@ -36,5 +36,5 @@ inline fun ChildWorkflowStub.execute(vararg args: Any?): T { */ @OptIn(ExperimentalStdlibApi::class) inline fun ChildWorkflowStub.executeAsync(vararg args: Any?): Promise { - return executeAsync(T::class.java, typeOf().javaType, args) + return executeAsync(T::class.java, typeOf().javaType, *args) } diff --git a/temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinActivityStubExtTest.kt b/temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinActivityStubExtTest.kt new file mode 100644 index 0000000000..07f7c62a95 --- /dev/null +++ b/temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinActivityStubExtTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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 io.temporal.workflow + +import io.temporal.activity.ActivityInterface +import io.temporal.activity.ActivityOptions +import io.temporal.activity.setRetryOptions +import io.temporal.client.WorkflowClientOptions +import io.temporal.client.WorkflowOptions +import io.temporal.common.converter.DefaultDataConverter +import io.temporal.common.converter.JacksonJsonPayloadConverter +import io.temporal.common.converter.KotlinObjectMapperFactory +import io.temporal.testing.internal.SDKTestWorkflowRule +import org.junit.Rule +import org.junit.Test +import java.time.Duration + +class KotlinActivityStubExtTest { + + @Rule + @JvmField + var testWorkflowRule: SDKTestWorkflowRule = SDKTestWorkflowRule.newBuilder() + .setWorkflowTypes( + SyncWorkflowImpl::class.java, + AsyncWorkflowImpl::class.java + ) + .setActivityImplementations(ActivityImpl()) + .setWorkflowClientOptions( + WorkflowClientOptions.newBuilder() + .setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(KotlinObjectMapperFactory.new()))) + .build() + ) + .build() + + @WorkflowInterface + interface SyncWorkflow { + @WorkflowMethod + fun execute() + } + + class SyncWorkflowImpl : SyncWorkflow { + + override fun execute() { + val activity = Workflow.newUntypedActivityStub( + ActivityOptions { + setStartToCloseTimeout(Duration.ofSeconds(5)) + setRetryOptions { setMaximumAttempts(1) } + } + ) + + activity.execute("Run", "test-argument") + } + } + + @WorkflowInterface + interface AsyncWorkflow { + @WorkflowMethod + fun execute() + } + + class AsyncWorkflowImpl : AsyncWorkflow { + override fun execute() { + val activity = Workflow.newUntypedActivityStub( + ActivityOptions { + setStartToCloseTimeout(Duration.ofSeconds(5)) + setRetryOptions { setMaximumAttempts(1) } + } + ) + + val promise = activity.executeAsync("Run", "test-argument") + promise.get() + } + } + + @ActivityInterface + interface TestActivity { + fun run(arg: String) + } + + class ActivityImpl : TestActivity { + override fun run(arg: String) { + } + } + + @Test + fun `execute on untyped activity stub should spread varargs`() { + val client = testWorkflowRule.workflowClient + val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build() + val workflowStub = client.newWorkflowStub(SyncWorkflow::class.java, options) + workflowStub.execute() + } + + @Test + fun `executeAsync on untyped activity stub should spread varargs`() { + val client = testWorkflowRule.workflowClient + val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build() + val workflowStub = client.newWorkflowStub(AsyncWorkflow::class.java, options) + workflowStub.execute() + } +} diff --git a/temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinChildWorkflowStubExtTest.kt b/temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinChildWorkflowStubExtTest.kt new file mode 100644 index 0000000000..5fd70535c4 --- /dev/null +++ b/temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinChildWorkflowStubExtTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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 io.temporal.workflow + +import io.temporal.client.WorkflowClientOptions +import io.temporal.client.WorkflowOptions +import io.temporal.common.converter.DefaultDataConverter +import io.temporal.common.converter.JacksonJsonPayloadConverter +import io.temporal.common.converter.KotlinObjectMapperFactory +import io.temporal.testing.internal.SDKTestWorkflowRule +import org.junit.Rule +import org.junit.Test + +class KotlinChildWorkflowStubExtTest { + + @Rule + @JvmField + var testWorkflowRule: SDKTestWorkflowRule = SDKTestWorkflowRule.newBuilder() + .setWorkflowTypes( + ParentWorkflowImpl::class.java, + AsyncParentWorkflowImpl::class.java, + ChildWorkflowImpl::class.java + ) + .setWorkflowClientOptions( + WorkflowClientOptions.newBuilder() + .setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(KotlinObjectMapperFactory.new()))) + .build() + ) + .build() + + @WorkflowInterface + interface ChildWorkflow { + @WorkflowMethod + fun execute(argument: String): Int + } + + class ChildWorkflowImpl : ChildWorkflow { + override fun execute(argument: String): Int { + return 0 + } + } + + @WorkflowInterface + interface ParentWorkflow { + @WorkflowMethod + fun execute() + } + + class ParentWorkflowImpl : ParentWorkflow { + override fun execute() { + val childWorkflow = Workflow.newUntypedChildWorkflowStub("ChildWorkflow") + childWorkflow.execute("test-argument") + } + } + + @WorkflowInterface + interface AsyncParentWorkflow { + @WorkflowMethod + fun execute() + } + + class AsyncParentWorkflowImpl : AsyncParentWorkflow { + override fun execute() { + val childWorkflow = Workflow.newUntypedChildWorkflowStub("ChildWorkflow") + val promise = childWorkflow.executeAsync("test-argument") + promise.get() + } + } + + @Test + fun `execute on untyped child workflow should spread varargs`() { + val client = testWorkflowRule.workflowClient + val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build() + val workflowStub = client.newWorkflowStub(ParentWorkflow::class.java, options) + workflowStub.execute() + } + + @Test + fun `executeAsync on untyped child workflow should spread varargs`() { + val client = testWorkflowRule.workflowClient + val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build() + val workflowStub = client.newWorkflowStub(AsyncParentWorkflow::class.java, options) + workflowStub.execute() + } +}