diff --git a/src/Exercise/AbstractExercise.php b/src/Exercise/AbstractExercise.php index ff790419..87a0b241 100644 --- a/src/Exercise/AbstractExercise.php +++ b/src/Exercise/AbstractExercise.php @@ -4,6 +4,8 @@ namespace PhpSchool\PhpWorkshop\Exercise; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Solution\SingleFileSolution; use PhpSchool\PhpWorkshop\Solution\SolutionInterface; @@ -78,12 +80,14 @@ public static function normaliseName(string $name): string } /** - * This method is implemented as empty by default, if you want to add additional checks or listen - * to events, you should override this method. - * - * @param ExerciseDispatcher $dispatcher + * @return list */ - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array + { + return []; + } + + public function defineListeners(EventDispatcher $dispatcher): void { } } diff --git a/src/Exercise/ExerciseInterface.php b/src/Exercise/ExerciseInterface.php index 2a969359..10443374 100644 --- a/src/Exercise/ExerciseInterface.php +++ b/src/Exercise/ExerciseInterface.php @@ -4,6 +4,7 @@ namespace PhpSchool\PhpWorkshop\Exercise; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\ExerciseDispatcher; /** @@ -34,14 +35,16 @@ public function getType(): ExerciseType; public function getProblem(): string; /** - * This is where the exercise specifies the extra checks it may require. It is also - * possible to grab the event dispatcher from the exercise dispatcher and listen to any - * events. This method is automatically invoked just before verifying/running an student's solution - * to an exercise. + * Subscribe to events triggered throughout the verification process + */ + public function defineListeners(EventDispatcher $dispatcher): void; + + /** + * This is where the exercise specifies the extra checks it may require. * - * @param ExerciseDispatcher $dispatcher + * @return array */ - public function configure(ExerciseDispatcher $dispatcher): void; + public function getRequiredChecks(): array; /** * A short description of the exercise. diff --git a/src/Exercise/MockExercise.php b/src/Exercise/MockExercise.php new file mode 100644 index 00000000..a8268785 --- /dev/null +++ b/src/Exercise/MockExercise.php @@ -0,0 +1,28 @@ + + */ + private array $executions = []; + + public function withExecution(RequestInterface $request): self + { + $this->executions[] = $request; + + return $this; + } + + /** + * @return array + */ + public function getExecutions(): array + { + return $this->executions; + } +} diff --git a/src/Exercise/Scenario/CliScenario.php b/src/Exercise/Scenario/CliScenario.php new file mode 100644 index 00000000..88b57d09 --- /dev/null +++ b/src/Exercise/Scenario/CliScenario.php @@ -0,0 +1,31 @@ +> + */ + private array $executions = []; + + /** + * @param array $args + */ + public function withExecution(array $args = []): static + { + $this->executions[] = new Collection($args); + + return $this; + } + + /** + * @return array> + */ + public function getExecutions(): array + { + return $this->executions; + } +} diff --git a/src/Exercise/Scenario/ExerciseScenario.php b/src/Exercise/Scenario/ExerciseScenario.php new file mode 100644 index 00000000..4d6a595a --- /dev/null +++ b/src/Exercise/Scenario/ExerciseScenario.php @@ -0,0 +1,26 @@ + + */ + private array $files = []; + + public function withFile(string $relativeFileName, string $content): static + { + $this->files[$relativeFileName] = $content; + + return $this; + } + + /** + * @return array + */ + public function getFiles(): array + { + return $this->files; + } +} diff --git a/src/ExerciseDispatcher.php b/src/ExerciseDispatcher.php index 6d9348a6..112fd68b 100644 --- a/src/ExerciseDispatcher.php +++ b/src/ExerciseDispatcher.php @@ -129,11 +129,11 @@ public function requireCheck(string $requiredCheck): void */ public function verify(ExerciseInterface $exercise, Input $input): ResultAggregator { - $exercise->configure($this); - $runner = $this->runnerManager->getRunner($exercise); - foreach ($runner->getRequiredChecks() as $requiredCheck) { + $exercise->defineListeners($this->eventDispatcher); + + foreach ([...$runner->getRequiredChecks(), ...$exercise->getRequiredChecks()] as $requiredCheck) { $this->requireCheck($requiredCheck); } @@ -181,7 +181,7 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega */ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output): bool { - $exercise->configure($this); + $exercise->defineListeners($this->eventDispatcher); /** @var PhpLintCheck $lint */ $lint = $this->checkRepository->getByClass(PhpLintCheck::class); diff --git a/src/ExerciseRunner/Context/ExecutionContext.php b/src/ExerciseRunner/Context/ExecutionContext.php new file mode 100644 index 00000000..a107d20f --- /dev/null +++ b/src/ExerciseRunner/Context/ExecutionContext.php @@ -0,0 +1,56 @@ +exercise; + } + + public function getInput(): Input + { + return $this->input; + } + + public function hasStudentSolution(): bool + { + return $this->input->hasArgument('program'); + } + + public function getEntryPoint(): string + { + if (!$this->hasStudentSolution()) { + throw new NoEntryPoint(); + } + + return Path::join( + $this->studentExecutionDirectory, + basename($this->input->getRequiredArgument('program')) + ); + } + + public function getStudentExecutionDirectory(): string + { + return $this->studentExecutionDirectory; + } + + public function getReferenceExecutionDirectory(): string + { + return $this->referenceExecutionDirectory; + } +} diff --git a/src/ExerciseRunner/Context/NoEntryPoint.php b/src/ExerciseRunner/Context/NoEntryPoint.php new file mode 100644 index 00000000..2480feb4 --- /dev/null +++ b/src/ExerciseRunner/Context/NoEntryPoint.php @@ -0,0 +1,13 @@ +exercise = $exercise ?? new MockExercise(); + + $this->filesystem = new Filesystem(); + + if ($studentDirectory === null) { + $studentDirectory = System::randomTempDir(); + } + + parent::__construct( + $studentDirectory, + System::randomTempDir(), + $this->exercise, + $input ? $input : new Input('test', ['program' => 'solution.php']), + ); + } + + public function createStudentSolutionDirectory(): void + { + $this->filesystem->mkdir($this->getStudentExecutionDirectory()); + $this->studentSolutionDirWasCreated = true; + } + + public function createReferenceSolutionDirectory(): void + { + $this->filesystem->mkdir($this->getReferenceExecutionDirectory()); + $this->referenceSolutionDirWasCreated = true; + } + + public function importStudentFileFromString(string $content, string $filename = 'solution.php'): void + { + if (!$this->studentSolutionDirWasCreated) { + throw new RuntimeException( + sprintf('Student execution directory not created. Call %s::createStudentSolutionDirectory() first.', self::class) + ); + } + + file_put_contents(Path::join($this->getStudentExecutionDirectory(), $filename), $content); + } + + public function importReferenceFileFromString(string $content, string $filename = 'solution.php'): void + { + if (!$this->referenceSolutionDirWasCreated) { + throw new RuntimeException( + sprintf('Reference execution directory not created. Call %s::createReferenceSolutionDirectory() first.', self::class) + ); + } + + file_put_contents(Path::join($this->getReferenceExecutionDirectory(), $filename), $content); + } + + public static function fromExerciseAndStudentSolution(ExerciseInterface $exercise, string $file): self + { + if (file_exists($file)) { + $file = (string) realpath($file); + } + + $input = new Input('test', ['program' => $file]); + return new self( + exercise: $exercise, + input: $input, + studentDirectory: dirname($file) + ); + } + + public function __destruct() + { + if ($this->studentSolutionDirWasCreated) { + $this->filesystem->remove($this->getStudentExecutionDirectory()); + } + + if ($this->referenceSolutionDirWasCreated) { + $this->filesystem->remove($this->getReferenceExecutionDirectory()); + } + } +} diff --git a/src/Utils/System.php b/src/Utils/System.php index 5dc80128..eb8da511 100644 --- a/src/Utils/System.php +++ b/src/Utils/System.php @@ -23,4 +23,9 @@ public static function tempDir(string $path = ''): string { return Path::join(self::realpath(sys_get_temp_dir()), 'php-school', $path); } + + public static function randomTempDir(): string + { + return Path::join(self::realpath(sys_get_temp_dir()), 'php-school', bin2hex(random_bytes(4))); + } } diff --git a/test/Asset/CgiExerciseImpl.php b/test/Asset/CgiExerciseImpl.php index 45c78005..d8c23c96 100644 --- a/test/Asset/CgiExerciseImpl.php +++ b/test/Asset/CgiExerciseImpl.php @@ -2,6 +2,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\CgiExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; @@ -62,7 +64,12 @@ public function getType(): ExerciseType return ExerciseType::CGI(); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array + { + return []; + } + + public function defineListeners(EventDispatcher $dispatcher): void { } } diff --git a/test/Asset/CliExerciseImpl.php b/test/Asset/CliExerciseImpl.php index df157189..0fcc9e32 100644 --- a/test/Asset/CliExerciseImpl.php +++ b/test/Asset/CliExerciseImpl.php @@ -2,6 +2,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; @@ -66,7 +68,12 @@ public function getType(): ExerciseType return ExerciseType::CLI(); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array + { + return []; + } + + public function defineListeners(EventDispatcher $dispatcher): void { } } diff --git a/test/Asset/CliExerciseMissingInterface.php b/test/Asset/CliExerciseMissingInterface.php index 5edd2200..21c8d482 100644 --- a/test/Asset/CliExerciseMissingInterface.php +++ b/test/Asset/CliExerciseMissingInterface.php @@ -2,6 +2,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\AbstractExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; @@ -31,4 +33,9 @@ public function getType(): ExerciseType { return ExerciseType::CLI(); } + + public function getRequiredChecks(): array + { + return []; + } } diff --git a/test/Asset/ComposerExercise.php b/test/Asset/ComposerExercise.php index 622a7de8..e7365893 100644 --- a/test/Asset/ComposerExercise.php +++ b/test/Asset/ComposerExercise.php @@ -3,6 +3,7 @@ namespace PhpSchool\PhpWorkshopTest\Asset; use PhpSchool\PhpWorkshop\Check\ComposerCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\ComposerExerciseCheck; @@ -53,8 +54,12 @@ public function getType(): ExerciseType return ExerciseType::CLI(); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array + { + return [ComposerCheck::class]; + } + + public function defineListeners(EventDispatcher $dispatcher): void { - $dispatcher->requireCheck(ComposerCheck::class); } } diff --git a/test/Asset/ExerciseWithInitialCode.php b/test/Asset/ExerciseWithInitialCode.php index 398b3785..49282605 100644 --- a/test/Asset/ExerciseWithInitialCode.php +++ b/test/Asset/ExerciseWithInitialCode.php @@ -2,6 +2,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\ProvidesInitialCode; @@ -41,13 +43,17 @@ public function getType(): ExerciseType // TODO: Implement getType() method. } - public function configure(ExerciseDispatcher $dispatcher): void + public function getInitialCode(): SolutionInterface { - // TODO: Implement configure() method. + return SingleFileSolution::fromFile(__DIR__ . '/initial-code/init-solution.php'); } - public function getInitialCode(): SolutionInterface + public function getRequiredChecks(): array + { + return []; + } + + public function defineListeners(EventDispatcher $dispatcher): void { - return SingleFileSolution::fromFile(__DIR__ . '/initial-code/init-solution.php'); } } diff --git a/test/Asset/FileComparisonExercise.php b/test/Asset/FileComparisonExercise.php index b4ecd9c1..8627a37c 100644 --- a/test/Asset/FileComparisonExercise.php +++ b/test/Asset/FileComparisonExercise.php @@ -3,6 +3,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; use PhpSchool\PhpWorkshop\Check\ComposerCheck; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\FileComparisonExerciseCheck; @@ -66,13 +68,17 @@ public function getType(): ExerciseType return ExerciseType::CLI(); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getFilesToCompare(): array { - $dispatcher->requireCheck(ComposerCheck::class); + return $this->files; } - public function getFilesToCompare(): array + public function getRequiredChecks(): array + { + return [FileComparisonCheck::class]; + } + + public function defineListeners(EventDispatcher $dispatcher): void { - return $this->files; } } diff --git a/test/Asset/FunctionRequirementsExercise.php b/test/Asset/FunctionRequirementsExercise.php index 927d6f56..4e243a3f 100644 --- a/test/Asset/FunctionRequirementsExercise.php +++ b/test/Asset/FunctionRequirementsExercise.php @@ -3,6 +3,9 @@ namespace PhpSchool\PhpWorkshopTest\Asset; use PhpSchool\PhpWorkshop\Check\ComposerCheck; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Check\FunctionRequirementsCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck; @@ -45,11 +48,6 @@ public function getType(): ExerciseType return ExerciseType::CLI(); } - public function configure(ExerciseDispatcher $dispatcher): void - { - $dispatcher->requireCheck(ComposerCheck::class); - } - /** * @return string[] */ @@ -65,4 +63,13 @@ public function getBannedFunctions(): array { return ['file']; } + + public function getRequiredChecks(): array + { + return [FunctionRequirementsCheck::class]; + } + + public function defineListeners(EventDispatcher $dispatcher): void + { + } } diff --git a/test/Asset/PatchableExercise.php b/test/Asset/PatchableExercise.php index 51a9d601..c232113e 100644 --- a/test/Asset/PatchableExercise.php +++ b/test/Asset/PatchableExercise.php @@ -2,6 +2,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\SubmissionPatchable; @@ -45,8 +47,12 @@ public function getType(): ExerciseType // TODO: Implement getType() method. } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array + { + return []; + } + + public function defineListeners(EventDispatcher $dispatcher): void { - // TODO: Implement configure() method. } } diff --git a/test/Asset/ProvidesSolutionExercise.php b/test/Asset/ProvidesSolutionExercise.php index b86f8dd9..503e1bf8 100644 --- a/test/Asset/ProvidesSolutionExercise.php +++ b/test/Asset/ProvidesSolutionExercise.php @@ -4,6 +4,8 @@ namespace PhpSchool\PhpWorkshopTest\Asset; +use PhpSchool\PhpWorkshop\Check\FileComparisonCheck; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution; @@ -28,11 +30,6 @@ public function getProblem(): string // TODO: Implement getProblem() method. } - public function configure(ExerciseDispatcher $dispatcher): void - { - // TODO: Implement configure() method. - } - public function getDescription(): string { // TODO: Implement getDescription() method. @@ -47,4 +44,13 @@ public function getSolution(): SolutionInterface { return SingleFileSolution::fromFile(__DIR__ . '/provided-solution/solution.php'); } + + public function getRequiredChecks(): array + { + return []; + } + + public function defineListeners(EventDispatcher $dispatcher): void + { + } } diff --git a/test/Check/DatabaseCheckTest.php b/test/Check/DatabaseCheckTest.php index 14629e90..da96cd7f 100644 --- a/test/Check/DatabaseCheckTest.php +++ b/test/Check/DatabaseCheckTest.php @@ -131,10 +131,8 @@ public function testIfPDOThrowsExceptionItCleansUp(): void $this->exercise ->expects($this->once()) - ->method('configure') - ->willReturnCallback(function (ExerciseDispatcher $dispatcher) { - $dispatcher->requireCheck(DatabaseCheck::class); - }); + ->method('getRequiredChecks') + ->willReturn([DatabaseCheck::class]); $this->exercise ->expects($this->once()) @@ -172,10 +170,8 @@ public function testSuccessIsReturnedIfDatabaseVerificationPassed(): void $this->exercise ->expects($this->once()) - ->method('configure') - ->willReturnCallback(function (ExerciseDispatcher $dispatcher) { - $dispatcher->requireCheck(DatabaseCheck::class); - }); + ->method('getRequiredChecks') + ->willReturn([DatabaseCheck::class]); $this->exercise ->expects($this->once()) @@ -207,13 +203,6 @@ public function testRunExercise(): void ->method('getArgs') ->willReturn([]); - $this->exercise - ->expects($this->once()) - ->method('configure') - ->willReturnCallback(function (ExerciseDispatcher $dispatcher) { - $dispatcher->requireCheck(DatabaseCheck::class); - }); - $this->checkRepository->registerCheck($this->check); $results = new ResultAggregator(); @@ -248,10 +237,8 @@ public function testFailureIsReturnedIfDatabaseVerificationFails(): void $this->exercise ->expects($this->once()) - ->method('configure') - ->willReturnCallback(function (ExerciseDispatcher $dispatcher) { - $dispatcher->requireCheck(DatabaseCheck::class); - }); + ->method('getRequiredChecks') + ->willReturn([DatabaseCheck::class]); $this->exercise ->expects($this->once()) @@ -296,10 +283,8 @@ public function testAlteringDatabaseInSolutionDoesNotEffectDatabaseInUserSolutio $this->exercise ->expects($this->once()) - ->method('configure') - ->willReturnCallback(function (ExerciseDispatcher $dispatcher) { - $dispatcher->requireCheck(DatabaseCheck::class); - }); + ->method('getRequiredChecks') + ->willReturn([DatabaseCheck::class]); $this->exercise ->expects($this->once()) diff --git a/test/Exercise/AbstractExerciseTest.php b/test/Exercise/AbstractExerciseTest.php index 6b2d722c..c500b0f7 100644 --- a/test/Exercise/AbstractExerciseTest.php +++ b/test/Exercise/AbstractExerciseTest.php @@ -2,6 +2,7 @@ namespace PhpSchool\PhpWorkshopTest\Exercise; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Solution\SolutionFile; use PhpSchool\PhpWorkshop\Solution\SolutionInterface; @@ -65,11 +66,11 @@ public function problemProvider(): array ]; } - public function testConfigureDoesNothing(): void + public function testDefineListenersDoesNothing(): void { - $dispatcher = $this->createMock(ExerciseDispatcher::class); + $dispatcher = $this->createMock(EventDispatcher::class); $exercise = new AbstractExerciseImpl('Array We Go'); - $this->assertNull($exercise->configure($dispatcher)); + $this->assertNull($exercise->defineListeners($dispatcher)); } } diff --git a/test/Exercise/Scenario/CgiScenarioTest.php b/test/Exercise/Scenario/CgiScenarioTest.php new file mode 100644 index 00000000..4c0782ca --- /dev/null +++ b/test/Exercise/Scenario/CgiScenarioTest.php @@ -0,0 +1,39 @@ +createMock(RequestInterface::class); + $requestTwo = $this->createMock(RequestInterface::class); + + $scenario = (new CgiScenario()) + ->withFile('file1.txt', 'content1') + ->withFile('file2.txt', 'content2') + ->withExecution($requestOne) + ->withExecution($requestTwo); + + static::assertEquals( + [ + 'file1.txt' => 'content1', + 'file2.txt' => 'content2', + ], + $scenario->getFiles() + ); + + static::assertEquals( + [ + $requestOne, + $requestTwo + ], + $scenario->getExecutions() + ); + } +} diff --git a/test/Exercise/Scenario/CliScenarioTest.php b/test/Exercise/Scenario/CliScenarioTest.php new file mode 100644 index 00000000..1ea86a9a --- /dev/null +++ b/test/Exercise/Scenario/CliScenarioTest.php @@ -0,0 +1,38 @@ +withFile('file1.txt', 'content1') + ->withFile('file2.txt', 'content2') + ->withExecution(['arg1', 'arg2']) + ->withExecution(['arg3', 'arg4']); + + static::assertEquals( + [ + 'file1.txt' => 'content1', + 'file2.txt' => 'content2', + ], + $scenario->getFiles() + ); + + static::assertEquals( + [ + ['arg1', 'arg2'], + ['arg3', 'arg4'], + ], + array_map( + fn (Collection $collection) => $collection->getArrayCopy(), + $scenario->getExecutions() + ) + ); + } +} diff --git a/test/ExerciseRunner/Context/ExecutionContextTest.php b/test/ExerciseRunner/Context/ExecutionContextTest.php new file mode 100644 index 00000000..de3d2db6 --- /dev/null +++ b/test/ExerciseRunner/Context/ExecutionContextTest.php @@ -0,0 +1,84 @@ + 'solution.php']); + $context = new ExecutionContext( + '/student-dir', + '/reference-dir', + $exercise, + $input + ); + + static::assertSame($exercise, $context->getExercise()); + static::assertSame($input, $context->getInput()); + static::assertSame('/student-dir', $context->getStudentExecutionDirectory()); + static::assertSame('/reference-dir', $context->getReferenceExecutionDirectory()); + } + + public function testHasStudentSolution(): void + { + $exercise = new MockExercise(); + $input = new Input('test', ['program' => 'solution.php']); + $context = new ExecutionContext( + '/student-dir', + '/reference-dir', + $exercise, + $input + ); + + static::assertTrue($context->hasStudentSolution()); + + $exercise = new MockExercise(); + $input = new Input('test'); + $context = new ExecutionContext( + '/student-dir', + '/reference-dir', + $exercise, + $input + ); + + static::assertFalse($context->hasStudentSolution()); + } + + public function testGetEntryPoint(): void + { + $exercise = new MockExercise(); + $input = new Input('test', ['program' => 'solution.php']); + $context = new ExecutionContext( + '/student-dir', + '/reference-dir', + $exercise, + $input + ); + + static::assertSame('/student-dir/solution.php', $context->getEntryPoint()); + } + + public function testGetEntryPointThrowsExceptionWhenNoStudentSolution(): void + { + static::expectException(NoEntryPoint::class); + + $exercise = new MockExercise(); + $input = new Input('test'); + $context = new ExecutionContext( + '/student-dir', + '/reference-dir', + $exercise, + $input + ); + + $context->getEntryPoint(); + } +} diff --git a/test/ExerciseRunner/Context/NoEntryPointTest.php b/test/ExerciseRunner/Context/NoEntryPointTest.php new file mode 100644 index 00000000..324119ff --- /dev/null +++ b/test/ExerciseRunner/Context/NoEntryPointTest.php @@ -0,0 +1,15 @@ +getMessage()); + } +} diff --git a/test/ExerciseRunner/Context/TestContextTest.php b/test/ExerciseRunner/Context/TestContextTest.php new file mode 100644 index 00000000..ac5ee0a9 --- /dev/null +++ b/test/ExerciseRunner/Context/TestContextTest.php @@ -0,0 +1,115 @@ +getStudentExecutionDirectory()); + static::assertFileNotExists($context->getReferenceExecutionDirectory()); + } + + public function testCreateDirectories(): void + { + $context = new TestContext(); + $context->createStudentSolutionDirectory(); + $context->createReferenceSolutionDirectory(); + + static::assertFileExists($context->getStudentExecutionDirectory()); + static::assertFileExists($context->getReferenceExecutionDirectory()); + } + + public function testFromExerciseAndSolutionFactory(): void + { + $exercise = new MockExercise(); + $context = TestContext::fromExerciseAndStudentSolution($exercise, __FILE__); + + static::assertSame(__DIR__, $context->getStudentExecutionDirectory()); + static::assertSame(__FILE__, $context->getInput()->getRequiredArgument('program')); + static::assertSame($exercise, $context->getExercise()); + } + + public function testImportStudentSolutionFileFromStringThrowsExceptionIfExecutionDirectoryDoesNotExist(): void + { + $this->expectException(\RuntimeException::class); + + $context = new TestContext(); + $context->importStudentFileFromString('createStudentSolutionDirectory(); + $context->importStudentFileFromString('getStudentExecutionDirectory() . '/solution.php'); + static::assertEquals('getStudentExecutionDirectory() . '/solution.php')); + } + + public function testImportStudentSolutionFileFromStringWithCustomPathCreatesFileInExecutionDirectory(): void + { + $context = new TestContext(); + $context->createStudentSolutionDirectory(); + $context->importStudentFileFromString('getStudentExecutionDirectory() . '/some-file.php'); + static::assertEquals('getStudentExecutionDirectory() . '/some-file.php')); + } + + public function testImportReferenceSolutionFileFromStringThrowsExceptionIfExecutionDirectoryDoesNotExist(): void + { + $this->expectException(\RuntimeException::class); + + $context = new TestContext(); + $context->importReferenceFileFromString('createReferenceSolutionDirectory(); + $context->importReferenceFileFromString('getReferenceExecutionDirectory() . '/solution.php'); + static::assertEquals('getReferenceExecutionDirectory() . '/solution.php')); + } + + public function testImportReferenceSolutionFileFromStringWithCustomPathCreatesFileInExecutionDirectory(): void + { + $context = new TestContext(); + $context->createReferenceSolutionDirectory(); + $context->importReferenceFileFromString('getReferenceExecutionDirectory() . '/some-file.php'); + static::assertEquals('getReferenceExecutionDirectory() . '/some-file.php')); + } + + public function testDestructCleansUpExecutionDirectories(): void + { + $context = new TestContext(); + $context->createStudentSolutionDirectory(); + $context->createReferenceSolutionDirectory(); + + $studentExecutionDirectory = $context->getStudentExecutionDirectory(); + $referenceExecutionDirectory = $context->getReferenceExecutionDirectory(); + + static::assertFileExists($studentExecutionDirectory); + static::assertFileExists($referenceExecutionDirectory); + + unset($context); + + static::assertFileNotExists($studentExecutionDirectory); + static::assertFileNotExists($referenceExecutionDirectory); + } +} diff --git a/test/Utils/SystemTest.php b/test/Utils/SystemTest.php index 96759cd6..69fd2a0a 100644 --- a/test/Utils/SystemTest.php +++ b/test/Utils/SystemTest.php @@ -33,4 +33,9 @@ public function testTempDirWithPath(): void $expect = sprintf('%s/php-school/%s', realpath(sys_get_temp_dir()), 'test'); self::assertSame($expect, System::tempDir('test')); } + + public function testRandomTempDir(): void + { + self::assertTrue(str_starts_with(System::randomTempDir(), realpath(sys_get_temp_dir()) . '/php-school')); + } }

AltStyle によって変換されたページ (->オリジナル) /