Skip to content

Commit 6b032af

Browse files
committed
feat: enhance compatibility and static analysis for testing
- Added `testing/src` to `psalm.xml` for better coverage. - Unified OS constants using `OperatingSystem` across mappings. - Improved handling of environment variables and type safety in `SystemInfo` and `Environment`. - Refactored `RoadRunnerActivityInvocationCache` and `WorkerMock` for cleaner interfaces. - Adjusted `Downloader` to use a working directory for asset management.
1 parent f04dd4f commit 6b032af

16 files changed

Lines changed: 145 additions & 76 deletions

psalm-baseline.xml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,9 +1146,6 @@
11461146
<code><![CDATA[$cacheName]]></code>
11471147
<code><![CDATA[$host]]></code>
11481148
</ArgumentTypeCoercion>
1149-
<MissingParamType>
1150-
<code><![CDATA[$value]]></code>
1151-
</MissingParamType>
11521149
<PossiblyUndefinedStringArrayOffset>
11531150
<code><![CDATA[$request->getOptions()['name']]]></code>
11541151
</PossiblyUndefinedStringArrayOffset>
@@ -1350,9 +1347,6 @@
13501347
<PossiblyUndefinedStringArrayOffset>
13511348
<code><![CDATA[$headers[self::HEADER_TASK_QUEUE]]]></code>
13521349
</PossiblyUndefinedStringArrayOffset>
1353-
<PropertyNotSetInConstructor>
1354-
<code><![CDATA[$codec]]></code>
1355-
</PropertyNotSetInConstructor>
13561350
<UndefinedInterfaceMethod>
13571351
<code><![CDATA[dispatch]]></code>
13581352
<code><![CDATA[dispatch]]></code>
@@ -1371,4 +1365,25 @@
13711365
<code><![CDATA[getUpdateContext]]></code>
13721366
</UndefinedInterfaceMethod>
13731367
</file>
1368+
<file src="testing/src/Downloader.php">
1369+
<PossiblyUndefinedStringArrayOffset>
1370+
<code><![CDATA[$asset['browser_download_url']]]></code>
1371+
<code><![CDATA[$response->toArray()['assets']]]></code>
1372+
</PossiblyUndefinedStringArrayOffset>
1373+
</file>
1374+
<file src="testing/src/Replay/WorkflowReplayer.php">
1375+
<UndefinedMethod>
1376+
<code><![CDATA[getWorkflowType]]></code>
1377+
</UndefinedMethod>
1378+
</file>
1379+
<file src="testing/src/TestService.php">
1380+
<UndefinedClass>
1381+
<code><![CDATA[ChannelCredentials]]></code>
1382+
</UndefinedClass>
1383+
</file>
1384+
<file src="testing/src/WorkerMock.php">
1385+
<DeprecatedMethod>
1386+
<code><![CDATA[registerActivityImplementations]]></code>
1387+
</DeprecatedMethod>
1388+
</file>
13741389
</files>

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
>
1818
<projectFiles>
1919
<directory name="src" />
20+
<directory name="testing/src" />
2021
<ignoreFiles>
2122
<directory name="vendor" />
2223
</ignoreFiles>

src/Worker/ActivityInvocationCache/RoadRunnerActivityInvocationCache.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function clear(): void
4141
$this->cache->clear();
4242
}
4343

44-
public function saveCompletion(string $activityMethodName, $value): void
44+
public function saveCompletion(string $activityMethodName, mixed $value): void
4545
{
4646
$this->cache->set($activityMethodName, ActivityInvocationResult::fromValue($value, $this->dataConverter));
4747
}

src/WorkerFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public function __construct(
111111
?ServiceCredentials $credentials = null,
112112
) {
113113
$this->converter = $dataConverter;
114+
$this->codec = $this->createCodec();
114115
$this->boot($credentials ?? ServiceCredentials::create());
115116
}
116117

