diff --git a/src/main/java/com/uber/cadence/internal/replay/ClockDecisionContext.java b/src/main/java/com/uber/cadence/internal/replay/ClockDecisionContext.java index 339029edb..ca90339c0 100644 --- a/src/main/java/com/uber/cadence/internal/replay/ClockDecisionContext.java +++ b/src/main/java/com/uber/cadence/internal/replay/ClockDecisionContext.java @@ -33,6 +33,7 @@ import com.uber.cadence.workflow.ActivityFailureException; import com.uber.cadence.workflow.Functions.Func; import com.uber.cadence.workflow.Functions.Func1; +import com.uber.cadence.workflow.GetVersionOptions; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; @@ -320,6 +321,16 @@ private void handleVersionMarker(MarkerRecordedEventAttributes attributes) { GetVersionResult getVersion( String changeId, DataConverter converter, int minSupported, int maxSupported) { + return getVersion( + changeId, converter, minSupported, maxSupported, GetVersionOptions.newBuilder().build()); + } + + GetVersionResult getVersion( + String changeId, + DataConverter converter, + int minSupported, + int maxSupported, + GetVersionOptions options) { Predicate changeIdEquals = (attributes) -> { MarkerHandler.MarkerInterface markerData = @@ -328,6 +339,9 @@ GetVersionResult getVersion( }; decisions.addAllMissingVersionMarker(true, Optional.of(changeIdEquals)); + // Determine which version to use based on options + int versionToUse = determineVersionToUse(minSupported, maxSupported, options); + final MarkerHandler.HandleResult result = versionHandler.handle( changeId, @@ -336,14 +350,14 @@ GetVersionResult getVersion( if (stored.isPresent()) { return Optional.empty(); } - return Optional.of(converter.toData(maxSupported)); + return Optional.of(converter.toData(versionToUse)); }); final boolean isNewlyAdded = result.isNewlyStored(); Map searchAttributesForChangeVersion = null; if (isNewlyAdded) { searchAttributesForChangeVersion = - createSearchAttributesForChangeVersion(changeId, maxSupported, versionMap); + createSearchAttributesForChangeVersion(changeId, versionToUse, versionMap); } Integer version = versionMap.get(changeId); @@ -361,6 +375,22 @@ GetVersionResult getVersion( return new GetVersionResult(version, isNewlyAdded, searchAttributesForChangeVersion); } + private int determineVersionToUse(int minSupported, int maxSupported, GetVersionOptions options) { + if (isReplaying()) { + return WorkflowInternal.DEFAULT_VERSION; + } + + if (options.getCustomVersion().isPresent()) { + return options.getCustomVersion().get(); + } + + if (options.isUseMinVersion()) { + return minSupported; + } + + return maxSupported; + } + private void validateVersion(String changeID, int version, int minSupported, int maxSupported) { if ((version < minSupported || version > maxSupported) && version != WorkflowInternal.DEFAULT_VERSION) { diff --git a/src/main/java/com/uber/cadence/internal/replay/DecisionContext.java b/src/main/java/com/uber/cadence/internal/replay/DecisionContext.java index e3e76c8e9..f154a8e87 100644 --- a/src/main/java/com/uber/cadence/internal/replay/DecisionContext.java +++ b/src/main/java/com/uber/cadence/internal/replay/DecisionContext.java @@ -25,6 +25,7 @@ import com.uber.cadence.converter.DataConverter; import com.uber.cadence.workflow.Functions.Func; import com.uber.cadence.workflow.Functions.Func1; +import com.uber.cadence.workflow.GetVersionOptions; import com.uber.cadence.workflow.Promise; import com.uber.m3.tally.Scope; import java.time.Duration; @@ -194,6 +195,23 @@ Optional mutableSideEffect( */ int getVersion(String changeID, DataConverter dataConverter, int minSupported, int maxSupported); + /** + * Enhanced version of getVersion with additional options for version control. + * + * @param changeID identifier of a particular change + * @param dataConverter data converter for serialization + * @param minSupported min version supported for the change + * @param maxSupported max version supported for the change + * @param options version control options + * @return version + */ + int getVersion( + String changeID, + DataConverter dataConverter, + int minSupported, + int maxSupported, + GetVersionOptions options); + Random newRandom(); /** @return scope to be used for metrics reporting. */ diff --git a/src/main/java/com/uber/cadence/internal/replay/DecisionContextImpl.java b/src/main/java/com/uber/cadence/internal/replay/DecisionContextImpl.java index c3bc89a50..b8d0e8469 100644 --- a/src/main/java/com/uber/cadence/internal/replay/DecisionContextImpl.java +++ b/src/main/java/com/uber/cadence/internal/replay/DecisionContextImpl.java @@ -35,6 +35,7 @@ import com.uber.cadence.internal.worker.SingleWorkerOptions; import com.uber.cadence.workflow.Functions.Func; import com.uber.cadence.workflow.Functions.Func1; +import com.uber.cadence.workflow.GetVersionOptions; import com.uber.cadence.workflow.Promise; import com.uber.cadence.workflow.Workflow; import com.uber.m3.tally.Scope; @@ -302,6 +303,23 @@ public int getVersion( return results.getVersion(); } + @Override + public int getVersion( + String changeID, + DataConverter converter, + int minSupported, + int maxSupported, + GetVersionOptions options) { + final ClockDecisionContext.GetVersionResult results = + workflowClock.getVersion(changeID, converter, minSupported, maxSupported, options); + if (results.shouldUpdateCadenceChangeVersion()) { + upsertSearchAttributes( + InternalUtils.convertMapToSearchAttributes( + results.getSearchAttributesForChangeVersion())); + } + return results.getVersion(); + } + @Override public long currentTimeMillis() { return workflowClock.currentTimeMillis(); diff --git a/src/main/java/com/uber/cadence/internal/sync/DeterministicRunnerImpl.java b/src/main/java/com/uber/cadence/internal/sync/DeterministicRunnerImpl.java index 84fb6a226..cc106eefe 100644 --- a/src/main/java/com/uber/cadence/internal/sync/DeterministicRunnerImpl.java +++ b/src/main/java/com/uber/cadence/internal/sync/DeterministicRunnerImpl.java @@ -36,6 +36,7 @@ import com.uber.cadence.internal.replay.StartChildWorkflowExecutionParameters; import com.uber.cadence.workflow.Functions.Func; import com.uber.cadence.workflow.Functions.Func1; +import com.uber.cadence.workflow.GetVersionOptions; import com.uber.cadence.workflow.Promise; import com.uber.m3.tally.Scope; import java.time.Duration; @@ -695,6 +696,16 @@ public int getVersion( throw new UnsupportedOperationException("not implemented"); } + @Override + public int getVersion( + String changeID, + DataConverter converter, + int minSupported, + int maxSupported, + GetVersionOptions options) { + throw new UnsupportedOperationException("not implemented"); + } + @Override public Random newRandom() { throw new UnsupportedOperationException("not implemented"); diff --git a/src/main/java/com/uber/cadence/internal/sync/SyncDecisionContext.java b/src/main/java/com/uber/cadence/internal/sync/SyncDecisionContext.java index c204ff22a..0bf55b9c0 100644 --- a/src/main/java/com/uber/cadence/internal/sync/SyncDecisionContext.java +++ b/src/main/java/com/uber/cadence/internal/sync/SyncDecisionContext.java @@ -54,6 +54,7 @@ import com.uber.cadence.workflow.ContinueAsNewOptions; import com.uber.cadence.workflow.Functions; import com.uber.cadence.workflow.Functions.Func; +import com.uber.cadence.workflow.GetVersionOptions; import com.uber.cadence.workflow.Promise; import com.uber.cadence.workflow.SignalExternalWorkflowException; import com.uber.cadence.workflow.Workflow; @@ -620,6 +621,12 @@ public int getVersion(String changeID, int minSupported, int maxSupported) { return context.getVersion(changeID, converter, minSupported, maxSupported); } + @Override + public int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + return context.getVersion(changeID, converter, minSupported, maxSupported, options); + } + void fireTimers() { timers.fireTimers(context.currentTimeMillis()); } diff --git a/src/main/java/com/uber/cadence/internal/sync/TestActivityEnvironmentInternal.java b/src/main/java/com/uber/cadence/internal/sync/TestActivityEnvironmentInternal.java index 2ed9218b1..56ffb7694 100644 --- a/src/main/java/com/uber/cadence/internal/sync/TestActivityEnvironmentInternal.java +++ b/src/main/java/com/uber/cadence/internal/sync/TestActivityEnvironmentInternal.java @@ -242,6 +242,12 @@ public int getVersion(String changeID, int minSupported, int maxSupported) { throw new UnsupportedOperationException("not implemented"); } + @Override + public int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + throw new UnsupportedOperationException("not implemented"); + } + @Override public void continueAsNew( Optional workflowType, Optional options, Object[] args) { diff --git a/src/main/java/com/uber/cadence/internal/sync/WorkflowInternal.java b/src/main/java/com/uber/cadence/internal/sync/WorkflowInternal.java index 217195841..980fdb24c 100644 --- a/src/main/java/com/uber/cadence/internal/sync/WorkflowInternal.java +++ b/src/main/java/com/uber/cadence/internal/sync/WorkflowInternal.java @@ -36,6 +36,7 @@ import com.uber.cadence.workflow.ExternalWorkflowStub; import com.uber.cadence.workflow.Functions; import com.uber.cadence.workflow.Functions.Func; +import com.uber.cadence.workflow.GetVersionOptions; import com.uber.cadence.workflow.Promise; import com.uber.cadence.workflow.QueryMethod; import com.uber.cadence.workflow.Workflow; @@ -253,6 +254,20 @@ public static int getVersion(String changeID, int minSupported, int maxSupported return getWorkflowInterceptor().getVersion(changeID, minSupported, maxSupported); } + /** + * Enhanced version of getVersion with additional options for version control. + * + * @param changeID identifier of a particular change + * @param minSupported min version supported for the change + * @param maxSupported max version supported for the change + * @param options version control options + * @return version + */ + public static int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + return getWorkflowInterceptor().getVersion(changeID, minSupported, maxSupported, options); + } + public static Promise> promiseAllOf(Collection> promises) { return new AllOfPromise<>(promises); } diff --git a/src/main/java/com/uber/cadence/workflow/GetVersionOptions.java b/src/main/java/com/uber/cadence/workflow/GetVersionOptions.java new file mode 100644 index 000000000..01ac1325e --- /dev/null +++ b/src/main/java/com/uber/cadence/workflow/GetVersionOptions.java @@ -0,0 +1,101 @@ +/* + * Copyright 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 file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.uber.cadence.workflow; + +import java.util.Optional; + +/** + * Options for configuring GetVersion behavior. This class provides a builder pattern for + * configuring version control options. + * + *

Example usage: + * + *


+ * // Force a specific version
+ * GetVersionOptions options = GetVersionOptions.newBuilder()
+ *     .executeWithVersion(2)
+ *     .build();
+ *
+ * // Use minimum supported version
+ * GetVersionOptions options = GetVersionOptions.newBuilder()
+ *     .executeWithMinVersion()
+ *     .build();
+ * 
+ */ +public final class GetVersionOptions { + private final Optional customVersion; + private final boolean useMinVersion; + + private GetVersionOptions(Optional customVersion, boolean useMinVersion) { + this.customVersion = customVersion; + this.useMinVersion = useMinVersion; + } + + /** Returns the custom version if specified, otherwise empty. */ + public Optional getCustomVersion() { + return customVersion; + } + + /** Returns true if the minimum version should be used instead of maximum version. */ + public boolean isUseMinVersion() { + return useMinVersion; + } + + /** Creates a new builder for GetVersionOptions. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** Builder for GetVersionOptions. */ + public static class Builder { + private Optional customVersion = Optional.empty(); + private boolean useMinVersion = false; + + /** + * Forces a specific version to be returned when executed for the first time, instead of + * returning maxSupported version. + * + * @param version the specific version to use + * @return this builder + */ + public Builder executeWithVersion(int version) { + this.customVersion = Optional.of(version); + return this; + } + + /** + * Makes GetVersion return minSupported version when executed for the first time, instead of + * returning maxSupported version. + * + * @return this builder + */ + public Builder executeWithMinVersion() { + this.useMinVersion = true; + return this; + } + + /** + * Builds the GetVersionOptions instance. + * + * @return the configured GetVersionOptions + */ + public GetVersionOptions build() { + return new GetVersionOptions(customVersion, useMinVersion); + } + } +} diff --git a/src/main/java/com/uber/cadence/workflow/Workflow.java b/src/main/java/com/uber/cadence/workflow/Workflow.java index b5c3966ae..20f53864c 100644 --- a/src/main/java/com/uber/cadence/workflow/Workflow.java +++ b/src/main/java/com/uber/cadence/workflow/Workflow.java @@ -353,7 +353,7 @@ * executed in parallel. *
  • Do not call any non deterministic functions like non seeded random or {@link * UUID#randomUUID()} directly form the workflow code. Always do this in activities. - *
  • Don’t perform any IO or service calls as they are not usually deterministic. Use activities + *
  • Don't perform any IO or service calls as they are not usually deterministic. Use activities * for this. *
  • Only use {@link #currentTimeMillis()} to get the current time inside a workflow. *
  • Do not use native Java {@link Thread} or any other multi-threaded classes like {@link @@ -369,7 +369,7 @@ *
  • Use {@link WorkflowQueue} instead of {@link java.util.concurrent.BlockingQueue}. *
  • Don't change workflow code when there are open workflows. The ability to do updates through * visioning is TBD. - *
  • Don’t access configuration APIs directly from a workflow because changes in the + *
  • Don't access configuration APIs directly from a workflow because changes in the * configuration might affect a workflow execution path. Pass it as an argument to a workflow * function or use an activity to load it. * @@ -1123,15 +1123,15 @@ public static R mutableSideEffect( * * * The reason to keep it is: 1) it ensures that if there is older version execution still running, - * it will fail here and not proceed; 2) if you ever need to make more changes for “fooChange”, + * it will fail here and not proceed; 2) if you ever need to make more changes for "fooChange", * for example change activity3 to activity4, you just need to update the maxVersion from 2 to 3. * *

    Note that, you only need to preserve the first call to GetVersion() for each changeID. All * subsequent call to GetVersion() with same changeID are safe to remove. However, if you really * want to get rid of the first GetVersion() call as well, you can do so, but you need to make - * sure: 1) all older version executions are completed; 2) you can no longer use “fooChange” as + * sure: 1) all older version executions are completed; 2) you can no longer use "fooChange" as * changeID. If you ever need to make changes to that same part, you would need to use a different - * changeID like “fooChange-fix2”, and start minVersion from DefaultVersion again. + * changeID like "fooChange-fix2", and start minVersion from DefaultVersion again. * * @param changeID identifier of a particular change. All calls to getVersion that share a * changeID are guaranteed to return the same version number. Use this to perform multiple @@ -1144,6 +1144,66 @@ public static int getVersion(String changeID, int minSupported, int maxSupported return WorkflowInternal.getVersion(changeID, minSupported, maxSupported); } + /** + * Enhanced version of {@code getVersion} with additional options for version control. This method + * provides more granular control over version execution and enables safer deployment strategies. + * + *

    Example usage with custom version: + * + *

    
    +   * int version = Workflow.getVersion("changeId", 1, 3,
    +   *     GetVersionOptions.newBuilder().executeWithVersion(2).build());
    +   * 
    + * + *

    Example usage with minimum version: + * + *

    
    +   * int version = Workflow.getVersion("changeId", 1, 3,
    +   *     GetVersionOptions.newBuilder().executeWithMinVersion().build());
    +   * 
    + * + * @param changeID identifier of a particular change + * @param minSupported min version supported for the change + * @param maxSupported max version supported for the change + * @param options version control options + * @return version + */ + public static int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + return WorkflowInternal.getVersion(changeID, minSupported, maxSupported, options); + } + + /** + * Convenience method that forces a specific version to be returned when executed for the first + * time. + * + * @param changeID identifier of a particular change + * @param minSupported min version supported for the change + * @param maxSupported max version supported for the change + * @param customVersion the specific version to use + * @return version + */ + public static int getVersionWithCustomVersion( + String changeID, int minSupported, int maxSupported, int customVersion) { + GetVersionOptions options = + GetVersionOptions.newBuilder().executeWithVersion(customVersion).build(); + return getVersion(changeID, minSupported, maxSupported, options); + } + + /** + * Convenience method that makes GetVersion return minSupported version when executed for the + * first time. + * + * @param changeID identifier of a particular change + * @param minSupported min version supported for the change + * @param maxSupported max version supported for the change + * @return version + */ + public static int getVersionWithMinVersion(String changeID, int minSupported, int maxSupported) { + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithMinVersion().build(); + return getVersion(changeID, minSupported, maxSupported, options); + } + /** * Get scope for reporting business metrics in workflow logic. This should be used instead of * creating new metrics scopes as it is able to dedup metrics during replay. diff --git a/src/main/java/com/uber/cadence/workflow/WorkflowInterceptor.java b/src/main/java/com/uber/cadence/workflow/WorkflowInterceptor.java index c744ae88f..055a9d2c9 100644 --- a/src/main/java/com/uber/cadence/workflow/WorkflowInterceptor.java +++ b/src/main/java/com/uber/cadence/workflow/WorkflowInterceptor.java @@ -127,6 +127,17 @@ R mutableSideEffect( int getVersion(String changeID, int minSupported, int maxSupported); + /** + * Enhanced version of getVersion with additional options for version control. + * + * @param changeID identifier of a particular change + * @param minSupported min version supported for the change + * @param maxSupported max version supported for the change + * @param options version control options + * @return version + */ + int getVersion(String changeID, int minSupported, int maxSupported, GetVersionOptions options); + void continueAsNew( Optional workflowType, Optional options, Object[] args); diff --git a/src/main/java/com/uber/cadence/workflow/WorkflowInterceptorBase.java b/src/main/java/com/uber/cadence/workflow/WorkflowInterceptorBase.java index b6f436160..ebaaffcdd 100644 --- a/src/main/java/com/uber/cadence/workflow/WorkflowInterceptorBase.java +++ b/src/main/java/com/uber/cadence/workflow/WorkflowInterceptorBase.java @@ -134,6 +134,12 @@ public int getVersion(String changeID, int minSupported, int maxSupported) { return next.getVersion(changeID, minSupported, maxSupported); } + @Override + public int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + return next.getVersion(changeID, minSupported, maxSupported, options); + } + @Override public void continueAsNew( Optional workflowType, Optional options, Object[] args) { diff --git a/src/test/java/com/uber/cadence/testing/TestWorkflowEnvironmentGetVersionTest.java b/src/test/java/com/uber/cadence/testing/TestWorkflowEnvironmentGetVersionTest.java new file mode 100644 index 000000000..aff628ff6 --- /dev/null +++ b/src/test/java/com/uber/cadence/testing/TestWorkflowEnvironmentGetVersionTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 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 file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.uber.cadence.testing; + +import static org.junit.Assert.*; + +import com.uber.cadence.client.WorkflowClient; +import com.uber.cadence.client.WorkflowOptions; +import com.uber.cadence.worker.Worker; +import com.uber.cadence.workflow.GetVersionOptions; +import com.uber.cadence.workflow.Workflow; +import com.uber.cadence.workflow.WorkflowMethod; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import org.junit.Test; + +/** + * Test to verify that TestWorkflowEnvironment supports the new GetVersionOptions functionality. + * This test ensures that workflows using enhanced version control features can be tested properly. + */ +public class TestWorkflowEnvironmentGetVersionTest { + + private static final String TASK_LIST = "TestWorkflowEnvironmentGetVersionTest"; + + public interface TestWorkflowWithVersionControl { + @WorkflowMethod + String workflowWithVersionControl(String input); + } + + public static class TestWorkflowWithVersionControlImpl implements TestWorkflowWithVersionControl { + + @Override + public String workflowWithVersionControl(String input) { + // Test the new getVersion method with options + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithVersion(2).build(); + + int version = Workflow.getVersion("test-change", 1, 3, options); + + // Test the convenience methods + int versionWithCustom = Workflow.getVersionWithCustomVersion("test-change", 1, 3, 2); + int versionWithMin = Workflow.getVersionWithMinVersion("test-change", 1, 3); + + return String.format( + "input=%s, version=%d, custom=%d, min=%d", + input, version, versionWithCustom, versionWithMin); + } + } + + @Test + public void testWorkflowWithVersionControl() throws ExecutionException, InterruptedException { + TestWorkflowEnvironment testEnvironment = TestWorkflowEnvironment.newInstance(); + + // Create a worker that polls tasks from the service owned by the testEnvironment + Worker worker = testEnvironment.newWorker(TASK_LIST); + worker.registerWorkflowImplementationTypes(TestWorkflowWithVersionControlImpl.class); + + // Create a WorkflowClient that interacts with the server owned by the testEnvironment + WorkflowClient client = testEnvironment.newWorkflowClient(); + + // Create workflow options with required timeout + WorkflowOptions options = + new WorkflowOptions.Builder() + .setExecutionStartToCloseTimeout(Duration.ofMinutes(5)) + .setTaskList(TASK_LIST) + .build(); + + TestWorkflowWithVersionControl workflow = + client.newWorkflowStub(TestWorkflowWithVersionControl.class, options); + + // Start the test environment (this starts the worker) + testEnvironment.start(); + + // Start a workflow execution + String result = workflow.workflowWithVersionControl("test-input"); + + // Verify the result contains the expected version information + assertNotNull("Result should not be null", result); + assertTrue("Result should contain version information", result.contains("version=")); + assertTrue("Result should contain custom version information", result.contains("custom=")); + assertTrue("Result should contain min version information", result.contains("min=")); + + // Close workers and release in-memory service + testEnvironment.close(); + } + + @Test + public void testGetVersionOptionsBuilder() { + // Test the builder pattern for GetVersionOptions + GetVersionOptions options1 = GetVersionOptions.newBuilder().executeWithVersion(5).build(); + + assertTrue("Should have custom version", options1.getCustomVersion().isPresent()); + assertEquals( + "Custom version should be 5", Integer.valueOf(5), options1.getCustomVersion().get()); + assertFalse("Should not use min version", options1.isUseMinVersion()); + + GetVersionOptions options2 = GetVersionOptions.newBuilder().executeWithMinVersion().build(); + + assertFalse("Should not have custom version", options2.getCustomVersion().isPresent()); + assertTrue("Should use min version", options2.isUseMinVersion()); + + GetVersionOptions options3 = + GetVersionOptions.newBuilder().executeWithVersion(10).executeWithMinVersion().build(); + + assertTrue("Should have custom version", options3.getCustomVersion().isPresent()); + assertEquals( + "Custom version should be 10", Integer.valueOf(10), options3.getCustomVersion().get()); + assertTrue("Should use min version", options3.isUseMinVersion()); + } + + @Test + public void testDefaultGetVersionOptions() { + // Test default options + GetVersionOptions defaultOptions = GetVersionOptions.newBuilder().build(); + + assertFalse( + "Default should not have custom version", defaultOptions.getCustomVersion().isPresent()); + assertFalse("Default should not use min version", defaultOptions.isUseMinVersion()); + } +} diff --git a/src/test/java/com/uber/cadence/workflow/EnhancedGetVersionTest.java b/src/test/java/com/uber/cadence/workflow/EnhancedGetVersionTest.java new file mode 100644 index 000000000..da122493d --- /dev/null +++ b/src/test/java/com/uber/cadence/workflow/EnhancedGetVersionTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 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 file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.uber.cadence.workflow; + +import static org.junit.Assert.*; + +import com.uber.cadence.client.WorkflowClient; +import com.uber.cadence.client.WorkflowOptions; +import com.uber.cadence.testing.TestWorkflowEnvironment; +import com.uber.cadence.worker.Worker; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class EnhancedGetVersionTest { + + private TestWorkflowEnvironment testEnvironment; + private Worker worker; + + @Before + public void setUp() { + testEnvironment = TestWorkflowEnvironment.newInstance(); + worker = testEnvironment.newWorker("test-task-list"); + } + + @After + public void tearDown() { + testEnvironment.close(); + } + + @Test + public void testGetVersionWithCustomVersion() throws ExecutionException, InterruptedException { + worker.registerWorkflowImplementationTypes(TestWorkflowWithCustomVersionImpl.class); + testEnvironment.start(); + + WorkflowClient client = testEnvironment.newWorkflowClient(); + + WorkflowOptions options = + new WorkflowOptions.Builder() + .setExecutionStartToCloseTimeout(Duration.ofMinutes(5)) + .setTaskList("test-task-list") + .build(); + + TestWorkflowWithCustomVersion workflow = + client.newWorkflowStub(TestWorkflowWithCustomVersion.class, options); + String result = workflow.execute("test-input"); + + assertEquals("custom-version-result", result); + } + + @Test + public void testGetVersionWithMinVersion() throws ExecutionException, InterruptedException { + worker.registerWorkflowImplementationTypes(TestWorkflowWithMinVersionImpl.class); + testEnvironment.start(); + + WorkflowClient client = testEnvironment.newWorkflowClient(); + + WorkflowOptions options = + new WorkflowOptions.Builder() + .setExecutionStartToCloseTimeout(Duration.ofMinutes(5)) + .setTaskList("test-task-list") + .build(); + + TestWorkflowWithMinVersion workflow = + client.newWorkflowStub(TestWorkflowWithMinVersion.class, options); + String result = workflow.execute("test-input"); + + assertEquals("min-version-result", result); + } + + @Test + public void testGetVersionWithOptions() throws ExecutionException, InterruptedException { + worker.registerWorkflowImplementationTypes(TestWorkflowWithOptionsImpl.class); + testEnvironment.start(); + + WorkflowClient client = testEnvironment.newWorkflowClient(); + + WorkflowOptions options = + new WorkflowOptions.Builder() + .setExecutionStartToCloseTimeout(Duration.ofMinutes(5)) + .setTaskList("test-task-list") + .build(); + + TestWorkflowWithOptions workflow = + client.newWorkflowStub(TestWorkflowWithOptions.class, options); + String result = workflow.execute("test-input"); + + assertEquals("options-result", result); + } + + @Test + public void testConvenienceMethods() throws ExecutionException, InterruptedException { + worker.registerWorkflowImplementationTypes(TestWorkflowWithConvenienceMethodsImpl.class); + testEnvironment.start(); + + WorkflowClient client = testEnvironment.newWorkflowClient(); + + WorkflowOptions options = + new WorkflowOptions.Builder() + .setExecutionStartToCloseTimeout(Duration.ofMinutes(5)) + .setTaskList("test-task-list") + .build(); + + TestWorkflowWithConvenienceMethods workflow = + client.newWorkflowStub(TestWorkflowWithConvenienceMethods.class, options); + String result = workflow.execute("test-input"); + + assertEquals("convenience-result", result); + } + + public interface TestWorkflowWithCustomVersion { + @WorkflowMethod + String execute(String input); + } + + public static class TestWorkflowWithCustomVersionImpl implements TestWorkflowWithCustomVersion { + @Override + public String execute(String input) { + int version = + Workflow.getVersion( + "test-change", 1, 3, GetVersionOptions.newBuilder().executeWithVersion(2).build()); + + if (version == 2) { + return "custom-version-result"; + } else { + return "other-result"; + } + } + } + + public interface TestWorkflowWithMinVersion { + @WorkflowMethod + String execute(String input); + } + + public static class TestWorkflowWithMinVersionImpl implements TestWorkflowWithMinVersion { + @Override + public String execute(String input) { + int version = + Workflow.getVersion( + "test-change", 1, 3, GetVersionOptions.newBuilder().executeWithMinVersion().build()); + + if (version == 1) { + return "min-version-result"; + } else { + return "other-result"; + } + } + } + + public interface TestWorkflowWithOptions { + @WorkflowMethod + String execute(String input); + } + + public static class TestWorkflowWithOptionsImpl implements TestWorkflowWithOptions { + @Override + public String execute(String input) { + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithVersion(2).build(); + + int version = Workflow.getVersion("test-change", 1, 3, options); + + if (version == 2) { + return "options-result"; + } else { + return "other-result"; + } + } + } + + public interface TestWorkflowWithConvenienceMethods { + @WorkflowMethod + String execute(String input); + } + + public static class TestWorkflowWithConvenienceMethodsImpl + implements TestWorkflowWithConvenienceMethods { + @Override + public String execute(String input) { + int version1 = Workflow.getVersionWithCustomVersion("test-change-1", 1, 3, 2); + int version2 = Workflow.getVersionWithMinVersion("test-change-2", 1, 3); + + if (version1 == 2 && version2 == 1) { + return "convenience-result"; + } else { + return "other-result"; + } + } + } +} diff --git a/src/test/java/com/uber/cadence/workflow/GetVersionOptionsTest.java b/src/test/java/com/uber/cadence/workflow/GetVersionOptionsTest.java new file mode 100644 index 000000000..6447e2af6 --- /dev/null +++ b/src/test/java/com/uber/cadence/workflow/GetVersionOptionsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 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 file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.uber.cadence.workflow; + +import static org.junit.Assert.*; + +import java.util.Optional; +import org.junit.Test; + +public class GetVersionOptionsTest { + + @Test + public void testExecuteWithVersion() { + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithVersion(5).build(); + + assertEquals(Optional.of(5), options.getCustomVersion()); + assertFalse(options.isUseMinVersion()); + } + + @Test + public void testExecuteWithMinVersion() { + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithMinVersion().build(); + + assertEquals(Optional.empty(), options.getCustomVersion()); + assertTrue(options.isUseMinVersion()); + } + + @Test + public void testDefaultOptions() { + GetVersionOptions options = GetVersionOptions.newBuilder().build(); + + assertEquals(Optional.empty(), options.getCustomVersion()); + assertFalse(options.isUseMinVersion()); + } + + @Test + public void testBuilderChaining() { + GetVersionOptions options = + GetVersionOptions.newBuilder().executeWithVersion(3).executeWithMinVersion().build(); + + // When both are set, custom version takes precedence + assertEquals(Optional.of(3), options.getCustomVersion()); + assertTrue(options.isUseMinVersion()); + } + + @Test + public void testMultipleExecuteWithVersionCalls() { + GetVersionOptions options = + GetVersionOptions.newBuilder().executeWithVersion(1).executeWithVersion(2).build(); + + // Last call should take precedence + assertEquals(Optional.of(2), options.getCustomVersion()); + assertFalse(options.isUseMinVersion()); + } + + @Test + public void testZeroVersion() { + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithVersion(0).build(); + + assertEquals(Optional.of(0), options.getCustomVersion()); + assertFalse(options.isUseMinVersion()); + } + + @Test + public void testNegativeVersion() { + GetVersionOptions options = GetVersionOptions.newBuilder().executeWithVersion(-1).build(); + + assertEquals(Optional.of(-1), options.getCustomVersion()); + assertFalse(options.isUseMinVersion()); + } +} diff --git a/src/test/java/com/uber/cadence/workflow/interceptors/SignalWorkflowInterceptor.java b/src/test/java/com/uber/cadence/workflow/interceptors/SignalWorkflowInterceptor.java index f4c113e5c..07fb02b94 100644 --- a/src/test/java/com/uber/cadence/workflow/interceptors/SignalWorkflowInterceptor.java +++ b/src/test/java/com/uber/cadence/workflow/interceptors/SignalWorkflowInterceptor.java @@ -157,6 +157,12 @@ public int getVersion(String changeID, int minSupported, int maxSupported) { return next.getVersion(changeID, minSupported, maxSupported); } + @Override + public int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + return next.getVersion(changeID, minSupported, maxSupported, options); + } + @Override public void continueAsNew( Optional workflowType, Optional options, Object[] args) { diff --git a/src/test/java/com/uber/cadence/workflow/interceptors/TracingWorkflowInterceptorFactory.java b/src/test/java/com/uber/cadence/workflow/interceptors/TracingWorkflowInterceptorFactory.java index 80cf10790..63e1712ab 100644 --- a/src/test/java/com/uber/cadence/workflow/interceptors/TracingWorkflowInterceptorFactory.java +++ b/src/test/java/com/uber/cadence/workflow/interceptors/TracingWorkflowInterceptorFactory.java @@ -207,6 +207,13 @@ public int getVersion(String changeID, int minSupported, int maxSupported) { return next.getVersion(changeID, minSupported, maxSupported); } + @Override + public int getVersion( + String changeID, int minSupported, int maxSupported, GetVersionOptions options) { + trace.add("getVersion with options"); + return next.getVersion(changeID, minSupported, maxSupported, options); + } + @Override public void continueAsNew( Optional workflowType, Optional options, Object[] args) {