diff --git a/.github/workflows/run-test-suite.yml b/.github/workflows/run-test-suite.yml index b375cf06c..66ab0dde0 100644 --- a/.github/workflows/run-test-suite.yml +++ b/.github/workflows/run-test-suite.yml @@ -95,6 +95,8 @@ jobs: - name: Run tests run: ${{ inputs.test-command }} + env: + XDEBUG_MODE: off - name: Check for failures if: steps.validate.outcome == 'failure' diff --git a/src/Workflow/WorkflowExecutionInfo.php b/src/Workflow/WorkflowExecutionInfo.php index d214bd081..48def583a 100644 --- a/src/Workflow/WorkflowExecutionInfo.php +++ b/src/Workflow/WorkflowExecutionInfo.php @@ -96,4 +96,29 @@ public function __construct( */ public readonly string $firstRunId, ) {} + + public function __debugInfo(): array + { + return [ + 'execution' => $this->execution, + 'type' => $this->type, + 'startTime' => $this->startTime, + 'closeTime' => $this->closeTime, + 'status' => $this->status, + 'historyLength' => $this->historyLength, + 'parentNamespaceId' => $this->parentNamespaceId, + 'parentExecution' => $this->parentExecution, + 'executionTime' => $this->executionTime, + // 'memo' => $this->memo, + // 'searchAttributes' => $this->searchAttributes, + 'autoResetPoints' => $this->autoResetPoints, + 'taskQueue' => $this->taskQueue, + 'stateTransitionCount' => $this->stateTransitionCount, + 'historySizeBytes' => $this->historySizeBytes, + 'mostRecentWorkerVersionStamp' => $this->mostRecentWorkerVersionStamp, + 'executionDuration' => $this->executionDuration, + 'rootExecution' => $this->rootExecution, + 'firstRunId' => $this->firstRunId, + ]; + } } diff --git a/testing/README.md b/testing/README.md index 0adcecab2..ab125de16 100644 --- a/testing/README.md +++ b/testing/README.md @@ -139,7 +139,7 @@ workflow has a timer, the server doesn't wait for it and continues immediately. this behaviour you can use `TestService` class: ```php -$testService = TestService::create('localhost:7233'); +$testService = TestService::create('127.0.0.1:7233'); $testService->lockTimeSkipping(); // ... @@ -217,7 +217,7 @@ final class SimpleWorkflowTestCase extends TestCase protected function setUp(): void { - $this->workflowClient = new WorkflowClient(ServiceClient::create('localhost:7233')); + $this->workflowClient = new WorkflowClient(ServiceClient::create('127.0.0.1:7233')); $this->activityMocks = new ActivityMocker(); parent::setUp(); diff --git a/testing/src/Command.php b/testing/src/Command.php index 1ed70d88d..7d9ff42c1 100644 --- a/testing/src/Command.php +++ b/testing/src/Command.php @@ -9,8 +9,10 @@ final class Command /** @var non-empty-string|null Temporal Namespace */ public ?string $namespace = null; - /** @var non-empty-string|null Temporal Address */ - public ?string $address = null; + /** + * @var non-empty-string Temporal Address + */ + public string $address; /** @var non-empty-string|null */ public ?string $tlsKey = null; @@ -20,12 +22,17 @@ final class Command private array $xdebug; + public function __construct( + string $address, + ) { + $this->address = $address; + } + public static function fromEnv(): self { - $self = new self(); + $self = new self(\getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233'); $self->namespace = \getenv('TEMPORAL_NAMESPACE') ?: 'default'; - $self->address = \getenv('TEMPORAL_ADDRESS') ?: 'localhost:7233'; $self->xdebug = [ 'xdebug.mode' => \ini_get('xdebug.mode'), 'xdebug.start_with_request' => \ini_get('xdebug.start_with_request'), @@ -42,30 +49,35 @@ public static function fromEnv(): self */ public static function fromCommandLine(array $argv): self { - $self = new self(); - - \array_shift($argv); // remove the script name (worker.php or runner.php) - foreach ($argv as $chunk) { - if (\str_starts_with($chunk, 'namespace=')) { - $self->namespace = \substr($chunk, 10); - continue; - } - - if (\str_starts_with($chunk, 'address=')) { - $self->address = \substr($chunk, 8); - continue; + $address = ''; + $namespace = ''; + $tlsCert = null; + $tlsKey = null; + + // remove the script name (worker.php or runner.php) + $chunks = \array_slice($argv, 1); + foreach ($chunks as $chunk) { + switch (true) { + case \str_starts_with($chunk, 'namespace='): + $namespace = \substr($chunk, 10); + break; + case \str_starts_with($chunk, 'address='): + $address = \substr($chunk, 8); + break; + case \str_starts_with($chunk, 'tls.cert='): + $tlsCert = \substr($chunk, 9); + break; + case \str_starts_with($chunk, 'tls.key='): + $tlsKey = \substr($chunk, 8); + break; } + } - if (\str_starts_with($chunk, 'tls.cert=')) { - $self->tlsCert = \substr($chunk, 9); - continue; - } + $self = new self($address); - if (\str_starts_with($chunk, 'tls.key=')) { - $self->tlsKey = \substr($chunk, 8); - continue; - } - } + $self->namespace = $namespace; + $self->tlsCert = $tlsCert; + $self->tlsKey = $tlsKey; return $self; } diff --git a/testing/src/Environment.php b/testing/src/Environment.php index e4391a98f..919707436 100644 --- a/testing/src/Environment.php +++ b/testing/src/Environment.php @@ -4,54 +4,54 @@ namespace Temporal\Testing; +use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Process\Process; use Temporal\Common\SearchAttributes\ValueType; +use Temporal\Testing\Support\TestOutputStyle; final class Environment { - private Downloader $downloader; - private Output $output; - private SystemInfo $systemInfo; private ?Process $temporalTestServerProcess = null; private ?Process $temporalServerProcess = null; private ?Process $roadRunnerProcess = null; - public function __construct(Output $output, Downloader $downloader, SystemInfo $systemInfo) - { - $this->downloader = $downloader; - $this->systemInfo = $systemInfo; - $this->output = $output; - } + public function __construct( + private SymfonyStyle $io, + private Downloader $downloader, + private SystemInfo $systemInfo, + public readonly Command $command, + ) {} - public static function create(): self + public static function create(?Command $command = null): self { $token = \getenv('GITHUB_TOKEN'); - $info = SystemInfo::detect(); - \is_string(\getenv('ROADRUNNER_BINARY')) and $info->rrExecutable = \getenv('ROADRUNNER_BINARY'); + $systemInfo = SystemInfo::detect(); + \is_string(\getenv('ROADRUNNER_BINARY')) and $systemInfo->rrExecutable = \getenv('ROADRUNNER_BINARY'); return new self( - new ConsoleOutput(), + new TestOutputStyle(new ArgvInput(), new ConsoleOutput()), new Downloader(new Filesystem(), HttpClient::create([ 'headers' => [ 'authorization' => $token ? 'token ' . $token : null, ], ])), - $info, + $systemInfo, + $command ?? Command::fromEnv(), ); } /** * @param array $envs */ - public function start(?string $rrCommand = null, int $commandTimeout = 10, array $envs = []): void + public function start(string $roadRunnerConfigFile, ?array $rrCommand = null, int $commandTimeout = 10, array $envs = []): void { $this->startTemporalTestServer($commandTimeout); - $this->startRoadRunner($rrCommand, $commandTimeout, $envs); + $this->startRoadRunner($roadRunnerConfigFile, $rrCommand, $commandTimeout, $envs); } /** @@ -64,7 +64,8 @@ public function startTemporalServer( array $parameters = [], array $searchAttributes = [], ): void { - $temporalPort = \parse_url(\getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233', PHP_URL_PORT); + $temporalHost = \parse_url($this->command->address, PHP_URL_HOST); + $temporalPort = \parse_url($this->command->address, PHP_URL_PORT); // Add search attributes foreach ($searchAttributes as $name => $type) { @@ -91,44 +92,66 @@ public function startTemporalServer( }; } - $this->output->write('Starting Temporal test server... '); + $this->io->info('Starting Temporal server... '); $this->temporalServerProcess = new Process( [ $this->systemInfo->temporalCliExecutable, "server", "start-dev", "--port", $temporalPort, '--log-level', 'error', - '--headless', + '--ip', $temporalHost, + // '--headless', ...$parameters, ], ); $this->temporalServerProcess->setTimeout($commandTimeout); + $temporalStarted = false; + $this->io->info('Running command: ' . $this->serializeProcess($this->temporalServerProcess)); $this->temporalServerProcess->start(); - $deadline = \microtime(true) + 1.2; - while (!$this->temporalServerProcess->isRunning() && \microtime(true) < $deadline) { + $deadline = \microtime(true) + $commandTimeout; + while (!$temporalStarted && \microtime(true) < $deadline) { \usleep(10_000); + $check = new Process([ + $this->systemInfo->temporalCliExecutable, + 'operator', + 'cluster', + 'health', + '--address', $this->command->address, + ]); + $check->run(); + if (\str_contains($check->getOutput(), 'SERVING')) { + $temporalStarted = true; + } } - if (!$this->temporalServerProcess->isRunning()) { - $this->output->writeln('error'); - $this->output->writeln('Error starting Temporal server: ' . $this->temporalServerProcess->getErrorOutput()); + if (!$temporalStarted || !$this->temporalServerProcess->isRunning()) { + $this->io->error([ + \sprintf( + 'Error starting Temporal server: %s.', + !$temporalStarted ? "Health check failed" : $this->temporalServerProcess->getErrorOutput(), + ), + \sprintf( + 'Command: `%s`.', + $this->serializeProcess($this->temporalServerProcess), + ), + ]); exit(1); } - $this->output->writeln('done.'); + $this->io->info('Temporal server started.'); } public function startTemporalTestServer(int $commandTimeout = 10): void { if (!$this->downloader->check($this->systemInfo->temporalServerExecutable)) { - $this->output->write('Download temporal test server... '); + $this->io->info('Download Temporal test server... '); $this->downloader->download($this->systemInfo); - $this->output->writeln('done.'); + $this->io->info('Temporal test server downloaded.'); } - $temporalPort = \parse_url(\getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233', PHP_URL_PORT); + $temporalPort = \parse_url($this->command->address, PHP_URL_PORT); - $this->output->write('Starting Temporal test server... '); + $this->io->info('Starting Temporal test server... '); $this->temporalTestServerProcess = new Process( [$this->systemInfo->temporalServerExecutable, $temporalPort, '--enable-time-skipping'], ); @@ -138,79 +161,83 @@ public function startTemporalTestServer(int $commandTimeout = 10): void \sleep(1); if (!$this->temporalTestServerProcess->isRunning()) { - $this->output->writeln('error'); - $this->output->writeln('Error starting Temporal Test server: ' . $this->temporalTestServerProcess->getErrorOutput()); + $this->io->error([ + \sprintf( + 'Error starting Temporal Test server: %s.', + $this->temporalTestServerProcess->getErrorOutput(), + ), + \sprintf( + 'Command: `%s`.', + $this->serializeProcess($this->temporalTestServerProcess), + ), + ]); exit(1); } - $this->output->writeln('done.'); + $this->io->info('Temporal Test server started.'); } /** * @param array $envs */ - public function startRoadRunner(?string $rrCommand = null, int $commandTimeout = 10, array $envs = []): void + public function startRoadRunner(string $configFile, ?array $parameters = null, int $commandTimeout = 10, array $envs = []): void { + if (!$this->isTemporalRunning() && !$this->isTemporalTestRunning()) { + $this->io->error([ + 'Temporal server is not running. Please start it before starting RoadRunner.', + ]); + exit(1); + } $this->roadRunnerProcess = new Process( - command: $rrCommand ? \explode(' ', $rrCommand) : [$this->systemInfo->rrExecutable, 'serve'], + command: [ + $this->systemInfo->rrExecutable, + "serve", + '-c', $configFile, + ...$parameters, + ], env: $envs, ); $this->roadRunnerProcess->setTimeout($commandTimeout); - $this->output->write('Starting RoadRunner... '); + $this->io->info('Starting RoadRunner... '); $roadRunnerStarted = false; - $this->roadRunnerProcess->start(static function ($type, $output) use (&$roadRunnerStarted): void { - if ($type === Process::OUT && \str_contains($output, 'RoadRunner server started')) { - $roadRunnerStarted = true; - } - }); - - if (!$this->roadRunnerProcess->isRunning()) { - $this->output->writeln('error'); - $this->output->writeln('Error starting RoadRunner: ' . $this->roadRunnerProcess->getErrorOutput()); - exit(1); - } + $this->io->info('Running command: ' . $this->serializeProcess($this->roadRunnerProcess)); + $this->roadRunnerProcess->start(); // wait for roadrunner to start - $ticks = $commandTimeout * 10; - while (!$roadRunnerStarted && $ticks > 0) { - $this->roadRunnerProcess->getStatus(); - \usleep(100000); - --$ticks; + $deadline = \microtime(true) + $commandTimeout; + while (!$roadRunnerStarted && \microtime(true) < $deadline) { + \usleep(10_000); + $check = new Process([$this->systemInfo->rrExecutable, 'workers', '-c', $configFile]); + $check->run(); + if (\str_contains($check->getOutput(), 'Workers of')) { + $roadRunnerStarted = true; + } } if (!$roadRunnerStarted) { - $this->output->writeln('error'); - $this->output->writeln('Error starting RoadRunner: ' . $this->roadRunnerProcess->getErrorOutput()); + $this->io->error(\sprintf( + 'Failed to start until RoadRunner is ready. Status: "%s". Stderr: "%s". Stdout: "%s".', + $this->roadRunnerProcess->getStatus(), + $this->roadRunnerProcess->getErrorOutput(), + $this->roadRunnerProcess->getOutput(), + )); + $this->io->writeln(\sprintf( + "Command: `%s`.", + $this->serializeProcess($this->roadRunnerProcess), + )); exit(1); } - $this->output->writeln('done.'); + $this->io->info('RoadRunner server started.'); } public function stop(): void { - if ($this->temporalServerProcess !== null && $this->temporalServerProcess->isRunning()) { - $this->output->write('Stopping Temporal server... '); - $this->temporalServerProcess->stop(); - $this->output->writeln('done.'); - } - - if ($this->temporalTestServerProcess !== null && $this->temporalTestServerProcess->isRunning()) { - $this->output->write('Stopping Temporal Test server... '); - $this->temporalTestServerProcess->stop(); - $this->output->writeln('done.'); - } - - if ($this->roadRunnerProcess !== null && $this->roadRunnerProcess->isRunning()) { - $this->output->write('Stopping RoadRunner... '); - $this->roadRunnerProcess->stop(); - $this->output->writeln('done.'); - } + $this->stopRoadRunner(); + $this->stopTemporalTestServer(); + $this->stopTemporalServer(); } - /** - * @internal - */ public function executeTemporalCommand(array|string $command, int $timeout = 10): void { $command = \array_merge( @@ -222,4 +249,57 @@ public function executeTemporalCommand(array|string $command, int $timeout = 10) $process->setTimeout($timeout); $process->run(); } + + public function stopTemporalServer(): void + { + if ($this->isTemporalRunning()) { + $this->io->info('Stopping Temporal server... '); + $this->temporalServerProcess->stop(); + $this->temporalServerProcess = null; + $this->io->info('Temporal server stopped.'); + } + } + + public function stopTemporalTestServer(): void + { + if ($this->isTemporalTestRunning()) { + $this->io->info('Stopping Temporal Test server... '); + $this->temporalTestServerProcess->stop(); + $this->temporalTestServerProcess = null; + $this->io->info('Temporal Test server stopped.'); + } + } + + public function stopRoadRunner(): void + { + if ($this->isRoadRunnerRunning()) { + $this->io->info('Stopping RoadRunner... '); + $this->roadRunnerProcess->stop(); + $this->roadRunnerProcess = null; + $this->io->info('RoadRunner server stopped.'); + } + } + + public function isTemporalRunning(): bool + { + return $this->temporalServerProcess?->isRunning() === true; + } + + public function isRoadRunnerRunning(): bool + { + return $this->roadRunnerProcess?->isRunning() === true; + } + + public function isTemporalTestRunning(): bool + { + return $this->temporalTestServerProcess?->isRunning() === true; + } + + private function serializeProcess(?Process $temporalServerProcess): string|array + { + $reflection = new \ReflectionClass($temporalServerProcess); + $reflectionProperty = $reflection->getProperty('commandline'); + $commandLine = $reflectionProperty->getValue($temporalServerProcess); + return \implode(' ', $commandLine); + } } diff --git a/testing/src/Support/TestOutputStyle.php b/testing/src/Support/TestOutputStyle.php new file mode 100644 index 000000000..09f4591fd --- /dev/null +++ b/testing/src/Support/TestOutputStyle.php @@ -0,0 +1,15 @@ +block($message, null, 'fg=green', '', false, false); + } +} diff --git a/tests/.rr.yaml b/tests/.rr.yaml index 112c1ab3d..0048694bd 100644 --- a/tests/.rr.yaml +++ b/tests/.rr.yaml @@ -10,7 +10,7 @@ server: # Workflow and activity mesh service temporal: - address: "localhost:7233" + address: "127.0.0.1:7233" activities: num_workers: 4 debug_level: 2 diff --git a/tests/Acceptance/.rr.yaml b/tests/Acceptance/.rr.yaml index 2271a06fc..dbe2cd601 100644 --- a/tests/Acceptance/.rr.yaml +++ b/tests/Acceptance/.rr.yaml @@ -7,7 +7,7 @@ server: # Workflow and activity mesh service temporal: - address: ${TEMPORAL_ADDRESS:-localhost:7233} + address: ${TEMPORAL_ADDRESS:-127.0.0.1:7233} namespace: ${TEMPORAL_NAMESPACE:-default} activities: num_workers: 2 diff --git a/tests/Acceptance/App/Feature/ClientFactory.php b/tests/Acceptance/App/Feature/ClientFactory.php index d645f3e30..94bb66b6f 100644 --- a/tests/Acceptance/App/Feature/ClientFactory.php +++ b/tests/Acceptance/App/Feature/ClientFactory.php @@ -82,9 +82,7 @@ public function workflowClient(\ReflectionParameter $context): WorkflowClientInt options: (new ClientOptions())->withNamespace($runtime->namespace), converter: $converter, interceptorProvider: $pipelineProvider, - )->withTimeout(5); - - $attribute->timeout === null or $client = $client->withTimeout($attribute->timeout); + )->withTimeout($attribute->timeout ?? 5); return $client; } diff --git a/tests/Acceptance/App/Feature/WorkflowStubInjector.php b/tests/Acceptance/App/Feature/WorkflowStubInjector.php index cc639b7b1..cb29c4283 100644 --- a/tests/Acceptance/App/Feature/WorkflowStubInjector.php +++ b/tests/Acceptance/App/Feature/WorkflowStubInjector.php @@ -55,20 +55,22 @@ public function createInjection( // Wait 5 seconds for the workflow to start $deadline = \microtime(true) + 5; - checkStart: - $description = $run->describe(); - if ($description->info->historyLength <= 2) { - if (\microtime(true) < $deadline) { - goto checkStart; + while (true) { + $description = $run->describe(); + if ($description->info->historyLength > 2) { + break; } - throw new \RuntimeException( - \sprintf( - 'Workflow %s did not start. TaskQueue: %s', - $attribute->type, - $feature->taskQueue, - ), - ); + if (\microtime(true) >= $deadline) { + throw new \RuntimeException( + \sprintf( + 'Workflow %s did not start. WorkflowOptions: %s. WorkflowInfo: %s', + $attribute->type, + \json_encode($options, JSON_PRETTY_PRINT), + \print_r($description->info, true), + ), + ); + } } return $stub; diff --git a/tests/Acceptance/App/Runtime/RRStarter.php b/tests/Acceptance/App/Runtime/RRStarter.php index 5169560fc..b5b96cfde 100644 --- a/tests/Acceptance/App/Runtime/RRStarter.php +++ b/tests/Acceptance/App/Runtime/RRStarter.php @@ -9,28 +9,24 @@ final class RRStarter { - private Environment $environment; - private bool $started = false; - public function __construct( private State $runtime, + private Environment $environment, ) { - $this->environment = Environment::create(); \register_shutdown_function(fn() => $this->stop()); } public function start(): void { - if ($this->started) { + if ($this->environment->isRoadRunnerRunning()) { return; } - $sysInfo = SystemInfo::detect(); $run = $this->runtime->command; - $rrCommand = [ - $this->runtime->workDir . DIRECTORY_SEPARATOR . $sysInfo->rrExecutable, - 'serve', + $configFile = $this->runtime->rrConfigDir . DIRECTORY_SEPARATOR . '.rr.yaml'; + + $parameters = [ '-w', $this->runtime->rrConfigDir, '-o', @@ -38,6 +34,8 @@ public function start(): void '-o', "temporal.address={$this->runtime->address}", '-o', + "temporal.activities.num_workers={$this->runtime->activityWorkers}", + '-o', 'server.command=' . \implode(',', [ PHP_BINARY, ...$run->getPhpBinaryArguments(), @@ -45,24 +43,15 @@ public function start(): void ...$run->getCommandLineArguments(), ]), ]; - $run->tlsKey === null or $rrCommand = [...$rrCommand, '-o', "tls.key={$run->tlsKey}"]; - $run->tlsCert === null or $rrCommand = [...$rrCommand, '-o', "tls.cert={$run->tlsCert}"]; - $command = \implode(' ', $rrCommand); + $run->tlsKey === null or $parameters = [...$parameters, '-o', "tls.key={$run->tlsKey}"]; + $run->tlsCert === null or $parameters = [...$parameters, '-o', "tls.cert={$run->tlsCert}"]; - // echo "\e[1;36mStart RoadRunner with command:\e[0m {$command}\n"; - $this->environment->startRoadRunner($command); - $this->started = true; + $this->environment->startRoadRunner($configFile, $parameters); } public function stop(): void { - if (!$this->started) { - return; - } - - // echo "\e[1;36mStop RoadRunner\e[0m\n"; - $this->environment->stop(); - $this->started = false; + $this->environment->stopRoadRunner(); } public function __destruct() diff --git a/tests/Acceptance/App/Runtime/State.php b/tests/Acceptance/App/Runtime/State.php index c4b14d662..1becbc3ee 100644 --- a/tests/Acceptance/App/Runtime/State.php +++ b/tests/Acceptance/App/Runtime/State.php @@ -28,9 +28,10 @@ public function __construct( public readonly string $rrConfigDir, public readonly string $workDir, public readonly iterable $testCasesDir, + public readonly int $activityWorkers, ) { $this->namespace = $command->namespace ?? 'default'; - $this->address = $command->address ?? 'localhost:7233'; + $this->address = $command->address; } /** diff --git a/tests/Acceptance/App/Runtime/TemporalStarter.php b/tests/Acceptance/App/Runtime/TemporalStarter.php index 7ef1a530b..4c52c34a2 100644 --- a/tests/Acceptance/App/Runtime/TemporalStarter.php +++ b/tests/Acceptance/App/Runtime/TemporalStarter.php @@ -10,18 +10,16 @@ final class TemporalStarter { - private Environment $environment; - private bool $started = false; - - public function __construct() + public function __construct( + private Environment $environment, + ) { - $this->environment = Environment::create(); \register_shutdown_function(fn() => $this->stop()); } public function start(): void { - if ($this->started) { + if ($this->environment->isTemporalRunning()) { return; } @@ -47,28 +45,13 @@ public function start(): void 'testDatetime' => ValueType::Datetime, ], ); - $this->started = true; - } - - public function executeTemporalCommand(array|string $command, int $timeout = 10): void - { - $this->environment->executeTemporalCommand( - command: $command, - timeout: $timeout, - ); } /** * @return bool Returns true if the server was stopped successfully, false if it was not started. */ - public function stop(): bool + public function stop(): void { - if (!$this->started) { - return false; - } - $this->environment->stop(); - $this->started = false; - return true; } } diff --git a/tests/Acceptance/App/RuntimeBuilder.php b/tests/Acceptance/App/RuntimeBuilder.php index be5b7d526..005b1905c 100644 --- a/tests/Acceptance/App/RuntimeBuilder.php +++ b/tests/Acceptance/App/RuntimeBuilder.php @@ -48,18 +48,18 @@ public static function hydrateClasses(State $runtime): void * @param non-empty-string $workDir * @param iterable $testCasesDir */ - public static function createEmpty(Command $command, string $workDir, iterable $testCasesDir): State + public static function createEmpty(Command $command, string $workDir, iterable $testCasesDir, int $workers = 1): State { - return new State($command, \dirname(__DIR__), $workDir, $testCasesDir); + return new State($command, \dirname(__DIR__), $workDir, $testCasesDir, $workers); } /** * @param non-empty-string $workDir * @param iterable $testCasesDir */ - public static function createState(Command $command, string $workDir, iterable $testCasesDir): State + public static function createState(Command $command, string $workDir, iterable $testCasesDir, int $workers = 1): State { - $runtime = new State($command, \dirname(__DIR__), $workDir, $testCasesDir); + $runtime = new State($command, \dirname(__DIR__), $workDir, $testCasesDir, $workers); self::hydrateClasses($runtime); diff --git a/tests/Acceptance/App/TestCase.php b/tests/Acceptance/App/TestCase.php index 2268777fe..7f3c726ff 100644 --- a/tests/Acceptance/App/TestCase.php +++ b/tests/Acceptance/App/TestCase.php @@ -20,6 +20,7 @@ use Temporal\Tests\Acceptance\App\Runtime\Feature; use Temporal\Tests\Acceptance\App\Runtime\RRStarter; use Temporal\Tests\Acceptance\App\Runtime\State; +use Temporal\Tests\Acceptance\App\Runtime\TemporalStarter; abstract class TestCase extends \Temporal\Tests\TestCase { @@ -90,10 +91,9 @@ function (Container $container): mixed { if (!$e instanceof SkippedTest) { // Restart RR if a Error occurs - /** @var RRStarter $runner */ - $runner = $container->get(RRStarter::class); - $runner->stop(); - $runner->start(); + $roadRunnerStarter = $container->get(RRStarter::class); + $roadRunnerStarter->stop(); + $roadRunnerStarter->start(); } throw $e; diff --git a/tests/Acceptance/Extra/Stability/ResetWorkerTest.php b/tests/Acceptance/Extra/Stability/ResetWorkerTest.php index 583de204b..b6bd51810 100644 --- a/tests/Acceptance/Extra/Stability/ResetWorkerTest.php +++ b/tests/Acceptance/Extra/Stability/ResetWorkerTest.php @@ -31,19 +31,20 @@ public function resetWithCancel( 'Extra_Stability_ResetWorker', WorkflowOptions::new() ->withTaskQueue($feature->taskQueue) - ->withWorkflowExecutionTimeout(20), + ->withWorkflowExecutionTimeout(10), ); - # Start the Workflow with a 10-second timer - $client->start($stub, 16); + # Start the Workflow with a 5-second timer + $client->start($stub, 5); # Query the Workflow to kill the Worker try { - $stub->query('die'); + $stub->query('sleepAndDie', 1); self::fail('Query must fail with a timeout'); } catch (WorkflowServiceException $e) { # Should fail with a timeout - self::assertInstanceOf(TimeoutException::class, $e->getPrevious()); + $previous = $e->getPrevious(); + self::assertInstanceOf(TimeoutException::class, $previous); } # Cancel Workflow @@ -51,13 +52,18 @@ public function resetWithCancel( try { # Workflow must be canceled - $stub->getResult(timeout: 12); + $result = $stub->getResult(timeout: 5); + self::fail( + \sprintf( + "Workflow must fail with a canceled failure, got: %s", + $result, + ), + ); } catch (WorkflowFailedException $e) { - self::assertInstanceOf(CanceledFailure::class, $e->getPrevious()); + $previous = $e->getPrevious(); + self::assertInstanceOf(CanceledFailure::class, $previous); return; } - - self::fail('Workflow must fail with a canceled failure'); } #[Test] @@ -71,29 +77,33 @@ public function resetWithSignal( 'Extra_Stability_ResetWorker', WorkflowOptions::new() ->withTaskQueue($feature->taskQueue) - ->withWorkflowExecutionTimeout(20), + ->withWorkflowExecutionTimeout(10), ); - # Start the Workflow with a 10-second timer - $client->start($stub, 16); + # Start the Workflow with a 5-second timer + $client->start($stub, 5); # Query the Workflow to kill the Worker try { - $stub->query('die'); + $stub->query('sleepAndDie', 1); self::fail('Query must fail with a timeout'); } catch (WorkflowServiceException $e) { # Should fail with a timeout - self::assertInstanceOf(TimeoutException::class, $e->getPrevious()); + $previous = $e->getPrevious(); + self::assertInstanceOf(TimeoutException::class, $previous); } $stub->signal('exit'); try { # Workflow must be canceled - $result = $stub->getResult(timeout: 16); + $result = $stub->getResult(timeout: 5); self::assertSame('Signal', $result); } catch (\Throwable) { - $this->fail('Workflow must finish successfully and no timeout must be thrown'); + $this->fail(\sprintf( + 'Workflow must finish successfully and no timeout must be thrown, got: %s.', + $e, + )); } # Check that Side Effect was not lost @@ -118,15 +128,15 @@ class TestWorkflow #[WorkflowMethod('Extra_Stability_ResetWorker')] #[ReturnType(Type::TYPE_STRING)] - public function expire(int $seconds = 10): \Generator + public function expire(int $seconds): \Generator { $isTimer = ! yield Workflow::awaitWithTimeout($seconds, fn(): bool => $this->exit); return yield $isTimer ? 'Timer' : 'Signal'; } - #[Workflow\QueryMethod('die')] - public function die(int $sleep = 2): void + #[Workflow\QueryMethod('sleepAndDie')] + public function sleepAndDie(int $sleep): void { \sleep($sleep); exit(1); diff --git a/tests/Acceptance/Extra/Versioning/DeploymentTest.php b/tests/Acceptance/Extra/Versioning/DeploymentTest.php index 7662b2231..1e780b5fb 100644 --- a/tests/Acceptance/Extra/Versioning/DeploymentTest.php +++ b/tests/Acceptance/Extra/Versioning/DeploymentTest.php @@ -11,8 +11,10 @@ use Temporal\Common\Versioning\VersioningBehavior; use Temporal\Common\Versioning\VersioningOverride; use Temporal\Common\Versioning\WorkerDeploymentVersion; +use Temporal\Testing\Environment; use Temporal\Tests\Acceptance\App\Attribute\Worker; use Temporal\Tests\Acceptance\App\Runtime\Feature; +use Temporal\Tests\Acceptance\App\Runtime\RRStarter; use Temporal\Tests\Acceptance\App\Runtime\TemporalStarter; use Temporal\Tests\Acceptance\App\TestCase; use Temporal\Worker\WorkerDeploymentOptions; @@ -26,11 +28,15 @@ class DeploymentTest extends TestCase { #[Test] public function defaultBehaviorAuto( + Environment $environment, + RRStarter $roadRunnerStarter, TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, ): void { $behavior = self::executeWorkflow( + $environment, + $roadRunnerStarter, $starter, $client, $feature, @@ -43,12 +49,16 @@ public function defaultBehaviorAuto( #[Test] public function customBehaviorPinned( + Environment $environment, + RRStarter $roadRunnerStarter, TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, ): void { $id = Uuid::v4(); self::executeWorkflow( + $environment, + $roadRunnerStarter, $starter, $client, $feature, @@ -72,12 +82,16 @@ public function customBehaviorPinned( #[Test] public function versionBehaviorOverrideAutoUpgrade( + Environment $environment, + RRStarter $roadRunnerStarter, TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, ): void { $id = Uuid::v4(); self::executeWorkflow( + $environment, + $roadRunnerStarter, $starter, $client, $feature, @@ -101,11 +115,15 @@ public function versionBehaviorOverrideAutoUpgrade( #[Test] public function versionBehaviorOverridePinned( + Environment $environment, + RRStarter $roadRunnerStarter, TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, ): void { $behavior = self::executeWorkflow( + $environment, + $roadRunnerStarter, $starter, $client, $feature, @@ -127,14 +145,16 @@ public function versionBehaviorOverridePinned( * @param null|callable(VersioningBehavior): void $postAction */ private static function executeWorkflow( - TemporalStarter $starter, + Environment $environment, + RRStarter $roadRunnerStarter, + TemporalStarter $temporalStarter, WorkflowClientInterface $client, Feature $feature, string $workflowType, WorkflowOptions $options, ?callable $postAction = null, ): ?VersioningBehavior { - WorkerFactory::setCurrentDeployment($starter); + WorkerFactory::setCurrentDeployment($environment); try { # Create a Workflow stub with an execution timeout 12 seconds @@ -176,8 +196,9 @@ private static function executeWorkflow( $postAction === null or $postAction($behavior); return $behavior; } finally { - $starter->stop() and $starter->start(); - sleep(5); + $temporalStarter->stop(); + $temporalStarter->start(); + $roadRunnerStarter->start(); } } } @@ -198,14 +219,15 @@ public static function options(): WorkerOptions ); } - public static function setCurrentDeployment(TemporalStarter $starter): void + public static function setCurrentDeployment(Environment $environment): void { - $starter->executeTemporalCommand([ + $environment->executeTemporalCommand([ 'worker', 'deployment', 'set-current-version', '--deployment-name', WorkerFactory::DEPLOYMENT_NAME, '--build-id', WorkerFactory::BUILD_ID, + '--address', $environment->command->address, '--yes', ], timeout: 5); } diff --git a/tests/Acceptance/Harness/Query/TimeoutDueToNoActiveWorkersTest.php b/tests/Acceptance/Harness/Query/TimeoutDueToNoActiveWorkersTest.php index de9d45dc5..0e42d8644 100644 --- a/tests/Acceptance/Harness/Query/TimeoutDueToNoActiveWorkersTest.php +++ b/tests/Acceptance/Harness/Query/TimeoutDueToNoActiveWorkersTest.php @@ -22,13 +22,13 @@ class TimeoutDueToNoActiveWorkersTest extends TestCase { #[Test] public static function check( - #[Client(timeout: 30)] + #[Client(timeout: 10)] #[Stub('Harness_Query_TimeoutDueToNoActiveWorkers')] WorkflowStubInterface $stub, - RRStarter $runner, + RRStarter $roadRunnerStarter, ): void { # Stop worker - $runner->stop(); + $roadRunnerStarter->stop(); try { $stub->query('simple_query')?->getValue(0); @@ -44,7 +44,7 @@ public static function check( ], 'Error code must be DEADLINE_EXCEEDED or CANCELLED. Got ' . \print_r($status, true)); } finally { # Restart the worker and finish the wf - $runner->start(); + $roadRunnerStarter->start(); $stub->signal('finish'); $stub->getResult(); } diff --git a/tests/Acceptance/Harness/Update/SelfTest.php b/tests/Acceptance/Harness/Update/SelfTest.php index c44f479fa..b4de3ea15 100644 --- a/tests/Acceptance/Harness/Update/SelfTest.php +++ b/tests/Acceptance/Harness/Update/SelfTest.php @@ -22,7 +22,8 @@ class SelfTest extends TestCase public static function check( #[Stub('Harness_Update_Self')]WorkflowStubInterface $stub, ): void { - self::assertSame('Hello, world!', $stub->getResult()); + $result = $stub->getResult(); + self::assertSame('Hello, world!', $result); } } @@ -36,7 +37,7 @@ public function run() { yield Workflow::executeActivity( 'result', - options: ActivityOptions::new()->withStartToCloseTimeout(2) + options: ActivityOptions::new()->withStartToCloseTimeout(10), ); yield Workflow::await(fn(): bool => $this->done); @@ -61,9 +62,10 @@ public function __construct( #[ActivityMethod('result')] public function result(): void { - $this->client->newUntypedRunningWorkflowStub( + $workflowStub = $this->client->newUntypedRunningWorkflowStub( workflowID: Activity::getInfo()->workflowExecution->getID(), workflowType: Activity::getInfo()->workflowType->name, - )->update('my_update'); + ); + $workflowStub->update('my_update'); } } diff --git a/tests/Acceptance/Harness/Update/WorkerRestartTest.php b/tests/Acceptance/Harness/Update/WorkerRestartTest.php index 08bd20f70..2875bc30f 100644 --- a/tests/Acceptance/Harness/Update/WorkerRestartTest.php +++ b/tests/Acceptance/Harness/Update/WorkerRestartTest.php @@ -14,6 +14,7 @@ use Temporal\Client\WorkflowStubInterface; use Temporal\Tests\Acceptance\App\Attribute\Stub; use Temporal\Tests\Acceptance\App\Runtime\RRStarter; +use Temporal\Tests\Acceptance\App\Runtime\TemporalStarter; use Temporal\Tests\Acceptance\App\TestCase; use Temporal\Workflow; use Temporal\Workflow\WorkflowInterface; @@ -29,7 +30,7 @@ class WorkerRestartTest extends TestCase public static function check( #[Stub('Harness_Update_WorkerRestart')]WorkflowStubInterface $stub, ContainerInterface $c, - RRStarter $runner, + RRStarter $roadRunnerStarter, ): void { $handle = $stub->startUpdate('do_activities'); @@ -45,8 +46,8 @@ public static function check( } while (true); # Restart the worker. - $runner->stop(); - $runner->start(); + $roadRunnerStarter->stop(); + $roadRunnerStarter->start(); # Unblocks the activity. $c->get(StorageInterface::class)->set(KV_ACTIVITY_BLOCKED, false); diff --git a/tests/Acceptance/bootstrap.php b/tests/Acceptance/bootstrap.php index 09faf6bed..c9de373ac 100644 --- a/tests/Acceptance/bootstrap.php +++ b/tests/Acceptance/bootstrap.php @@ -19,6 +19,7 @@ use Temporal\DataConverter\DataConverter; use Temporal\DataConverter\DataConverterInterface; use Temporal\Testing\Command; +use Temporal\Testing\Environment; use Temporal\Tests\Acceptance\App\Feature\WorkflowStubInjector; use Temporal\Tests\Acceptance\App\Runtime\ContainerFacade; use Temporal\Tests\Acceptance\App\Runtime\RRStarter; @@ -36,11 +37,12 @@ $runtime = RuntimeBuilder::createEmpty($command, \getcwd(), [ 'Temporal\Tests\Acceptance\Harness' => __DIR__ . '/Harness', 'Temporal\Tests\Acceptance\Extra' => __DIR__ . '/Extra', -]); +], workers: (int) (\getenv('ACTIVITY_WORKERS') ?: 2)); # Run RoadRunner and Temporal -$temporalRunner = new TemporalStarter(); -$rrRunner = new RRStarter($runtime); +$environment = Environment::create($command); +$temporalRunner = new TemporalStarter($environment); +$rrRunner = new RRStarter($runtime, $environment); $temporalRunner->start(); $rrRunner->start(); @@ -59,7 +61,7 @@ try { $serviceClient->getConnection()->connect(5); echo "\e[1;32mOK\e[0m\n"; -} catch (\Throwable $e) { +} catch (Throwable $e) { echo "\e[1;31mFAILED\e[0m\n"; Support::echoException($e); return; @@ -86,6 +88,7 @@ $container->bindSingleton(State::class, $runtime); $container->bindSingleton(RRStarter::class, $rrRunner); $container->bindSingleton(TemporalStarter::class, $temporalRunner); +$container->bindSingleton(Environment::class, $environment); $container->bindSingleton(ServiceClientInterface::class, $serviceClient); $container->bindSingleton(WorkflowClientInterface::class, $workflowClient); $container->bindSingleton(ScheduleClientInterface::class, $scheduleClient); @@ -94,5 +97,5 @@ $container->bind(RPCInterface::class, static fn() => RPC::create(\getenv('RR_RPC_ADDRESS') ?: 'tcp://127.0.0.1:6001')); $container->bind( StorageInterface::class, - static fn(#[Proxy] ContainerInterface $c): StorageInterface => $c->get(Factory::class)->select('harness'), + static fn(#[Proxy] ContainerInterface $container): StorageInterface => $container->get(Factory::class)->select('harness'), ); diff --git a/tests/Acceptance/worker.php b/tests/Acceptance/worker.php index 15f2f9582..f8a3e2c6c 100644 --- a/tests/Acceptance/worker.php +++ b/tests/Acceptance/worker.php @@ -44,7 +44,7 @@ $runtime = RuntimeBuilder::createState($command, \getcwd(), [ 'Temporal\Tests\Acceptance\Harness' => __DIR__ . '/Harness', 'Temporal\Tests\Acceptance\Extra' => __DIR__ . '/Extra', - ]); + ], workers: (int) (\getenv('ACTIVITY_WORKERS') ?: 2)); $run = $runtime->command; // Init container $container = new Spiral\Core\Container(); diff --git a/tests/Functional/.rr.silent.yaml b/tests/Functional/.rr.silent.yaml index 5c8c91067..c8aa584fd 100644 --- a/tests/Functional/.rr.silent.yaml +++ b/tests/Functional/.rr.silent.yaml @@ -8,7 +8,7 @@ server: # Workflow and activity mesh service temporal: - address: "localhost:7233" + address: "127.0.0.1:7233" activities: num_workers: 1 diff --git a/tests/Functional/Client/AbstractClient.php b/tests/Functional/Client/AbstractClient.php index 6342207ed..6734159bd 100644 --- a/tests/Functional/Client/AbstractClient.php +++ b/tests/Functional/Client/AbstractClient.php @@ -28,7 +28,7 @@ abstract class AbstractClient extends AbstractFunctional * @param string $connection * @return WorkflowClient */ - protected function createClient(string $connection = 'localhost:7233'): WorkflowClient + protected function createClient(string $connection = '127.0.0.1:7233'): WorkflowClient { return new WorkflowClient( ServiceClient::create($connection) diff --git a/tests/Functional/Client/TypedStubTestCase.php b/tests/Functional/Client/TypedStubTestCase.php index 13e3052c5..21abcc458 100644 --- a/tests/Functional/Client/TypedStubTestCase.php +++ b/tests/Functional/Client/TypedStubTestCase.php @@ -130,8 +130,8 @@ public function testGetDTOResult() public function testVoidReturnType() { - $w = $this->createClient(); - $dto = $w->newWorkflowStub(ActivityReturnTypeWorkflow::class); + $client = $this->createClient(); + $dto = $client->newWorkflowStub(ActivityReturnTypeWorkflow::class); $this->assertEquals( 100, diff --git a/tests/Functional/HistoryLengthWorkflowTestCase.php b/tests/Functional/HistoryLengthWorkflowTestCase.php index c8a5b91db..153aa426c 100644 --- a/tests/Functional/HistoryLengthWorkflowTestCase.php +++ b/tests/Functional/HistoryLengthWorkflowTestCase.php @@ -18,7 +18,7 @@ final class HistoryLengthWorkflowTestCase extends TestCase protected function setUp(): void { $this->workflowClient = new WorkflowClient( - ServiceClient::create('localhost:7233') + ServiceClient::create('127.0.0.1:7233') ); $this->activityMocks = new ActivityMocker(); diff --git a/tests/Functional/Interceptor/AbstractClient.php b/tests/Functional/Interceptor/AbstractClient.php index 5853062b8..042a9eae7 100644 --- a/tests/Functional/Interceptor/AbstractClient.php +++ b/tests/Functional/Interceptor/AbstractClient.php @@ -26,7 +26,7 @@ abstract class AbstractClient extends AbstractFunctional * @param string $connection * @return WorkflowClient */ - protected function createClient(string $connection = 'localhost:7233'): WorkflowClient + protected function createClient(string $connection = '127.0.0.1:7233'): WorkflowClient { return new WorkflowClient( ServiceClient::create($connection), diff --git a/tests/Functional/NamedArgumentsTestCase.php b/tests/Functional/NamedArgumentsTestCase.php index 9584b8616..0a5e40251 100644 --- a/tests/Functional/NamedArgumentsTestCase.php +++ b/tests/Functional/NamedArgumentsTestCase.php @@ -23,7 +23,7 @@ final class NamedArgumentsTestCase extends TestCase protected function setUp(): void { $this->workflowClient = new WorkflowClient( - ServiceClient::create('localhost:7233') + ServiceClient::create('127.0.0.1:7233') ); parent::setUp(); diff --git a/tests/Functional/SimpleWorkflowTestCase.php b/tests/Functional/SimpleWorkflowTestCase.php index 19b6901f5..dfbeec033 100644 --- a/tests/Functional/SimpleWorkflowTestCase.php +++ b/tests/Functional/SimpleWorkflowTestCase.php @@ -130,7 +130,7 @@ public function testWorkflowMethodInAbstractParent(): void protected function setUp(): void { $this->workflowClient = new WorkflowClient( - ServiceClient::create('localhost:7233'), + ServiceClient::create('127.0.0.1:7233'), ); $this->activityMocks = new ActivityMocker(); diff --git a/tests/Functional/bootstrap.php b/tests/Functional/bootstrap.php index 9726420da..c9b383f18 100644 --- a/tests/Functional/bootstrap.php +++ b/tests/Functional/bootstrap.php @@ -10,16 +10,11 @@ \chdir(__DIR__ . '/../..'); require_once __DIR__ . '/../../vendor/autoload.php'; -$sysInfo = \Temporal\Testing\SystemInfo::detect(); - $command = Command::fromEnv(); $environment = Environment::create(); $environment->startTemporalTestServer(); (new SearchAttributeTestInvoker())(); -$environment->startRoadRunner(\implode(' ', [ - $sysInfo->rrExecutable, - 'serve', - '-c', '.rr.silent.yaml', +$environment->startRoadRunner(__DIR__ . DIRECTORY_SEPARATOR . '.rr.silent.yaml', [ '-w', 'tests/Functional', '-o', 'server.command=' . \implode(',', [ @@ -28,7 +23,7 @@ 'worker.php', ...$command->getCommandLineArguments(), ]), -])); +]); \register_shutdown_function(static fn() => $environment->stop()); diff --git a/tests/runner.php b/tests/runner.php index b8aabb98c..ea387161c 100755 --- a/tests/runner.php +++ b/tests/runner.php @@ -14,11 +14,13 @@ \passthru(\sprintf("%s %s --log-junit=%s 2>&1", PHP_BINARY, $command, $logFile), $code); if (\file_exists($logFile)) { - $xml = \simplexml_load_file($logFile); - $failures = (int) $xml->testsuite['failures'] + (int) $xml->testsuite['errors']; + $xml = @\simplexml_load_file($logFile); + if ($xml !== false) { + $failures = (int)$xml->testsuite['failures'] + (int)$xml->testsuite['errors']; - if ($failures === 0) { - exit(0); + if ($failures === 0) { + exit(0); + } } }