@@ -190,7 +191,6 @@ public function getEnvironment(): EnvironmentInterface
190191
public function run(?HostConnectionInterface $host = null): int
191192
{
192193
$host ??= RoadRunner::create();
193-
$this->codec = $this->createCodec();
194194

195195
while ($msg = $host->waitBatch()) {
196196
try {

testing/src/ActivityMocker.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ public function clear(): void
2121
$this->cache->clear();
2222
}
2323

24-
public function expectCompletion(string $activityMethodName, $value): void
24+
/**
25+
* @param non-empty-string $activityMethodName
26+
*/
27+
public function expectCompletion(string $activityMethodName, mixed $value): void
2528
{
2629
$this->cache->saveCompletion($activityMethodName, $value);
2730
}
2831

32+
/**
33+
* @param non-empty-string $activityMethodName
34+
*/
2935
public function expectFailure(string $activityMethodName, \Throwable $error): void
3036
{
3137
$this->cache->saveFailure($activityMethodName, $error);

testing/src/Command.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ final class Command
1212
/** @var non-empty-string|null Temporal Address */
1313
public ?string $address = null;
1414

15-
/** @var non-empty-string|null */
1615
public ?string $tlsKey = null;
1716

18-
/** @var non-empty-string|null */
1917
public ?string $tlsCert = null;
2018

21-
private array $xdebug;
19+
private array $xdebug = [];
2220

21+
/**
22+
* @param non-empty-string|null $address
23+
*/
2324
public function __construct(
2425
?string $address = null,
2526
) {
@@ -28,9 +29,11 @@ public function __construct(
2829

2930
public static function fromEnv(): self
3031
{
31-
$self = new self(\getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233');
32+
$address = \getenv('TEMPORAL_ADDRESS');
33+
$self = new self((is_string($address) && $address !== '') ? $address : '127.0.0.1:7233');
3234

33-
$self->namespace = \getenv('TEMPORAL_NAMESPACE') ?: 'default';
35+
$namespace = \getenv('TEMPORAL_NAMESPACE');
36+
$self->namespace = (is_string($namespace) && $namespace !== '') ? $namespace : 'default';
3437
$self->xdebug = [
3538
'xdebug.mode' => \ini_get('xdebug.mode'),
3639
'xdebug.start_with_request' => \ini_get('xdebug.start_with_request'),
@@ -70,13 +73,17 @@ public static function fromCommandLine(array $argv): self
7073
break;
7174
}
7275
}
76+
if (empty($address)) {
77+
throw new \InvalidArgumentException('Address cannot be empty');
78+
}
79+
if (empty($namespace)) {
80+
throw new \InvalidArgumentException('Namespace cannot be empty');
81+
}
7382

7483
$self = new self($address);
75-
7684
$self->namespace = $namespace;
7785
$self->tlsCert = $tlsCert;
7886
$self->tlsKey = $tlsKey;
79-
8087
return $self;
8188
}
8289

testing/src/Downloader.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ final class Downloader
1515
private Filesystem $filesystem;
1616
private HttpClientInterface $httpClient;
1717
private string $javaSdkUrl;
18+
private string $workingDir;
1819

1920
public function __construct(
2021
Filesystem $filesystem,
@@ -27,15 +28,23 @@ public function __construct(
2728
$javaSdkVersion === self::TAG_LATEST => self::TAG_LATEST,
2829
default => "tags/$javaSdkVersion",
2930
};
31+
32+
$workingDir = \getcwd();
33+
if ($workingDir === false) {
34+
throw new \RuntimeException('Failed to get current working directory.');
35+
}
36+
37+
$this->workingDir = $workingDir;
3038
}
3139

3240
public function download(SystemInfo $systemInfo): void
3341
{
3442
$asset = $this->getAsset($systemInfo);
43+
/** @var string $assetUrl */
3544
$assetUrl = $asset['browser_download_url'];
3645
$pathToExtractedAsset = $this->downloadAsset($assetUrl);
3746

38-
$targetPath = \getcwd() . DIRECTORY_SEPARATOR . $systemInfo->temporalServerExecutable;
47+
$targetPath = $this->workingDir . DIRECTORY_SEPARATOR . $systemInfo->temporalServerExecutable;
3948
$this->filesystem->copy($pathToExtractedAsset . DIRECTORY_SEPARATOR . $systemInfo->temporalServerExecutable, $targetPath);
4049
$this->filesystem->chmod($targetPath, 0755);
4150
$this->filesystem->remove($pathToExtractedAsset);
@@ -78,7 +87,7 @@ private function findAsset(array $assets, SystemInfo $systemInfo): array
7887
private function downloadAsset(string $assetUrl): string
7988
{
8089
$response = $this->httpClient->request('GET', $assetUrl);
81-
$assetPath = \getcwd() . DIRECTORY_SEPARATOR . \basename($assetUrl);
90+
$assetPath = $this->workingDir . DIRECTORY_SEPARATOR . \basename($assetUrl);
8291

8392
if ($this->filesystem->exists($assetPath)) {
8493
$this->filesystem->remove($assetPath);
@@ -87,9 +96,9 @@ private function downloadAsset(string $assetUrl): string
8796
$this->filesystem->appendToFile($assetPath, $response->getContent());
8897

8998
$phar = new \PharData($assetPath);
90-
$extractedPath = \getcwd() . DIRECTORY_SEPARATOR . $phar->getFilename();
99+
$extractedPath = $this->workingDir . DIRECTORY_SEPARATOR . $phar->getFilename();
91100
if (!$this->filesystem->exists($extractedPath)) {
92-
$phar->extractTo(\getcwd());
101+
$phar->extractTo($this->workingDir);
93102
}
94103
$this->filesystem->remove($phar->getPath());
95104

testing/src/Environment.php

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,19 @@ public function __construct(
3636

3737
public static function create(?Command $command = null): self
3838
{
39-
$token = \getenv('GITHUB_TOKEN');
40-
4139
$systemInfo = SystemInfo::detect();
42-
\is_string(\getenv('ROADRUNNER_BINARY')) and $systemInfo->rrExecutable = \getenv('ROADRUNNER_BINARY');
40+
$roadRunnerBinary = \getenv('ROADRUNNER_BINARY');
41+
if (\is_string($roadRunnerBinary)) {
42+
$systemInfo->rrExecutable = $roadRunnerBinary;
43+
}
44+
45+
$token = \getenv('GITHUB_TOKEN');
4346

4447
return new self(
4548
new TestOutputStyle(new ArgvInput(), new ConsoleOutput()),
4649
new Downloader(new Filesystem(), HttpClient::create([
4750
'headers' => [
48-
'authorization' => $token ? 'token ' . $token : null,
51+
'authorization' => \is_string($token) ? 'token ' . $token : null,
4952
],
5053
])),
5154
$systemInfo,
@@ -122,7 +125,7 @@ public function startTemporalServer(
122125
$this->io->info('Running command: ' . $this->serializeProcess($this->temporalServerProcess));
123126
$this->temporalServerProcess->start();
124127

125-
$deadline = \microtime(true) + $commandTimeout;
128+
$deadline = \microtime(true) + (float) $commandTimeout;
126129
while (!$temporalStarted && \microtime(true) < $deadline) {
127130
\usleep(10_000);
128131
$check = new Process([
@@ -162,7 +165,7 @@ public function startTemporalTestServer(int $commandTimeout = 10): void
162165
$this->io->info('Temporal test server downloaded.');
163166
}
164167

165-
$temporalPort = \parse_url($this->command->address, PHP_URL_PORT);
168+
$temporalPort = \parse_url((string) $this->command->address, PHP_URL_PORT);
166169

167170
$this->io->info('Starting Temporal test server... ');
168171
$this->temporalTestServerProcess = new Process(
@@ -192,7 +195,7 @@ public function startTemporalTestServer(int $commandTimeout = 10): void
192195
/**
193196
* @param array<string, mixed> $envs
194197
*/
195-
public function startRoadRunner(?string $rrCommand = null, int $commandTimeout = 10, array $envs = [], string $configFile = '.rr.yaml'): void
198+
public function startRoadRunner(?array $rrCommand = null, int $commandTimeout = 10, array $envs = [], string $configFile = '.rr.yaml'): void
196199
{
197200
if (!$this->isTemporalRunning() && !$this->isTemporalTestRunning()) {
198201
$this->io->error([
@@ -202,7 +205,7 @@ public function startRoadRunner(?string $rrCommand = null, int $commandTimeout =
202205
}
203206

204207
$this->roadRunnerProcess = new Process(
205-
command: $rrCommand ? \explode(' ', $rrCommand) : [$this->systemInfo->rrExecutable, 'serve'],
208+
command: $rrCommand ?? [$this->systemInfo->rrExecutable, 'serve'],
206209
env: $envs,
207210
);
208211
$this->roadRunnerProcess->setTimeout($commandTimeout);
@@ -213,7 +216,7 @@ public function startRoadRunner(?string $rrCommand = null, int $commandTimeout =
213216
$this->roadRunnerProcess->start();
214217

215218
// wait for roadrunner to start
216-
$deadline = \microtime(true) + $commandTimeout;
219+
$deadline = \microtime(true) + (float) $commandTimeout;
217220
while (!$roadRunnerStarted && \microtime(true) < $deadline) {
218221
\usleep(10_000);
219222
$check = new Process([$this->systemInfo->rrExecutable, 'workers', '-c', $configFile]);
@@ -289,26 +292,38 @@ public function stopRoadRunner(): void
289292
}
290293
}
291294

295+
/**
296+
* @psalm-assert Process $this->temporalServerProcess
297+
*/
292298
public function isTemporalRunning(): bool
293299
{
294300
return $this->temporalServerProcess?->isRunning() === true;
295301
}
296302

303+
/**
304+
* @psalm-assert Process $this->roadRunnerProcess
305+
*/
297306
public function isRoadRunnerRunning(): bool
298307
{
299308
return $this->roadRunnerProcess?->isRunning() === true;
300309
}
301310

311+
/**
312+
* @psalm-assert Process $this->temporalTestServerProcess
313+
*/
302314
public function isTemporalTestRunning(): bool
303315
{
304316
return $this->temporalTestServerProcess?->isRunning() === true;
305317
}
306318

307-
private function serializeProcess(?Process $temporalServerProcess): string|array
319+
private function serializeProcess(?Process $process): string
308320
{
309-
$reflection = new \ReflectionClass($temporalServerProcess);
321+
if ($process === null) {
322+
return 'process is not started';
323+
}
324+
$reflection = new \ReflectionClass($process);
310325
$reflectionProperty = $reflection->getProperty('commandline');
311-
$commandLine = $reflectionProperty->getValue($temporalServerProcess);
326+
$commandLine = $reflectionProperty->getValue($process);
312327
return \implode(' ', $commandLine);
313328
}
314329
}

testing/src/Replay/WorkflowReplayer.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ final class WorkflowReplayer
3434

3535
public function __construct()
3636
{
37-
$this->rpc = new RPC(Relay::create(Environment::fromGlobals()->getRPCAddress()), new ProtobufCodec());
37+
$rpcAddress = Environment::fromGlobals()->getRPCAddress();
38+
$this->rpc = new RPC(Relay::create(!empty($rpcAddress) ? $rpcAddress : 'tcp://127.0.0.1:6001'), new ProtobufCodec());
3839
}
3940

4041
/**
@@ -44,7 +45,6 @@ public function __construct()
4445
*/
4546
public function replayHistory(History $history): void
4647
{
47-
/** @var HistoryEvent|null $firstEvent */
4848
$firstEvent = $history->getEvents()[0] ?? null;
4949
$workflowType = $firstEvent?->getWorkflowExecutionStartedEventAttributes()?->getWorkflowType()?->getName()
5050
?? throw new \LogicException('History is empty or broken.');
@@ -110,7 +110,10 @@ public function replayFromJSON(
110110
$this->sendRequest('temporal.ReplayFromJSON', $request);
111111
}
112112

113-
private function sendRequest(string $command, Message $request): ReplayResponse
113+
/**
114+
* @param non-empty-string $command
115+
*/
116+
private function sendRequest(string $command, Message $request): void
114117
{
115118
$wfType = (string) $request->getWorkflowType()?->getName();
116119
try {
@@ -132,7 +135,7 @@ private function sendRequest(string $command, Message $request): ReplayResponse
132135
\assert($status !== null);
133136

134137
if ($status->getCode() === 0) {
135-
return $message;
138+
return;
136139
}
137140

138141
throw match ($status->getCode()) {

0 commit comments

Comments
 (0)