diff --git a/composer.lock b/composer.lock index 9f2c85b..0196760 100644 --- a/composer.lock +++ b/composer.lock @@ -1130,12 +1130,12 @@ "source": { "type": "git", "url": "https://github.com/php-school/php-workshop.git", - "reference": "6f461ac93816ddedf17d7a2b3af20f9afcd42b23" + "reference": "73febf8c62d982cd74896df9266522dc2a5153b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-school/php-workshop/zipball/6f461ac93816ddedf17d7a2b3af20f9afcd42b23", - "reference": "6f461ac93816ddedf17d7a2b3af20f9afcd42b23", + "url": "https://api.github.com/repos/php-school/php-workshop/zipball/73febf8c62d982cd74896df9266522dc2a5153b5", + "reference": "73febf8c62d982cd74896df9266522dc2a5153b5", "shasum": "" }, "require": { @@ -1211,7 +1211,7 @@ "issues": "https://github.com/php-school/php-workshop/issues", "source": "https://github.com/php-school/php-workshop/tree/docker-fixes" }, - "time": "2024-05-26T20:19:31+00:00" + "time": "2024-09-04T11:22:50+00:00" }, { "name": "php-school/terminal", diff --git a/src/Exercise/TimeServer.php b/src/Exercise/TimeServer.php index ae1dada..ad5ee12 100644 --- a/src/Exercise/TimeServer.php +++ b/src/Exercise/TimeServer.php @@ -10,7 +10,6 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\ComparisonFailure; use PhpSchool\PhpWorkshop\Result\Failure; @@ -31,71 +30,101 @@ public function getDescription(): string public function defineListeners(EventDispatcher $eventDispatcher): void { - $appendArgsListener = function (CliExecuteEvent $event) { - $event->appendArg('127.0.0.1'); - $event->appendArg($this->getRandomPort()); - }; + $referencePort = $this->getRandomPort(); + $studentPort = $this->getRandomPort(); + + $eventDispatcher->listen( + 'cli.verify.reference-execute.pre', + function (CliExecuteEvent $event) use ($referencePort) { + $event->appendArg('0.0.0.0'); + $event->appendArg((string) $referencePort); + $event->getScenario()->exposePort($referencePort); + } + ); + $eventDispatcher->listen( + ['cli.verify.student-execute.pre', 'cli.run.student-execute.pre'], + function (CliExecuteEvent $event) use ($studentPort) { + $event->appendArg('0.0.0.0'); + $event->appendArg((string) $studentPort); + $event->getScenario()->exposePort($studentPort); + } + ); - $eventDispatcher->listen('cli.verify.reference-execute.pre', $appendArgsListener); - $eventDispatcher->listen('cli.verify.student-execute.pre', $appendArgsListener); - $eventDispatcher->listen('cli.run.student-execute.pre', $appendArgsListener); + $eventDispatcher->listen( + 'cli.verify.reference.executing', + function (CliExecuteEvent $event) use ($referencePort) { + //wait for server to boot + sleep(1); - $eventDispatcher->listen('cli.verify.reference.executing', function (CliExecuteEvent $event) { - $args = $event->getArgs()->getArrayCopy(); + $socket = $this->createSocket(); + @socket_connect($socket, '0.0.0.0', $referencePort); + @socket_read($socket, 2048, PHP_NORMAL_READ); - //wait for server to boot - usleep(100000); + socket_close($socket); - $socket = $this->createSocket(); - socket_connect($socket, $args[0], (int) $args[1]); - socket_read($socket, 2048, PHP_NORMAL_READ); + //wait for shutdown + usleep(100000); + } + ); - //wait for shutdown - usleep(100000); - }); + $eventDispatcher->insertVerifier( + 'cli.verify.student.executing', + function (CliExecuteEvent $event) use ($studentPort) { + //wait for server to boot + sleep(1); - $eventDispatcher->insertVerifier('cli.verify.student.executing', function (CliExecuteEvent $event) { - $args = $event->getArgs()->getArrayCopy(); + $socket = $this->createSocket(); - //wait for server to boot - usleep(100000); + $result = @socket_connect($socket, '0.0.0.0', $studentPort); - $socket = $this->createSocket(); - $connectResult = @socket_connect($socket, $args[0], (int) $args[1]); - - if (!$connectResult) { - return Failure::fromNameAndReason($this->getName(), sprintf( - "Client returns an error (number %d): Connection refused while trying to join tcp://127.0.0.1:%d.", - socket_last_error($socket), - $args[1] - )); - } + if (!$result) { + $error = "Client returns an error (number %d): Connection refused "; + $error .= "while trying to join tcp://0.0.0.0:%d."; - $out = (string) socket_read($socket, 2048, PHP_NORMAL_READ); + return Failure::fromNameAndReason($this->getName(), sprintf( + $error, + socket_last_error($socket), + $studentPort + )); + } - //wait for shutdown - usleep(100000); + $out = (string) socket_read($socket, 2048, PHP_NORMAL_READ); + + socket_close($socket); - $date = new \DateTime(); + //wait for shutdown + usleep(100000); - //match the current date but any seconds - //since we can't mock time in PHP easily - if (!preg_match(sprintf('/^%s:([0-5][0-9]|60)\n$/', $date->format('Y-m-d H:i')), $out)) { - return ComparisonFailure::fromNameAndValues($this->getName(), $date->format("Y-m-d H:i:s\n"), $out); + $date = new \DateTime(); + + //match the current date but any seconds + //since we can't mock time in PHP easily + if (!preg_match(sprintf('/^%s:([0-5][0-9]|60)\n$/', $date->format('Y-m-d H:i')), $out)) { + return ComparisonFailure::fromNameAndValues($this->getName(), $date->format("Y-m-d H:i:s\n"), $out); + } + return new Success($this->getName()); } - return new Success($this->getName()); - }); + ); - $eventDispatcher->listen('cli.run.student.executing', function (CliExecuteEvent $event) { + $eventDispatcher->listen('cli.run.student.executing', function (CliExecuteEvent $event) use ($studentPort) { /** @var OutputInterface $output */ $output = $event->getParameter('output'); - $args = $event->getArgs()->getArrayCopy(); //wait for server to boot - usleep(100000); + sleep(1); $socket = $this->createSocket(); - socket_connect($socket, $args[0], (int) $args[1]); + try { + $connectResult = @socket_connect($socket, '0.0.0.0', $studentPort); + } catch (\ErrorException $e) { + $output->write('Cannot connect'); + return; + } + + if (false === $connectResult) { + $output->write('Cannot connect'); + return; + } $out = (string) socket_read($socket, 2048, PHP_NORMAL_READ); //wait for shutdown @@ -105,9 +134,18 @@ public function defineListeners(EventDispatcher $eventDispatcher): void }); } - private function getRandomPort(): string + private function getRandomPort(): int { - return (string) mt_rand(1025, 65535); + $sock = socket_create_listen(0); + + if ($sock === false) { + throw new RuntimeException('Cannot create socket'); + } + + socket_getsockname($sock, $addr, $port); + socket_close($sock); + + return $port; } public function getType(): ExerciseType @@ -117,7 +155,8 @@ public function getType(): ExerciseType public function defineTestScenario(): CliScenario { - return (new CliScenario())->withExecution(); + return (new CliScenario()) + ->withExecution(); } private function createSocket(): Socket @@ -128,6 +167,8 @@ private function createSocket(): Socket throw new RuntimeException('Cannot create socket'); } + socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ["sec" => 5, "usec" => 0]); + return $socket; } } diff --git a/test/Exercise/TimeServerTest.php b/test/Exercise/TimeServerTest.php index cf21995..ea976d6 100644 --- a/test/Exercise/TimeServerTest.php +++ b/test/Exercise/TimeServerTest.php @@ -48,14 +48,8 @@ public function testFailureWhenCannotConnect(): void $this->assertVerifyWasNotSuccessful(); - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $reason = '/^Client returns an error \(number \d+\): No connection could be made because'; - $reason .= ' the target machine actively refused it\.\r\n'; - $reason .= ' while trying to join tcp:\/\/127\.0\.0\.1:\d+\.$/'; - } else { - $reason = '/^Client returns an error \(number \d+\): Connection refused'; - $reason .= ' while trying to join tcp:\/\/127\.0\.0\.1:\d+\.$/'; - } + $reason = '/^Client returns an error \(number \d+\): Connection refused'; + $reason .= ' while trying to join tcp:\/\/0\.0\.0\.0:\d+\.$/'; $this->assertResultsHasFailureAndMatches(Failure::class, function (Failure $failure) use ($reason) { $this->assertMatchesRegularExpression($reason, $failure->getReason());