Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions resources/client.meta-storm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,24 @@
argument="0"
>
<collection name="temporal/sdk:workflow-class" argument="0" />
<collection name="temporal/sdk:workflow-type" argument="0" />
<stopCompletion />
</classMethod>
<classMethod
class="\Temporal\Client\Schedule\Action\StartWorkflowAction"
method="new"
argument="0"
>
<collection name="temporal/sdk:workflow-class" />
<collection name="temporal/sdk:workflow-type" argument="0" />
<stopCompletion />
</classMethod>
<classMethod
class="\Temporal\DataConverter\ValuesInterface"
method="getValue"
argument="1"
>
<languageInjection language="InjectablePHP" prefix="/** @var " suffix=" $_ **/" />
</classMethod>
</definitions>
</meta-storm>
7 changes: 7 additions & 0 deletions tests/.meta-storm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<meta-storm>
<definitions>
<classConstructor class="\Temporal\Tests\Acceptance\App\Attribute\Stub" argument="0">
<collection name="temporal/sdk:workflow-type" />
</classConstructor>
</definitions>
</meta-storm>
15 changes: 15 additions & 0 deletions tests/Acceptance/App/Attribute/RetryOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,25 @@ class RetryOptions
* @param int<0, max> $maximumAttempts
*/
public function __construct(
/**
* @see CommonOptions::withInitialInterval()
*/
public ?string $initialInterval = CommonOptions::DEFAULT_INITIAL_INTERVAL,
/**
* @see CommonOptions::withBackoffCoefficient()
*/
public float $backoffCoefficient = CommonOptions::DEFAULT_BACKOFF_COEFFICIENT,
/**
* @see CommonOptions::withMaximumInterval()
*/
public ?string $maximumInterval = CommonOptions::DEFAULT_MAXIMUM_INTERVAL,
/**
* @see CommonOptions::withMaximumAttempts()
*/
public int $maximumAttempts = CommonOptions::DEFAULT_MAXIMUM_ATTEMPTS,
/**
* @see CommonOptions::withNonRetryableExceptions()
*/
public array $nonRetryableExceptions = CommonOptions::DEFAULT_NON_RETRYABLE_EXCEPTIONS,
) {}

