From b86f3569a5687c660f15768d931ae3f12a2f944a Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Tue, 7 May 2024 22:50:31 +0200 Subject: [PATCH] Use context objects in dispatcher and runners --- app/config.php | 16 +++- src/ExerciseDispatcher.php | 52 ++++------- src/ExerciseRunner/CgiRunner.php | 68 +++++++++------ src/ExerciseRunner/CliRunner.php | 69 ++++++++------- src/ExerciseRunner/CustomVerifyingRunner.php | 20 ++--- .../ExerciseRunnerInterface.php | 9 +- .../Factory/CgiRunnerFactory.php | 5 +- .../Factory/CliRunnerFactory.php | 5 +- test/Asset/CgiExerciseImpl.php | 1 - test/Asset/CliExerciseImpl.php | 2 - test/Check/DatabaseCheckTest.php | 53 ++++++----- test/ExerciseDispatcherTest.php | 61 ++++++------- test/ExerciseRunner/CgiRunnerTest.php | 87 +++++++++---------- test/ExerciseRunner/CliRunnerTest.php | 74 ++++++++-------- .../CustomVerifyingRunnerTest.php | 18 ++-- .../Factory/CgiRunnerFactoryTest.php | 3 +- .../Factory/CliRunnerFactoryTest.php | 3 +- 17 files changed, 265 insertions(+), 281 deletions(-) diff --git a/app/config.php b/app/config.php index 1394abf4..25ae0367 100644 --- a/app/config.php +++ b/app/config.php @@ -38,10 +38,10 @@ use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\ExerciseRenderer; use PhpSchool\PhpWorkshop\ExerciseRepository; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CgiRunnerFactory; use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CliRunnerFactory; use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CustomVerifyingRunnerFactory; -use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\ServerRunnerFactory; use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager; use PhpSchool\PhpWorkshop\Factory\EventDispatcherFactory; use PhpSchool\PhpWorkshop\Factory\MenuFactory; @@ -134,7 +134,7 @@ $c->get(RunnerManager::class), $c->get(ResultAggregator::class), $c->get(EventDispatcher::class), - $c->get(CheckRepository::class) + $c->get(CheckRepository::class), ); }, ResultAggregator::class => create(ResultAggregator::class), @@ -193,8 +193,16 @@ //Exercise Runners RunnerManager::class => function (ContainerInterface $c) { $manager = new RunnerManager(); - $manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class), $c->get(ProcessFactory::class))); - $manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class), $c->get(ProcessFactory::class))); + $manager->addFactory(new CliRunnerFactory( + $c->get(EventDispatcher::class), + $c->get(ProcessFactory::class), + $c->get(EnvironmentManager::class) + )); + $manager->addFactory(new CgiRunnerFactory( + $c->get(EventDispatcher::class), + $c->get(ProcessFactory::class), + $c->get(EnvironmentManager::class) + )); $manager->addFactory(new CustomVerifyingRunnerFactory()); return $manager; }, diff --git a/src/ExerciseDispatcher.php b/src/ExerciseDispatcher.php index 112fd68b..5e9c5b30 100644 --- a/src/ExerciseDispatcher.php +++ b/src/ExerciseDispatcher.php @@ -14,6 +14,7 @@ use PhpSchool\PhpWorkshop\Exception\ExerciseNotConfiguredException; use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; @@ -29,49 +30,26 @@ class ExerciseDispatcher /** * @var array */ - private $checksToRunBefore = []; + private array $checksToRunBefore = []; /** * @var array */ - private $checksToRunAfter = []; + private array $checksToRunAfter = []; - /** - * @var RunnerManager - */ - private $runnerManager; - - /** - * @var ResultAggregator - */ - private $results; - - /** - * @var EventDispatcher - */ - private $eventDispatcher; - - /** - * @var CheckRepository - */ - private $checkRepository; /** * @param RunnerManager $runnerManager Factory capable of building an exercise runner based on the exercise type. - * @param ResultAggregator $resultAggregator + * @param ResultAggregator $results * @param EventDispatcher $eventDispatcher * @param CheckRepository $checkRepository */ public function __construct( - RunnerManager $runnerManager, - ResultAggregator $resultAggregator, - EventDispatcher $eventDispatcher, - CheckRepository $checkRepository + private RunnerManager $runnerManager, + private ResultAggregator $results, + private EventDispatcher $eventDispatcher, + private CheckRepository $checkRepository, ) { - $this->runnerManager = $runnerManager; - $this->results = $resultAggregator; - $this->eventDispatcher = $eventDispatcher; - $this->checkRepository = $checkRepository; } /** @@ -129,6 +107,8 @@ public function requireCheck(string $requiredCheck): void */ public function verify(ExerciseInterface $exercise, Input $input): ResultAggregator { + $context = ExecutionContext::fromInputAndExercise($input, $exercise); + $runner = $this->runnerManager->getRunner($exercise); $exercise->defineListeners($this->eventDispatcher); @@ -143,7 +123,7 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega $this->validateChecks($this->checksToRunAfter, $exercise); foreach ($this->checksToRunBefore as $check) { - $this->results->add($check->check($exercise, $input)); + $this->results->add($check->check($context->getExercise(), $context->getInput())); if (!$this->results->isSuccessful()) { return $this->results; @@ -153,13 +133,13 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.pre.execute', $exercise, $input)); try { - $this->results->add($runner->verify($input)); + $this->results->add($runner->verify($context)); } finally { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.execute', $exercise, $input)); } foreach ($this->checksToRunAfter as $check) { - $this->results->add($check->check($exercise, $input)); + $this->results->add($check->check($context->getExercise(), $context->getInput())); } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.check', $exercise, $input)); @@ -181,11 +161,13 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega */ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output): bool { + $context = ExecutionContext::fromInputAndExercise($input, $exercise); + $exercise->defineListeners($this->eventDispatcher); /** @var PhpLintCheck $lint */ $lint = $this->checkRepository->getByClass(PhpLintCheck::class); - $result = $lint->check($exercise, $input); + $result = $lint->check($context->getExercise(), $context->getInput()); if ($result instanceof FailureInterface) { throw CouldNotRunException::fromFailure($result); @@ -196,7 +178,7 @@ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $ try { $exitStatus = $this->runnerManager ->getRunner($exercise) - ->run($input, $output); + ->run($context, $output); } finally { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('run.finish', $exercise, $input)); } diff --git a/src/ExerciseRunner/CgiRunner.php b/src/ExerciseRunner/CgiRunner.php index 288a692d..b207844e 100644 --- a/src/ExerciseRunner/CgiRunner.php +++ b/src/ExerciseRunner/CgiRunner.php @@ -18,6 +18,7 @@ use PhpSchool\PhpWorkshop\Exception\SolutionExecutionException; use PhpSchool\PhpWorkshop\Exercise\CgiExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Process\ProcessFactory; @@ -63,7 +64,8 @@ class CgiRunner implements ExerciseRunnerInterface public function __construct( private CgiExercise $exercise, private EventDispatcher $eventDispatcher, - private ProcessFactory $processFactory + private ProcessFactory $processFactory, + private EnvironmentManager $environmentManager ) { } @@ -99,37 +101,42 @@ public function getRequiredChecks(): array * * cgi.verify.student.executing * * cgi.verify.student-execute.fail (if the student's solution fails to execute) * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. * @return CgiResult The result of the check. */ - public function verify(Input $input): ResultInterface + public function verify(ExecutionContext $context): ResultInterface { $scenario = $this->exercise->defineTestScenario(); - $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.start', $this->exercise, $input)); + $this->environmentManager->prepareStudent($context, $scenario); + $this->environmentManager->prepareReference($context, $scenario); + + $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.start', $this->exercise, $context->getInput())); $result = new CgiResult( array_map( - function (RequestInterface $request) use ($input) { - return $this->doVerify($request, $input); + function (RequestInterface $request) use ($context) { + return $this->doVerify($request, $context); }, $scenario->getExecutions() ) ); - $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.finish', $this->exercise, $input)); + + $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.finish', $this->exercise, $context->getInput())); return $result; } - private function doVerify(RequestInterface $request, Input $input): CgiResultInterface + private function doVerify(RequestInterface $request, ExecutionContext $context): CgiResultInterface { try { /** @var CgiExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( - new CgiExecuteEvent('cgi.verify.reference-execute.pre', $this->exercise, $input, $request) + new CgiExecuteEvent('cgi.verify.reference-execute.pre', $this->exercise, $context->getInput(), $request) ); $solutionResponse = $this->executePhpFile( - $input, - $this->exercise->getSolution()->getEntryPoint()->getAbsolutePath(), + $context, + $context->getReferenceExecutionDirectory(), + $this->exercise->getSolution()->getEntryPoint()->getRelativePath(), $event->getRequest(), 'reference' ); @@ -138,7 +145,7 @@ private function doVerify(RequestInterface $request, Input $input): CgiResultInt new CgiExecuteEvent( 'cgi.verify.reference-execute.fail', $this->exercise, - $input, + $context->getInput(), $request, ['exception' => $e] ) @@ -149,11 +156,12 @@ private function doVerify(RequestInterface $request, Input $input): CgiResultInt try { /** @var CgiExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( - new CgiExecuteEvent('cgi.verify.student-execute.pre', $this->exercise, $input, $request) + new CgiExecuteEvent('cgi.verify.student-execute.pre', $this->exercise, $context->getInput(), $request) ); $userResponse = $this->executePhpFile( - $input, - $input->getRequiredArgument('program'), + $context, + $context->getStudentExecutionDirectory(), + $context->getEntryPoint(), $event->getRequest(), 'student' ); @@ -162,7 +170,7 @@ private function doVerify(RequestInterface $request, Input $input): CgiResultInt new CgiExecuteEvent( 'cgi.verify.student-execute.fail', $this->exercise, - $input, + $context->getInput(), $request, ['exception' => $e] ) @@ -202,16 +210,17 @@ private function getHeaders(ResponseInterface $response): array * @return ResponseInterface */ private function executePhpFile( - Input $input, + ExecutionContext $context, + string $workingDirectory, string $fileName, RequestInterface $request, string $type ): ResponseInterface { - $process = $this->getPhpProcess(dirname($fileName), basename($fileName), $request); + $process = $this->getPhpProcess($workingDirectory, $fileName, $request); $process->start(); $this->eventDispatcher->dispatch( - new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $this->exercise, $input, $request) + new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $this->exercise, $context->getInput(), $request) ); $process->wait(); @@ -280,25 +289,27 @@ private function getPhpProcess(string $workingDirectory, string $fileName, Reque * * cgi.run.student-execute.pre * * cgi.run.student.executing * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output): bool + public function run(ExecutionContext $context, OutputInterface $output): bool { $scenario = $this->exercise->defineTestScenario(); - $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.start', $this->exercise, $input)); + $this->environmentManager->prepareStudent($context, $scenario); + + $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.start', $this->exercise, $context->getInput())); $success = true; foreach ($scenario->getExecutions() as $i => $request) { /** @var CgiExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( - new CgiExecuteEvent('cgi.run.student-execute.pre', $this->exercise, $input, $request) + new CgiExecuteEvent('cgi.run.student-execute.pre', $this->exercise, $context->getInput(), $request) ); $process = $this->getPhpProcess( - dirname($input->getRequiredArgument('program')), - $input->getRequiredArgument('program'), + $context->getStudentExecutionDirectory(), + $context->getEntryPoint(), $event->getRequest() ); @@ -307,7 +318,7 @@ public function run(Input $input, OutputInterface $output): bool new CgiExecuteEvent( 'cgi.run.student.executing', $this->exercise, - $input, + $context->getInput(), $request, ['output' => $output] ) @@ -324,10 +335,11 @@ public function run(Input $input, OutputInterface $output): bool $output->lineBreak(); $this->eventDispatcher->dispatch( - new CgiExecuteEvent('cgi.run.student-execute.post', $this->exercise, $input, $request) + new CgiExecuteEvent('cgi.run.student-execute.post', $this->exercise, $context->getInput(), $request) ); } - $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.finish', $this->exercise, $input)); + + $this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.finish', $this->exercise, $context->getInput())); return $success; } } diff --git a/src/ExerciseRunner/CliRunner.php b/src/ExerciseRunner/CliRunner.php index 3a7b23a1..96440e8d 100644 --- a/src/ExerciseRunner/CliRunner.php +++ b/src/ExerciseRunner/CliRunner.php @@ -17,6 +17,7 @@ use PhpSchool\PhpWorkshop\Exception\SolutionExecutionException; use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Process\ProcessFactory; @@ -61,7 +62,8 @@ class CliRunner implements ExerciseRunnerInterface public function __construct( private CliExercise $exercise, private EventDispatcher $eventDispatcher, - private ProcessFactory $processFactory + private ProcessFactory $processFactory, + private EnvironmentManager $environmentManager ) { } @@ -96,41 +98,45 @@ public function getRequiredChecks(): array * * cli.verify.student.executing * * cli.verify.student-execute.fail (if the student's solution fails to execute) * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. * @return CliResult The result of the check. */ - public function verify(Input $input): ResultInterface + public function verify(ExecutionContext $context): ResultInterface { $scenario = $this->exercise->defineTestScenario(); - $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.verify.start', $this->exercise, $input)); + $this->environmentManager->prepareStudent($context, $scenario); + $this->environmentManager->prepareReference($context, $scenario); + + $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.verify.start', $this->exercise, $context->getInput())); + $result = new CliResult( array_map( - function (Collection $args) use ($input) { - return $this->doVerify($input, $args); + function (Collection $args) use ($context) { + return $this->doVerify($context, $args); }, $scenario->getExecutions() ) ); - $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.verify.finish', $this->exercise, $input)); + + $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.verify.finish', $this->exercise, $context->getInput())); return $result; } /** - * @param Input $input * @param Collection $args - * @return CliResultInterface */ - private function doVerify(Input $input, Collection $args): CliResultInterface + private function doVerify(ExecutionContext $context, Collection $args): CliResultInterface { try { /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( - new CliExecuteEvent('cli.verify.reference-execute.pre', $this->exercise, $input, $args) + new CliExecuteEvent('cli.verify.reference-execute.pre', $this->exercise, $context->getInput(), $args) ); $solutionOutput = $this->executePhpFile( - $input, - $this->exercise->getSolution()->getEntryPoint()->getAbsolutePath(), + $context, + $context->getReferenceExecutionDirectory(), + $this->exercise->getSolution()->getEntryPoint()->getRelativePath(), $event->getArgs(), 'reference' ); @@ -139,7 +145,7 @@ private function doVerify(Input $input, Collection $args): CliResultInterface new CliExecuteEvent( 'cli.verify.reference-execute.fail', $this->exercise, - $input, + $context->getInput(), $args, ['exception' => $e] ) @@ -150,11 +156,12 @@ private function doVerify(Input $input, Collection $args): CliResultInterface try { /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( - new CliExecuteEvent('cli.verify.student-execute.pre', $this->exercise, $input, $args) + new CliExecuteEvent('cli.verify.student-execute.pre', $this->exercise, $context->getInput(), $args) ); $userOutput = $this->executePhpFile( - $input, - $input->getRequiredArgument('program'), + $context, + $context->getStudentExecutionDirectory(), + $context->getEntryPoint(), $event->getArgs(), 'student' ); @@ -163,7 +170,7 @@ private function doVerify(Input $input, Collection $args): CliResultInterface new CliExecuteEvent( 'cli.verify.student-execute.fail', $this->exercise, - $input, + $context->getInput(), $args, ['exception' => $e] ) @@ -189,34 +196,36 @@ private function doVerify(Input $input, Collection $args): CliResultInterface * * cli.run.student-execute.pre * * cli.run.student.executing * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output): bool + public function run(ExecutionContext $context, OutputInterface $output): bool { $scenario = $this->exercise->defineTestScenario(); - $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.run.start', $this->exercise, $input)); + $this->environmentManager->prepareStudent($context, $scenario); + + $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.run.start', $this->exercise, $context->getInput())); $success = true; foreach ($scenario->getExecutions() as $i => $args) { /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( - new CliExecuteEvent('cli.run.student-execute.pre', $this->exercise, $input, $args) + new CliExecuteEvent('cli.run.student-execute.pre', $this->exercise, $context->getInput(), $args) ); $args = $event->getArgs(); $process = $this->getPhpProcess( - dirname($input->getRequiredArgument('program')), - $input->getRequiredArgument('program'), + $context->getStudentExecutionDirectory(), + $context->getEntryPoint(), $args ); $process->start(); $this->eventDispatcher->dispatch( - new CliExecuteEvent('cli.run.student.executing', $this->exercise, $input, $args, ['output' => $output]) + new CliExecuteEvent('cli.run.student.executing', $this->exercise, $context->getInput(), $args, ['output' => $output]) ); $process->wait(function ($outputType, $outputBuffer) use ($output) { $output->write($outputBuffer); @@ -230,24 +239,24 @@ public function run(Input $input, OutputInterface $output): bool $output->lineBreak(); $this->eventDispatcher->dispatch( - new CliExecuteEvent('cli.run.student-execute.post', $this->exercise, $input, $args) + new CliExecuteEvent('cli.run.student-execute.post', $this->exercise, $context->getInput(), $args) ); } - $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.run.finish', $this->exercise, $input)); + $this->eventDispatcher->dispatch(new CliExerciseRunnerEvent('cli.run.finish', $this->exercise, $context->getInput())); return $success; } /** * @param ArrayObject $args */ - private function executePhpFile(Input $input, string $fileName, ArrayObject $args, string $type): string + private function executePhpFile(ExecutionContext $context, string $workingDirectory, string $fileName, ArrayObject $args, string $type): string { - $process = $this->getPhpProcess(dirname($fileName), $fileName, $args); + $process = $this->getPhpProcess($workingDirectory, $fileName, $args); $process->start(); $this->eventDispatcher->dispatch( - new CliExecuteEvent(sprintf('cli.verify.%s.executing', $type), $this->exercise, $input, $args) + new CliExecuteEvent(sprintf('cli.verify.%s.executing', $type), $this->exercise, $context->getInput(), $args) ); $process->wait(); diff --git a/src/ExerciseRunner/CustomVerifyingRunner.php b/src/ExerciseRunner/CustomVerifyingRunner.php index c2fb9afc..f911fdea 100644 --- a/src/ExerciseRunner/CustomVerifyingRunner.php +++ b/src/ExerciseRunner/CustomVerifyingRunner.php @@ -5,6 +5,7 @@ namespace PhpSchool\PhpWorkshop\ExerciseRunner; use PhpSchool\PhpWorkshop\Exercise\CustomVerifyingExercise; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\ResultInterface; @@ -14,17 +15,8 @@ */ class CustomVerifyingRunner implements ExerciseRunnerInterface { - /** - * @var CustomVerifyingExercise - */ - private $exercise; - - /** - * @param CustomVerifyingExercise $exercise - */ - public function __construct(CustomVerifyingExercise $exercise) + public function __construct(private CustomVerifyingExercise $exercise) { - $this->exercise = $exercise; } /** @@ -51,10 +43,10 @@ public function getRequiredChecks(): array * Delegate to the exercise for verifying. Verifying could mean checking that a program was installed or that some * other arbitrary task was performed. * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. * @return ResultInterface The result of the check. */ - public function verify(Input $input): ResultInterface + public function verify(ExecutionContext $context): ResultInterface { return $this->exercise->verify(); } @@ -63,11 +55,11 @@ public function verify(Input $input): ResultInterface * Running a custom verifying exercise does nothing. There is no program required, therefore there is nothing * to run. * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output): bool + public function run(ExecutionContext $context, OutputInterface $output): bool { $message = 'Nothing to run here. This exercise does not require a code solution, '; $message .= 'so there is nothing to execute.'; diff --git a/src/ExerciseRunner/ExerciseRunnerInterface.php b/src/ExerciseRunner/ExerciseRunnerInterface.php index 66acec07..03dea33d 100644 --- a/src/ExerciseRunner/ExerciseRunnerInterface.php +++ b/src/ExerciseRunner/ExerciseRunnerInterface.php @@ -4,6 +4,7 @@ namespace PhpSchool\PhpWorkshop\ExerciseRunner; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\ResultInterface; @@ -38,19 +39,19 @@ public function getRequiredChecks(): array; * Other things that could go wrong include the student's solution returning a non-zero * exit code, or a notice/warning being exhibited. * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context * @return ResultInterface The result of the check. */ - public function verify(Input $input): ResultInterface; + public function verify(ExecutionContext $context): ResultInterface; /** * Run a solution to an exercise. This simply run's the student's solution with the correct input from the exercise * (such as the CLI arguments) and prints the output directly. This allows the student to have the environment * setup for them including getting a different set of arguments each time (if the exercise supports that). * - * @param Input $input The command line arguments passed to the command. + * @param ExecutionContext $context * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output): bool; + public function run(ExecutionContext $context, OutputInterface $output): bool; } diff --git a/src/ExerciseRunner/Factory/CgiRunnerFactory.php b/src/ExerciseRunner/Factory/CgiRunnerFactory.php index 3b0deed3..3a90195a 100644 --- a/src/ExerciseRunner/Factory/CgiRunnerFactory.php +++ b/src/ExerciseRunner/Factory/CgiRunnerFactory.php @@ -11,6 +11,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface; use PhpSchool\PhpWorkshop\Process\ProcessFactory; use PhpSchool\PhpWorkshop\Utils\RequestRenderer; @@ -25,7 +26,7 @@ class CgiRunnerFactory implements ExerciseRunnerFactoryInterface */ private static string $type = ExerciseType::CGI; - public function __construct(private EventDispatcher $eventDispatcher, private ProcessFactory $processFactory) + public function __construct(private EventDispatcher $eventDispatcher, private ProcessFactory $processFactory, private EnvironmentManager $environmentManager) { } @@ -58,6 +59,6 @@ public function configureInput(CommandDefinition $commandDefinition): void */ public function create(ExerciseInterface $exercise): ExerciseRunnerInterface { - return new CgiRunner($exercise, $this->eventDispatcher, $this->processFactory); + return new CgiRunner($exercise, $this->eventDispatcher, $this->processFactory, $this->environmentManager); } } diff --git a/src/ExerciseRunner/Factory/CliRunnerFactory.php b/src/ExerciseRunner/Factory/CliRunnerFactory.php index 9b6eec2e..61db3491 100644 --- a/src/ExerciseRunner/Factory/CliRunnerFactory.php +++ b/src/ExerciseRunner/Factory/CliRunnerFactory.php @@ -11,6 +11,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface; use PhpSchool\PhpWorkshop\Process\ProcessFactory; @@ -24,7 +25,7 @@ class CliRunnerFactory implements ExerciseRunnerFactoryInterface */ private static string $type = ExerciseType::CLI; - public function __construct(private EventDispatcher $eventDispatcher, private ProcessFactory $processFactory) + public function __construct(private EventDispatcher $eventDispatcher, private ProcessFactory $processFactory, private EnvironmentManager $environmentManager) { } @@ -57,6 +58,6 @@ public function configureInput(CommandDefinition $commandDefinition): void */ public function create(ExerciseInterface $exercise): ExerciseRunnerInterface { - return new CliRunner($exercise, $this->eventDispatcher, $this->processFactory); + return new CliRunner($exercise, $this->eventDispatcher, $this->processFactory, $this->environmentManager); } } diff --git a/test/Asset/CgiExerciseImpl.php b/test/Asset/CgiExerciseImpl.php index 43ebda58..643420cb 100644 --- a/test/Asset/CgiExerciseImpl.php +++ b/test/Asset/CgiExerciseImpl.php @@ -7,7 +7,6 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\Scenario\CgiScenario; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Solution\SolutionInterface; use Psr\Http\Message\RequestInterface; diff --git a/test/Asset/CliExerciseImpl.php b/test/Asset/CliExerciseImpl.php index cdd627cd..cfab2501 100644 --- a/test/Asset/CliExerciseImpl.php +++ b/test/Asset/CliExerciseImpl.php @@ -6,9 +6,7 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution; use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Solution\SolutionInterface; class CliExerciseImpl implements ExerciseInterface, CliExercise diff --git a/test/Check/DatabaseCheckTest.php b/test/Check/DatabaseCheckTest.php index 35d5152b..dbbac4e0 100644 --- a/test/Check/DatabaseCheckTest.php +++ b/test/Check/DatabaseCheckTest.php @@ -13,6 +13,10 @@ use PhpSchool\PhpWorkshop\ExerciseCheck\DatabaseExerciseCheck; use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContextFactory; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\StaticExecutionContextFactory; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; @@ -20,33 +24,18 @@ use PhpSchool\PhpWorkshop\ResultAggregator; use PhpSchool\PhpWorkshop\Solution\SingleFileSolution; use PhpSchool\PhpWorkshopTest\Asset\DatabaseExercise; -use PhpSchool\PhpWorkshopTest\Asset\DatabaseExerciseInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionProperty; use RuntimeException; +use Symfony\Component\Filesystem\Filesystem; class DatabaseCheckTest extends TestCase { - /** - * @var DatabaseCheck - */ - private $check; - - /** - * @var CheckRepository - */ - private $checkRepository; - - /** - * @var ExerciseInterface - */ - private $exercise; - - /** - * @var string - */ - private $dbDir; + private DatabaseCheck $check; + private CheckRepository $checkRepository; + private ExerciseInterface $exercise; + private string $dbDir; public function setUp(): void { @@ -62,7 +51,10 @@ public function setUp(): void '%s/php-school/PhpSchool_PhpWorkshop_Check_DatabaseCheck', str_replace('\\', '/', realpath(sys_get_temp_dir())) ); + } + public function testCheckMeta(): void + { $this->assertEquals('Database Verification Check', $this->check->getName()); $this->assertEquals(DatabaseExerciseCheck::class, $this->check->getExerciseInterface()); } @@ -70,7 +62,7 @@ public function setUp(): void private function getRunnerManager(ExerciseInterface $exercise, EventDispatcher $eventDispatcher): MockObject { $runner = $this->getMockBuilder(CliRunner::class) - ->setConstructorArgs([$exercise, $eventDispatcher, new HostProcessFactory()]) + ->setConstructorArgs([$exercise, $eventDispatcher, new HostProcessFactory(), new EnvironmentManager(new Filesystem(), $eventDispatcher)]) ->onlyMethods(['getRequiredChecks']) ->getMock(); @@ -96,7 +88,6 @@ public function testIfDatabaseFolderExistsExceptionIsThrown(): void $this->fail('Exception was not thrown'); } catch (RuntimeException $e) { $this->assertEquals(sprintf('Database directory: "%s" already exists', $this->dbDir), $e->getMessage()); - rmdir($this->dbDir); } } @@ -133,7 +124,7 @@ public function testIfPDOThrowsExceptionItCleansUp(): void $this->getRunnerManager($this->exercise, $eventDispatcher), $results, $eventDispatcher, - $this->checkRepository + $this->checkRepository, ); $dispatcher->verify($this->exercise, new Input('app', ['program' => __DIR__ . '/../res/database/user.php'])); @@ -146,6 +137,7 @@ public function testSuccessIsReturnedIfDatabaseVerificationPassed(): void $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])); + $this->exercise->setVerifier(fn () => true); $this->checkRepository->registerCheck($this->check); $results = new ResultAggregator(); @@ -154,10 +146,9 @@ public function testSuccessIsReturnedIfDatabaseVerificationPassed(): void $this->getRunnerManager($this->exercise, $eventDispatcher), $results, $eventDispatcher, - $this->checkRepository + $this->checkRepository, ); - $dispatcher->verify($this->exercise, new Input('app', ['program' => __DIR__ . '/../res/database/user.php'])); $this->assertTrue($results->isSuccessful()); @@ -173,7 +164,7 @@ public function testRunExercise(): void $this->getRunnerManager($this->exercise, $eventDispatcher), $results, $eventDispatcher, - $this->checkRepository + $this->checkRepository, ); $dispatcher->run( @@ -198,7 +189,7 @@ public function testFailureIsReturnedIfDatabaseVerificationFails(): void $this->getRunnerManager($this->exercise, $eventDispatcher), $results, $eventDispatcher, - $this->checkRepository + $this->checkRepository, ); $dispatcher->verify($this->exercise, new Input('app', ['program' => __DIR__ . '/../res/database/user.php'])); @@ -246,7 +237,7 @@ public function testAlteringDatabaseInSolutionDoesNotEffectDatabaseInUserSolutio $this->getRunnerManager($this->exercise, $eventDispatcher), $results, $eventDispatcher, - $this->checkRepository + $this->checkRepository, ); $dispatcher->verify( @@ -254,4 +245,10 @@ public function testAlteringDatabaseInSolutionDoesNotEffectDatabaseInUserSolutio new Input('app', ['program' => __DIR__ . '/../res/database/user-solution-alter-db.php']) ); } + + protected function tearDown(): void + { + $fs = new Filesystem(); + $fs->remove($this->dbDir); + } } diff --git a/test/ExerciseDispatcherTest.php b/test/ExerciseDispatcherTest.php index bab752ea..f5897f58 100644 --- a/test/ExerciseDispatcherTest.php +++ b/test/ExerciseDispatcherTest.php @@ -14,6 +14,8 @@ use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\ExerciseDispatcher; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContextFactory; use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface; use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager; use PhpSchool\PhpWorkshop\Input\Input; @@ -28,15 +30,8 @@ class ExerciseDispatcherTest extends TestCase { - /** - * @var Filesystem - */ - private $filesystem; - - /** - * @var string - */ - private $file; + private Filesystem $filesystem; + private string $file; public function setUp(): void { @@ -54,7 +49,7 @@ public function testGetEventDispatcher(): void $this->createMock(RunnerManager::class), $results, $eventDispatcher, - new CheckRepository() + new CheckRepository(), ); $this->assertSame($eventDispatcher, $exerciseDispatcher->getEventDispatcher()); @@ -69,7 +64,7 @@ public function testRequireCheckThrowsExceptionIfCheckDoesNotExist(): void $this->createMock(RunnerManager::class), new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository() + new CheckRepository(), ); $exerciseDispatcher->requireCheck('NotACheck'); } @@ -84,7 +79,7 @@ public function testRequireCheckThrowsExceptionIfPositionNotValid(): void $this->createMock(RunnerManager::class), new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $this->expectException(InvalidArgumentException::class); @@ -102,7 +97,7 @@ public function testRequireBeforeCheckIsCorrectlyRegistered(): void $this->createMock(RunnerManager::class), new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $exerciseDispatcher->requireCheck(get_class($check)); @@ -119,7 +114,7 @@ public function testRequireAfterCheckIsCorrectlyRegistered(): void $this->createMock(RunnerManager::class), new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $exerciseDispatcher->requireCheck(get_class($check)); @@ -135,7 +130,7 @@ public function testRequireCheckThrowsExceptionIfCheckIsNotSimpleOrListenable(): $this->createMock(RunnerManager::class), new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $this->expectException(InvalidArgumentException::class); @@ -153,7 +148,7 @@ public function testRequireListenableCheckAttachesToDispatcher(): void $this->createMock(RunnerManager::class), new ResultAggregator(), $eventDispatcher, - new CheckRepository([$check]) + new CheckRepository([$check]), ); $exerciseDispatcher->requireCheck(get_class($check)); @@ -178,13 +173,13 @@ public function testVerifyThrowsExceptionIfCheckDoesNotSupportExerciseType(): vo $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $this->expectException(CheckNotApplicableException::class); $this->expectExceptionMessage('Check: "Some Check" cannot process exercise: "Some Exercise" with type: "CLI"'); - $exerciseDispatcher->verify($exercise, new Input('app')); + $exerciseDispatcher->verify($exercise, new Input('app', ['program' => $this->file])); } public function testVerifyThrowsExceptionIfExerciseDoesNotImplementCorrectInterface(): void @@ -206,13 +201,13 @@ public function testVerifyThrowsExceptionIfExerciseDoesNotImplementCorrectInterf $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $this->expectException(ExerciseNotConfiguredException::class); $this->expectExceptionMessage('Exercise: "Some Exercise" should implement interface: "LolIDoNotExist"'); - $exerciseDispatcher->verify($exercise, new Input('app')); + $exerciseDispatcher->verify($exercise, new Input('app', ['program' => $this->file])); } public function testVerify(): void @@ -228,7 +223,7 @@ public function testVerify(): void $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([get_class($check)]); - $runner->method('verify')->with($input)->willReturn(new Success('Success!')); + $runner->method('verify')->willReturn(new Success('Success!')); $runnerManager = $this->createMock(RunnerManager::class); $runnerManager->method('getRunner')->with($exercise)->willReturn($runner); @@ -237,7 +232,7 @@ public function testVerify(): void $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check]) + new CheckRepository([$check]), ); $result = $exerciseDispatcher->verify($exercise, $input); @@ -289,7 +284,6 @@ public function testVerifyOnlyRunsRequiredChecks(): void $runner ->method('verify') - ->with($input) ->willReturn(new Success('Success!')); $runnerManager = $this->createMock(RunnerManager::class); @@ -302,7 +296,7 @@ public function testVerifyOnlyRunsRequiredChecks(): void $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check1, $check2]) + new CheckRepository([$check1, $check2]), ); $result = $exerciseDispatcher->verify($exercise, $input); @@ -327,10 +321,9 @@ public function testVerifyWithBeforeAndAfterRequiredChecks(): void $check2->method('getExerciseInterface')->willReturn(ExerciseInterface::class); $check2->method('check')->with($exercise, $input)->willReturn(new Success('Success!')); - $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([get_class($check1), get_class($check2)]); - $runner->method('verify')->with($input)->willReturn(new Success('Success!')); + $runner->method('verify')->willReturn(new Success('Success!')); $runnerManager = $this->createMock(RunnerManager::class); $runnerManager->method('getRunner')->with($exercise)->willReturn($runner); @@ -339,7 +332,7 @@ public function testVerifyWithBeforeAndAfterRequiredChecks(): void $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check1, $check2]) + new CheckRepository([$check1, $check2]), ); $result = $exerciseDispatcher->verify($exercise, $input); @@ -415,7 +408,7 @@ public function testWhenBeforeChecksFailTheyReturnImmediately(): void $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([$check1, $check2]) + new CheckRepository([$check1, $check2]), ); $result = $exerciseDispatcher->verify($exercise, $input); @@ -462,7 +455,7 @@ public function testAllEventsAreDispatched(): void $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([]); - $runner->method('verify')->with($input)->willReturn(new Success('Success!')); + $runner->method('verify')->willReturn(new Success('Success!')); $runnerManager = $this->createMock(RunnerManager::class); $runnerManager->method('getRunner')->with($exercise)->willReturn($runner); @@ -471,7 +464,7 @@ public function testAllEventsAreDispatched(): void $runnerManager, new ResultAggregator(), $eventDispatcher, - new CheckRepository() + new CheckRepository(), ); $exerciseDispatcher->verify($exercise, $input); @@ -506,7 +499,7 @@ public function testVerifyPostExecuteIsStillDispatchedEvenIfRunnerThrowsExceptio $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([]); - $runner->method('verify')->with($input)->will($this->throwException(new RuntimeException())); + $runner->method('verify')->will($this->throwException(new RuntimeException())); $runnerManager = $this->createMock(RunnerManager::class); $runnerManager->method('getRunner')->with($exercise)->willReturn($runner); @@ -515,7 +508,7 @@ public function testVerifyPostExecuteIsStillDispatchedEvenIfRunnerThrowsExceptio $runnerManager, new ResultAggregator(), $eventDispatcher, - new CheckRepository() + new CheckRepository(), ); $this->expectException(RuntimeException::class); @@ -530,7 +523,7 @@ public function testRun(): void $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([]); - $runner->method('run')->with($input, $output)->willReturn(true); + $runner->method('run')->willReturn(true); $runnerManager = $this->createMock(RunnerManager::class); $runnerManager->method('getRunner')->with($exercise)->willReturn($runner); @@ -539,7 +532,7 @@ public function testRun(): void $runnerManager, new ResultAggregator(), new EventDispatcher(new ResultAggregator()), - new CheckRepository([new PhpLintCheck()]) + new CheckRepository([new PhpLintCheck()]), ); $this->assertTrue($exerciseDispatcher->run($exercise, $input, $output)); diff --git a/test/ExerciseRunner/CgiRunnerTest.php b/test/ExerciseRunner/CgiRunnerTest.php index 28298e67..e82c728d 100644 --- a/test/ExerciseRunner/CgiRunnerTest.php +++ b/test/ExerciseRunner/CgiRunnerTest.php @@ -7,8 +7,10 @@ use PhpSchool\PhpWorkshop\Check\CodeExistsCheck; use PhpSchool\PhpWorkshop\Exercise\Scenario\CgiScenario; use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\Listener\OutputRunInfoListener; use PhpSchool\PhpWorkshop\Process\HostProcessFactory; +use PhpSchool\PhpWorkshop\Result\Cli\CliResult; use PhpSchool\PhpWorkshopTest\Asset\CgiExerciseImpl; use PhpSchool\Terminal\Terminal; use PhpSchool\PhpWorkshop\Check\CodeParseCheck; @@ -26,9 +28,9 @@ use PhpSchool\PhpWorkshop\ResultAggregator; use PhpSchool\PhpWorkshop\Solution\SingleFileSolution; use PhpSchool\PhpWorkshop\Utils\RequestRenderer; -use PhpSchool\PhpWorkshopTest\Asset\CgiExerciseInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; class CgiRunnerTest extends TestCase @@ -36,14 +38,14 @@ class CgiRunnerTest extends TestCase use AssertionRenames; private CgiRunner $runner; - private CgiExerciseImpl $exercise; + private $exercise; private EventDispatcher $eventDispatcher; public function setUp(): void { $this->exercise = new CgiExerciseImpl(); $this->eventDispatcher = new EventDispatcher(new ResultAggregator()); - $this->runner = new CgiRunner($this->exercise, $this->eventDispatcher, new HostProcessFactory()); + $this->runner = new CgiRunner($this->exercise, $this->eventDispatcher, new HostProcessFactory(), new EnvironmentManager(new Filesystem(), $this->eventDispatcher)); $this->assertEquals('CGI Program Runner', $this->runner->getName()); } @@ -71,7 +73,8 @@ public function testVerifyThrowsExceptionIfSolutionFailsExecution(): void $regex = "/^PHP Code failed to execute\. Error: \"PHP Parse error: syntax error, unexpected end of file in/"; $this->expectException(SolutionExecutionException::class); $this->expectExceptionMessageMatches($regex); - $this->runner->verify(new Input('app', ['program' => ''])); + + $this->runner->verify(new TestContext($this->exercise)); } public function testVerifyReturnsSuccessIfGetSolutionOutputMatchesUserOutput(): void @@ -82,12 +85,11 @@ public function testVerifyReturnsSuccessIfGetSolutionOutputMatchesUserOutput(): (new CgiScenario())->withExecution((new Request('GET', 'http://some.site?number=5'))) ); - $request = (new Request('GET', 'http://some.site?number=5')); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/get-solution.php'); + $result = $this->runner->verify($context); - $this->assertInstanceOf( - CgiResult::class, - $this->runner->verify(new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/get-solution.php')])) - ); + $this->assertInstanceOf(CgiResult::class, $result); + $this->assertTrue($result->isSuccessful()); } public function testVerifyReturnsSuccessIfPostSolutionOutputMatchesUserOutput(): void @@ -102,14 +104,11 @@ public function testVerifyReturnsSuccessIfPostSolutionOutputMatchesUserOutput(): $this->exercise->setScenario((new CgiScenario())->withExecution($request)); - $this->assertInstanceOf( - CgiResult::class, - $res = $this->runner->verify( - new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/post-solution.php')]) - ) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/post-solution.php'); + $result = $this->runner->verify($context); - $this->assertTrue($res->isSuccessful()); + $this->assertInstanceOf(CgiResult::class, $result); + $this->assertTrue($result->isSuccessful()); } public function testVerifyReturnsSuccessIfPostSolutionOutputMatchesUserOutputWithMultipleParams(): void @@ -124,11 +123,11 @@ public function testVerifyReturnsSuccessIfPostSolutionOutputMatchesUserOutputWit $this->exercise->setScenario((new CgiScenario())->withExecution($request)); - $result = $this->runner->verify( - new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/post-multiple-solution.php')]) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/post-multiple-solution.php'); + $result = $this->runner->verify($context); $this->assertInstanceOf(CgiResult::class, $result); + $this->assertTrue($result->isSuccessful()); } public function testVerifyReturnsFailureIfUserSolutionFailsToExecute(): void @@ -140,14 +139,13 @@ public function testVerifyReturnsFailureIfUserSolutionFailsToExecute(): void $this->exercise->setScenario((new CgiScenario())->withExecution($request)); - $failure = $this->runner->verify( - new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/user-error.php')]) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/user-error.php'); + $result = $this->runner->verify($context); - $this->assertInstanceOf(CgiResult::class, $failure); - $this->assertCount(1, $failure); + $this->assertInstanceOf(CgiResult::class, $result); + $this->assertCount(1, $result); - $result = iterator_to_array($failure)[0]; + $result = iterator_to_array($result)[0]; $this->assertInstanceOf(Failure::class, $result); $failureMsg = '/^PHP Code failed to execute. Error: "PHP Parse error: syntax error, unexpected end of file'; @@ -164,14 +162,13 @@ public function testVerifyReturnsFailureIfSolutionOutputDoesNotMatchUserOutput() $this->exercise->setScenario((new CgiScenario())->withExecution($request)); - $failure = $this->runner->verify( - new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/get-user-wrong.php')]) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/get-user-wrong.php'); + $result = $this->runner->verify($context); - $this->assertInstanceOf(CgiResult::class, $failure); - $this->assertCount(1, $failure); + $this->assertInstanceOf(CgiResult::class, $result); + $this->assertCount(1, $result); - $result = iterator_to_array($failure)[0]; + $result = iterator_to_array($result)[0]; $this->assertInstanceOf(RequestFailure::class, $result); $this->assertEquals('10', $result->getExpectedOutput()); $this->assertEquals('15', $result->getActualOutput()); @@ -188,14 +185,13 @@ public function testVerifyReturnsFailureIfSolutionOutputHeadersDoesNotMatchUserO $this->exercise->setScenario((new CgiScenario())->withExecution($request)); - $failure = $this->runner->verify( - new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/get-user-header-wrong.php')]) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/get-user-header-wrong.php'); + $result = $this->runner->verify($context); - $this->assertInstanceOf(CgiResult::class, $failure); - $this->assertCount(1, $failure); + $this->assertInstanceOf(CgiResult::class, $result); + $this->assertCount(1, $result); - $result = iterator_to_array($failure)[0]; + $result = iterator_to_array($result)[0]; $this->assertInstanceOf(RequestFailure::class, $result); $this->assertSame($result->getExpectedOutput(), $result->getActualOutput()); @@ -255,12 +251,10 @@ public function testRunPassesOutputAndReturnsSuccessIfAllRequestsAreSuccessful() $this->expectOutputString($exp); - $success = $this->runner->run( - new Input('app', ['program' => realpath(__DIR__ . '/../res/cgi/get-solution.php')]), - $output - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cgi/get-solution.php'); + $result = $this->runner->run($context, $output); - $this->assertTrue($success); + $this->assertTrue($result); } public function testRunPassesOutputAndReturnsFailureIfARequestFails(): void @@ -291,10 +285,9 @@ public function testRunPassesOutputAndReturnsFailureIfARequestFails(): void $this->expectOutputString($exp); - $success = $this->runner->run( - new Input('app', ['program' => 'not-existing-file.php']), - $output - ); - $this->assertFalse($success); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/non-existent.php'); + $result = $this->runner->run($context, $output); + + $this->assertFalse($result); } } diff --git a/test/ExerciseRunner/CliRunnerTest.php b/test/ExerciseRunner/CliRunnerTest.php index e9af3f3d..59c74a51 100644 --- a/test/ExerciseRunner/CliRunnerTest.php +++ b/test/ExerciseRunner/CliRunnerTest.php @@ -6,6 +6,8 @@ use PhpSchool\PhpWorkshop\Check\CodeExistsCheck; use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\Listener\OutputRunInfoListener; use PhpSchool\PhpWorkshop\Process\HostProcessFactory; use PhpSchool\PhpWorkshop\Utils\RequestRenderer; @@ -26,9 +28,9 @@ use PhpSchool\PhpWorkshop\Result\Cli\RequestFailure; use PhpSchool\PhpWorkshop\ResultAggregator; use PhpSchool\PhpWorkshop\Solution\SingleFileSolution; -use PhpSchool\PhpWorkshopTest\Asset\CliExerciseInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; class CliRunnerTest extends TestCase @@ -43,7 +45,7 @@ public function setUp(): void { $this->exercise = new CliExerciseImpl(); $this->eventDispatcher = new EventDispatcher(new ResultAggregator()); - $this->runner = new CliRunner($this->exercise, $this->eventDispatcher, new HostProcessFactory()); + $this->runner = new CliRunner($this->exercise, $this->eventDispatcher, new HostProcessFactory(), new EnvironmentManager(new Filesystem(), $this->eventDispatcher)); $this->assertEquals('CLI Program Runner', $this->runner->getName()); } @@ -63,7 +65,6 @@ public function testRequiredChecks(): void public function testVerifyThrowsExceptionIfSolutionFailsExecution(): void { $solution = SingleFileSolution::fromFile(realpath(__DIR__ . '/../res/cli/solution-error.php')); - $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution()); @@ -71,7 +72,8 @@ public function testVerifyThrowsExceptionIfSolutionFailsExecution(): void $regex .= ", expecting ['\"][,;]['\"] or ['\"][;,]['\"]/"; $this->expectException(SolutionExecutionException::class); $this->expectExceptionMessageMatches($regex); - $this->runner->verify(new Input('app', ['program' => ''])); + + $this->runner->verify(new TestContext($this->exercise)); } public function testVerifyReturnsSuccessIfSolutionOutputMatchesUserOutput(): void @@ -80,12 +82,11 @@ public function testVerifyReturnsSuccessIfSolutionOutputMatchesUserOutput(): voi $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])); - $this->assertInstanceOf( - CliResult::class, - $res = $this->runner->verify(new Input('app', ['program' => __DIR__ . '/../res/cli/user.php'])) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user.php'); + $result = $this->runner->verify($context); - $this->assertTrue($res->isSuccessful()); + $this->assertInstanceOf(CliResult::class, $result); + $this->assertTrue($result->isSuccessful()); } public function testSuccessWithSingleSetOfArgsForBC(): void @@ -94,12 +95,11 @@ public function testSuccessWithSingleSetOfArgsForBC(): void $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])); - $this->assertInstanceOf( - CliResult::class, - $res = $this->runner->verify(new Input('app', ['program' => __DIR__ . '/../res/cli/user.php'])) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user.php'); + $result = $this->runner->verify($context); - $this->assertTrue($res->isSuccessful()); + $this->assertInstanceOf(CliResult::class, $result); + $this->assertTrue($result->isSuccessful()); } public function testVerifyReturnsFailureIfUserSolutionFailsToExecute(): void @@ -108,15 +108,16 @@ public function testVerifyReturnsFailureIfUserSolutionFailsToExecute(): void $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution()); - $failure = $this->runner->verify(new Input('app', ['program' => __DIR__ . '/../res/cli/user-error.php'])); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user-error.php'); + $result = $this->runner->verify($context); $failureMsg = '/^PHP Code failed to execute. Error: "PHP Parse error: syntax error, '; $failureMsg .= "unexpected end of file, expecting ['\"][,;]['\"] or ['\"][;,]['\"]/"; - $this->assertInstanceOf(CliResult::class, $failure); - $this->assertCount(1, $failure); + $this->assertInstanceOf(CliResult::class, $result); + $this->assertCount(1, $result); - $result = iterator_to_array($failure)[0]; + $result = iterator_to_array($result)[0]; $this->assertInstanceOf(GenericFailure::class, $result); $this->assertMatchesRegularExpression($failureMsg, $result->getReason()); } @@ -127,12 +128,13 @@ public function testVerifyReturnsFailureIfSolutionOutputDoesNotMatchUserOutput() $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])); - $failure = $this->runner->verify(new Input('app', ['program' => __DIR__ . '/../res/cli/user-wrong.php'])); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user-wrong.php'); + $result = $this->runner->verify($context); - $this->assertInstanceOf(CliResult::class, $failure); - $this->assertCount(1, $failure); + $this->assertInstanceOf(CliResult::class, $result); + $this->assertCount(1, $result); - $result = iterator_to_array($failure)[0]; + $result = iterator_to_array($result)[0]; $this->assertInstanceOf(RequestFailure::class, $result); $this->assertEquals('6', $result->getExpectedOutput()); @@ -163,14 +165,10 @@ public function testRunPassesOutputAndReturnsSuccessIfScriptIsSuccessful(): void $this->expectOutputString($exp); - $this->exercise->setScenario( - (new CliScenario()) - ->withExecution([1, 2, 3]) - ->withExecution([4, 5, 6]) - ); + $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])->withExecution([4, 5, 6])); - - $result = $this->runner->run(new Input('app', ['program' => __DIR__ . '/../res/cli/user.php']), $output); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user.php'); + $result = $this->runner->run($context, $output); $this->assertTrue($result); } @@ -178,14 +176,17 @@ public function testRunPassesOutputAndReturnsSuccessIfScriptIsSuccessful(): void public function testRunPassesOutputAndReturnsFailureIfScriptFails(): void { $output = new StdOutput(new Color(), $this->createMock(Terminal::class)); + $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])); $this->expectOutputRegex( "/(PHP )?Parse error:\W+syntax error, unexpected end of file, expecting ['\"][,;]['\"] or ['\"][;,]['\"] /" ); - $success = $this->runner->run(new Input('app', ['program' => __DIR__ . '/../res/cli/user-error.php']), $output); - $this->assertFalse($success); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user-error.php'); + $result = $this->runner->run($context, $output); + + $this->assertFalse($result); } public function testsArgsAppendedByEventsArePassedToResults(): void @@ -201,12 +202,11 @@ function (CliExecuteEvent $e) { $this->exercise->setSolution($solution); $this->exercise->setScenario((new CliScenario())->withExecution([1, 2, 3])); - $this->assertInstanceOf( - CliResult::class, - $res = $this->runner->verify(new Input('app', ['program' => __DIR__ . '/../res/cli/user.php'])) - ); + $context = TestContext::fromExerciseAndStudentSolution($this->exercise, __DIR__ . '/../res/cli/user.php'); + $result = $this->runner->verify($context); - $this->assertTrue($res->isSuccessful()); - $this->assertEquals([1, 2, 3, 4], $res->getResults()[0]->getArgs()->getArrayCopy()); + $this->assertInstanceOf(CliResult::class, $result); + $this->assertTrue($result->isSuccessful()); + $this->assertEquals([1, 2, 3, 4], $result->getResults()[0]->getArgs()->getArrayCopy()); } } diff --git a/test/ExerciseRunner/CustomVerifyingRunnerTest.php b/test/ExerciseRunner/CustomVerifyingRunnerTest.php index 76c79749..5b16bf58 100644 --- a/test/ExerciseRunner/CustomVerifyingRunnerTest.php +++ b/test/ExerciseRunner/CustomVerifyingRunnerTest.php @@ -3,6 +3,7 @@ namespace PhpSchool\PhpWorkshopTest\ExerciseRunner; use Colors\Color; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\Terminal\Terminal; use PhpSchool\PhpWorkshop\ExerciseRunner\CustomVerifyingRunner; use PhpSchool\PhpWorkshop\Input\Input; @@ -12,15 +13,8 @@ class CustomVerifyingRunnerTest extends TestCase { - /** - * @var CustomVerifyingRunner - */ - private $runner; - - /** - * @var CustomVerifyingExerciseImpl - */ - private $exercise; + private CustomVerifyingRunner $runner; + private CustomVerifyingExerciseImpl $exercise; public function setUp(): void { @@ -46,11 +40,13 @@ public function testRunOutputsErrorMessage(): void $this->expectOutputString($exp); - $this->runner->run(new Input('app'), $output); + $this->runner->run(new TestContext(), $output); } public function testVerifyProxiesToExercise(): void { - self::assertEquals($this->exercise->verify(), $this->runner->verify(new Input('app'))); + $result = $this->runner->verify(new TestContext()); + + self::assertEquals($this->exercise->verify(), $result); } } diff --git a/test/ExerciseRunner/Factory/CgiRunnerFactoryTest.php b/test/ExerciseRunner/Factory/CgiRunnerFactoryTest.php index 9b334eae..29c32a75 100644 --- a/test/ExerciseRunner/Factory/CgiRunnerFactoryTest.php +++ b/test/ExerciseRunner/Factory/CgiRunnerFactoryTest.php @@ -7,6 +7,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CgiRunnerFactory; use PhpSchool\PhpWorkshop\Process\HostProcessFactory; use PhpSchool\PhpWorkshop\Utils\RequestRenderer; @@ -21,7 +22,7 @@ class CgiRunnerFactoryTest extends TestCase public function setUp(): void { $this->eventDispatcher = $this->createMock(EventDispatcher::class); - $this->factory = new CgiRunnerFactory($this->eventDispatcher, new HostProcessFactory()); + $this->factory = new CgiRunnerFactory($this->eventDispatcher, new HostProcessFactory(), $this->createMock(EnvironmentManager::class)); } public function testSupports(): void diff --git a/test/ExerciseRunner/Factory/CliRunnerFactoryTest.php b/test/ExerciseRunner/Factory/CliRunnerFactoryTest.php index 8fc5eb58..ce5fe6f6 100644 --- a/test/ExerciseRunner/Factory/CliRunnerFactoryTest.php +++ b/test/ExerciseRunner/Factory/CliRunnerFactoryTest.php @@ -7,6 +7,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner; +use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager; use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CliRunnerFactory; use PhpSchool\PhpWorkshop\Process\HostProcessFactory; use PhpSchool\PhpWorkshopTest\Asset\CliExerciseImpl; @@ -20,7 +21,7 @@ class CliRunnerFactoryTest extends TestCase public function setUp(): void { $this->eventDispatcher = $this->createMock(EventDispatcher::class); - $this->factory = new CliRunnerFactory($this->eventDispatcher, new HostProcessFactory()); + $this->factory = new CliRunnerFactory($this->eventDispatcher, new HostProcessFactory(), $this->createMock(EnvironmentManager::class)); } public function testSupports(): void