Skip to content

Commit 73e6717

Browse files
committed
test: Add stability test with flaky "Persistent store operation failure" and others
1 parent 728df25 commit 73e6717

File tree

2 files changed

+191
-10
lines changed

2 files changed

+191
-10
lines changed

tests/Acceptance/App/TestCase.php

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@ function (Container $container): mixed {
6262
return parent::runTest();
6363
} catch (\Throwable $e) {
6464
if ($e instanceof TemporalException) {
65-
echo "\n=== Workflow history for failed test {$this->name()} ===\n";
66-
$this->printWorkflowHistory($container->get(WorkflowClientInterface::class), $args);
65+
$h = $this->fetchWorkflowHistory($container->get(WorkflowClientInterface::class), $args);
66+
if ($h !== '') {
67+
echo "\n=== Workflow history for failed test {$this->name()} ===\n";
68+
echo $h;
69+
}
6770

6871
$logRecords = $container->get(ClientLogger::class)->getRecords();
6972
if ($logRecords !== []) {
@@ -106,8 +109,12 @@ function (Container $container): mixed {
106109
);
107110
}
108111

109-
private function printWorkflowHistory(WorkflowClientInterface $workflowClient, array $args): void
112+
/**
113+
* Fetch workflow history
114+
*/
115+
private function fetchWorkflowHistory(WorkflowClientInterface $workflowClient, array $args): string
110116
{
117+
$result = '';
111118
foreach ($args as $arg) {
112119
if (!$arg instanceof WorkflowStubInterface) {
113120
continue;
@@ -121,17 +128,17 @@ private function printWorkflowHistory(WorkflowClientInterface $workflowClient, a
121128
$arg->getExecution(),
122129
) as $event) {
123130
$start ??= $fnTime($event->getEventTime());
124-
echo "\n" . \str_pad((string) $event->getEventId(), 3, ' ', STR_PAD_LEFT) . ' ';
131+
$result .= "\n" . \str_pad((string) $event->getEventId(), 3, ' ', STR_PAD_LEFT) . ' ';
125132
# Calculate delta time
126133
$deltaMs = \round(1_000 * ($fnTime($event->getEventTime()) - $start));
127-
echo \str_pad(\number_format($deltaMs, 0, '.', "'"), 6, ' ', STR_PAD_LEFT) . 'ms ';
128-
echo \str_pad(EventType::name($event->getEventType()), 40, ' ', STR_PAD_RIGHT) . ' ';
134+
$result .= \str_pad(\number_format($deltaMs, 0, '.', "'"), 6, ' ', STR_PAD_LEFT) . 'ms ';
135+
$result .= \str_pad(EventType::name($event->getEventType()), 40, ' ', STR_PAD_RIGHT) . ' ';
129136

130137
$cause = $event->getStartChildWorkflowExecutionFailedEventAttributes()?->getCause()
131138
?? $event->getSignalExternalWorkflowExecutionFailedEventAttributes()?->getCause()
132139
?? $event->getRequestCancelExternalWorkflowExecutionFailedEventAttributes()?->getCause();
133140
if ($cause !== null) {
134-
echo "Cause: $cause";
141+
$result .= "Cause: $cause";
135142
continue;
136143
}
137144

@@ -147,12 +154,14 @@ private function printWorkflowHistory(WorkflowClientInterface $workflowClient, a
147154
}
148155

149156
# Render failure
150-
echo "Failure:\n";
151-
echo " ========== BEGIN ===========\n";
157+
$result .= "Failure:\n";
158+
$result .= " ========== BEGIN ===========\n";
152159
$this->renderFailure($failure, 1);
153-
echo " =========== END ============";
160+
$result .= " =========== END ============";
154161
}
155162
}
163+
164+
return $result;
156165
}
157166

158167
private function renderFailure(Failure $failure, int $level): void
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Tests\Acceptance\Extra\Stability\ConcurrencyResetWorker;
6+
7+
use PHPUnit\Framework\Attributes\Test;
8+
use Ramsey\Uuid\Uuid;
9+
use Ramsey\Uuid\UuidInterface;
10+
use Temporal\Api\Common\V1\Payloads;
11+
use Temporal\Client\WorkflowClientInterface;
12+
use Temporal\Client\WorkflowOptions;
13+
use Temporal\Client\WorkflowStubInterface;
14+
use Temporal\DataConverter\DataConverter;
15+
use Temporal\DataConverter\EncodedValues;
16+
use Temporal\DataConverter\Type;
17+
use Temporal\Exception\Client\TimeoutException;
18+
use Temporal\Exception\Client\WorkflowServiceException;
19+
use Temporal\Tests\Acceptance\App\Runtime\Feature;
20+
use Temporal\Tests\Acceptance\App\TestCase;
21+
use Temporal\Workflow;
22+
use Temporal\Workflow\ReturnType;
23+
use Temporal\Workflow\WorkflowMethod;
24+
25+
final class Dto
26+
{
27+
public UuidInterface $parentId;
28+
public string $parentResult;
29+
public UuidInterface $childId;
30+
public string $childResult;
31+
32+
public function __construct()
33+
{
34+
$this->parentId = Uuid::uuid4();
35+
$this->childId = Uuid::uuid4();
36+
37+
$this->parentResult = (string) $this->parentId;
38+
$this->childResult = (string) $this->childId;
39+
}
40+
}
41+
42+
class ConcurrencyResetWorkerTest extends TestCase
43+
{
44+
#[Test]
45+
public function chainOfDeath(
46+
WorkflowClientInterface $client,
47+
Feature $feature,
48+
): void {
49+
$stubs = $dtos = [];
50+
51+
# Start multiple Workflows
52+
for ($i = 0; $i < 10; ++$i) {
53+
$stubs[$i] = self::runAndDie(
54+
client: $client,
55+
feature: $feature,
56+
dto: $dtos[$i] = new Dto(),
57+
die: $i % 3 === 2, // Die every 3rd Workflow
58+
);
59+
}
60+
61+
# Finish all Workflows
62+
foreach ($stubs as $stub) {
63+
/** @see TestWorkflow::exit() */
64+
$stub->signal('exit');
65+
}
66+
67+
# Validate all Workflows
68+
foreach ($stubs as $i => $stub) {
69+
# Assert results
70+
self::checkHistory(
71+
$client,
72+
$stub,
73+
$dtos[$i],
74+
);
75+
}
76+
}
77+
78+
private static function checkHistory(WorkflowClientInterface $client, mixed $stub, Dto $dto): void
79+
{
80+
$found = false;
81+
foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) {
82+
if ($event->hasMarkerRecordedEventAttributes()) {
83+
$record = $event->getMarkerRecordedEventAttributes();
84+
self::assertSame('SideEffect', $record->getMarkerName());
85+
86+
$data = $record->getDetails()['data'];
87+
self::assertInstanceOf(Payloads::class, $data);
88+
$values = EncodedValues::fromPayloads($data, DataConverter::createDefault());
89+
self::assertSame($dto->childResult, $values->getValue(0), 'Side effect value mismatch');
90+
91+
$found = true;
92+
}
93+
94+
# Assert that Workflow has no failures
95+
if ($event->hasWorkflowTaskFailedEventAttributes()) {
96+
self::fail($event->getWorkflowTaskFailedEventAttributes()->getFailure()->getMessage());
97+
}
98+
}
99+
100+
self::assertTrue($found, 'Side Effect must be found in the Workflow history');
101+
self::assertSame($dto->parentResult, $stub->getResult(), 'Workflow result mismatch');
102+
}
103+
104+
private static function runAndDie(
105+
WorkflowClientInterface $client,
106+
Feature $feature,
107+
Dto $dto,
108+
bool $die = true,
109+
): WorkflowStubInterface {
110+
$stub = $client->withTimeout(4)
111+
->newUntypedWorkflowStub(
112+
'Extra_Stability_ConcurrencyResetWorker',
113+
WorkflowOptions::new()
114+
->withTaskQueue($feature->taskQueue)
115+
->withWorkflowExecutionTimeout(20)
116+
->withWorkflowId((string) $dto->parentId),
117+
);
118+
119+
$client->start($stub, $dto);
120+
121+
if ($die) {
122+
$dieStub = $client->withTimeout(0.5)->newUntypedRunningWorkflowStub(
123+
workflowID: (string) $dto->parentId,
124+
workflowType: 'Extra_Stability_ConcurrencyResetWorker',
125+
);
126+
# Query the Workflow to kill the Worker
127+
try {
128+
/** @see TestWorkflow::die() */
129+
$dieStub->query('die');
130+
self::fail('Query must fail with a timeout');
131+
} catch (WorkflowServiceException $e) {
132+
# Should fail with a timeout
133+
trap($e->getPrevious())->if(!$e->getPrevious() instanceof TimeoutException);
134+
// self::assertInstanceOf(TimeoutException::class, $e->getPrevious());
135+
} finally {
136+
unset($dieStub);
137+
}
138+
}
139+
140+
return $stub;
141+
}
142+
}
143+
144+
#[Workflow\WorkflowInterface]
145+
class TestWorkflow
146+
{
147+
private bool $exit = false;
148+
149+
#[WorkflowMethod('Extra_Stability_ConcurrencyResetWorker')]
150+
#[ReturnType(Type::TYPE_STRING)]
151+
public function expire(DTO $dto): \Generator
152+
{
153+
yield Workflow::sideEffect(static fn(): string => $dto->childResult);
154+
yield Workflow::await(fn(): bool => $this->exit);
155+
156+
return $dto->parentResult;
157+
}
158+
159+
#[Workflow\QueryMethod('die')]
160+
public function die(int $sleep = 4): void
161+
{
162+
\sleep($sleep);
163+
exit(1);
164+
}
165+
166+
#[Workflow\SignalMethod('exit')]
167+
public function exit()
168+
{
169+
yield Workflow::uuid7();
170+
$this->exit = true;
171+
}
172+
}

0 commit comments

Comments
 (0)