Expand Down
15 changes: 15 additions & 0 deletions tests/Acceptance/App/Attribute/Stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,26 @@ final class Stub
*/
public function __construct(
public string $type,
/**
* @see WorkflowOptions::withEagerStart()
*/
public bool $eagerStart = false,
/**
* @see WorkflowOptions::withWorkflowId()
*/
public ?string $workflowId = null,
/**
* @see WorkflowOptions::withWorkflowExecutionTimeout()
*/
public ?string $executionTimeout = null,
public array $args = [],
/**
* @see WorkflowOptions::withMemo()
*/
public array $memo = [],
/**
* @see WorkflowOptions::withRetryOptions()
*/
?RetryOptions $retryOptions = null,
) {
$this->retryOptions = $retryOptions?->toRetryOptions();
Expand Down
33 changes: 16 additions & 17 deletions tests/Acceptance/Harness/Activity/BasicTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,25 @@
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

/*

# Basic activity

The most basic workflow which just runs an activity and returns its result.
Importantly, without setting a workflow execution timeout.

# Detailed spec

It's important that the workflow execution timeout is not set here, because server will propagate that to all un-set
activity timeouts. We had a bug where TS would crash (after proto changes from gogo to google) because it was expecting
timeouts to be set to zero rather than null.

*/

/**
* # Basic activity
*
* The most basic workflow which just runs an activity and returns its result.
* Importantly, without setting a workflow execution timeout.
*
* # Detailed spec
*
* It's important that the workflow execution timeout is not set here, because server will propagate that to all un-set
* activity timeouts. We had a bug where TS would crash (after proto changes from gogo to google) because it was expecting
* timeouts to be set to zero rather than null.
*/
class BasicTest extends TestCase
{
#[Test]
public static function check(#[Stub('Harness_Activity_Basic')]WorkflowStubInterface $stub): void
{
public static function check(
#[Stub('Harness_Activity_Basic')]
WorkflowStubInterface $stub,
): void {
self::assertSame('echo', $stub->getResult());
}
}
Expand Down
77 changes: 36 additions & 41 deletions tests/Acceptance/Harness/Activity/CancelTryCancelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Temporal\Tests\Acceptance\Harness\Activity\CancelTryCancel;

use PHPUnit\Framework\Attributes\Test;
use React\Promise\PromiseInterface;
use Temporal\Activity;
use Temporal\Activity\ActivityInterface;
use Temporal\Activity\ActivityMethod;
Expand All @@ -21,41 +20,41 @@
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

/*

# Activity cancellation - Try Cancel mode
Activities may be cancelled in three different ways, this feature spec covers the
Try Cancel mode.

Each feature workflow in this folder should start an activity and cancel it
using the Try Cancel mode. The implementation should demonstrate that the activity
keeps receives a cancel request after the workflow has issued it, but the workflow
immediately should proceed with the activity result being cancelled.

## Detailed spec

* When the SDK issues the activity cancel request command, server will write an
activity cancel requested event to history
* The workflow immediately resolves the activity with its result being cancelled
* Server will notify the activity cancellation has been requested via a response
to activity heartbeating
* The activity may ignore the cancellation request if it explicitly chooses to

## Feature implementation

* Execute activity that heartbeats and checks cancellation
* If a minute passes without cancellation, send workflow a signal that it timed out
* If cancellation is received, send workflow a signal that it was cancelled
* Cancel activity and confirm cancellation error is returned
* Check in the workflow that the signal sent from the activity is showing it was cancelled

*/

/**
* # Activity cancellation - Try Cancel mode
*
* Activities may be cancelled in three different ways, this feature spec covers the
* Try Cancel mode.
*
* Each feature workflow in this folder should start an activity and cancel it
* using the Try Cancel mode. The implementation should demonstrate that the activity
* keeps receives a cancel request after the workflow has issued it, but the workflow
* immediately should proceed with the activity result being cancelled.
*
* ## Detailed spec
*
* When the SDK issues the activity cancel request command, server will write an
* activity cancel requested event to history
* The workflow immediately resolves the activity with its result being cancelled
* Server will notify the activity cancellation has been requested via a response
* to activity heartbeating
* The activity may ignore the cancellation request if it explicitly chooses to
*
* ## Feature implementation
*
* Execute activity that heartbeats and checks cancellation
* If a minute passes without cancellation, send workflow a signal that it timed out
* If cancellation is received, send workflow a signal that it was cancelled
* Cancel activity and confirm cancellation error is returned
* Check in the workflow that the signal sent from the activity is showing it was cancelled
*/
class CancelTryCancelTest extends TestCase
{
#[Test]
public static function check(#[Stub('Harness_Activity_CancelTryCancel')]WorkflowStubInterface $stub): void
{
public static function check(
#[Stub('Harness_Activity_CancelTryCancel')]
WorkflowStubInterface $stub,
): void {
self::assertSame('cancelled', $stub->getResult(timeout: 10));
}
}
Expand All @@ -76,7 +75,7 @@ public function run()
->withHeartbeatTimeout('5 seconds')
# Disable retry
->withRetryOptions(RetryOptions::new()->withMaximumAttempts(1))
->withCancellationType(Activity\ActivityCancellationType::TryCancel)
->withCancellationType(Activity\ActivityCancellationType::TryCancel),
);

$scope = Workflow::async(static fn() => $activity->cancellableActivity());
Expand All @@ -98,7 +97,7 @@ public function run()
}

#[Workflow\SignalMethod('activity_result')]
public function activityResult(string $result)
public function activityResult(string $result): void
{
$this->result = $result;
}
Expand All @@ -109,14 +108,10 @@ class FeatureActivity
{
public function __construct(
private readonly WorkflowClientInterface $client,
) {
}
) {}

/**
* @return PromiseInterface<null>
*/
#[ActivityMethod('cancellable_activity')]
public function cancellableActivity()
public function cancellableActivity(): void
{
# Heartbeat every second for a minute
$result = 'timeout';
Expand Down
43 changes: 22 additions & 21 deletions tests/Acceptance/Harness/Activity/RetryOnErrorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,46 @@
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

/*

# Retrying activities on error

Failed activities can retry in a number of ways. This is configurable by retry policies that govern if and
how a failed activity may retry.

## Feature implementation

* Workflow executes activity with 5 max attempts and low backoff
* Activity errors every time with the attempt that failed
* Workflow waits on activity and re-bubbles its same error
* Confirm the right attempt error message is present

*/

/**
* # Retrying activities on error
*
* Failed activities can retry in a number of ways. This is configurable by retry policies that govern if and
* how a failed activity may retry.
*
* ## Feature implementation
*
* Workflow executes activity with 5 max attempts and low backoff
* Activity errors every time with the attempt that failed
* Workflow waits on activity and re-bubbles its same error
* Confirm the right attempt error message is present
*/
class RetryOnErrorTest extends TestCase
{
#[Test]
public static function check(#[Stub('Harness_Activity_CancelTryCancel')]WorkflowStubInterface $stub): void
{
public static function check(
#[Stub('Harness_Activity_CancelTryCancel')]
WorkflowStubInterface $stub,
): void {
try {
$stub->getResult();
throw new \Exception('Expected WorkflowFailedException');
} catch (WorkflowFailedException $e) {
self::assertInstanceOf(ActivityFailure::class, $e->getPrevious());
/** @var ActivityFailure $failure */
$failure = $e->getPrevious()->getPrevious();
self::assertInstanceOf(ApplicationFailure::class, $failure);
self::assertStringContainsStringIgnoringCase('activity attempt 5 failed', $failure->getOriginalMessage());
return;
}

throw new \Exception('Expected getResult() produced WorkflowFailedException');
}
}

#[WorkflowInterface]
class FeatureWorkflow
{
#[WorkflowMethod('Harness_Activity_CancelTryCancel')]
public function run()
public function run(): iterable
{
# Allow 4 retries with basically no backoff
yield Workflow::newActivityStub(
Expand All @@ -71,7 +72,7 @@ public function run()
# Do not increase retry backoff each time
->withBackoffCoefficient(1)
# 5 total maximum attempts
->withMaximumAttempts(5)
->withMaximumAttempts(5),
),
)->alwaysFailActivity();
}
Expand Down
28 changes: 16 additions & 12 deletions tests/Acceptance/Harness/ContinueAsNew/ContinueAsSameTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,41 @@
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

\define('INPUT_DATA', 'InputData');
\define('MEMO_KEY', 'MemoKey');
\define('MEMO_VALUE', 'MemoValue');
\define('WORKFLOW_ID', 'TestID');

/**
* # Continues workflow execution
*/
class ContinueAsSameTest extends TestCase
{
private const INPUT_DATA = 'InputData';
private const MEMO_KEY = 'MemoKey';
private const MEMO_VALUE = 'MemoValue';
private const WORKFLOW_ID = 'TestID';

#[Test]
public static function check(
#[Stub(
type: 'Harness_ContinueAsNew_ContinueAsSame',
workflowId: WORKFLOW_ID,
args: [INPUT_DATA],
memo: [MEMO_KEY => MEMO_VALUE],
workflowId: self::WORKFLOW_ID,
args: [self::INPUT_DATA],
memo: [self::MEMO_KEY => self::MEMO_VALUE],
)]
WorkflowStubInterface $stub,
): void {
self::assertSame(INPUT_DATA, $stub->getResult());
self::assertSame(self::INPUT_DATA, $stub->getResult());
# Workflow ID does not change after continue as new
self::assertSame(WORKFLOW_ID, $stub->getExecution()->getID());
self::assertSame(self::WORKFLOW_ID, $stub->getExecution()->getID());
# Memos do not change after continue as new
$description = $stub->describe();
self::assertSame([MEMO_KEY => MEMO_VALUE], $description->info->memo->getValues());
self::assertSame(5, $description->info->historyLength);
self::assertSame([self::MEMO_KEY => self::MEMO_VALUE], $description->info->memo->getValues());
}
}

#[WorkflowInterface]
class FeatureWorkflow
{
#[WorkflowMethod('Harness_ContinueAsNew_ContinueAsSame')]
public function run(string $input)
public function run(string $input): iterable
{
if (!empty(Workflow::getInfo()->continuedExecutionRunId)) {
return $input;
Expand Down
Loading
Loading