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 @@ +hasArgument('program') ? dirname($input->getRequiredArgument('program')) : (string) getcwd(); + + return new self( + $program, + System::randomTempDir(), + $exercise, + $input + ); + } + + public function getExercise(): ExerciseInterface + { + return $this->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/ExerciseRunner/Context/ExecutionContextTest.php b/test/ExerciseRunner/Context/ExecutionContextTest.php new file mode 100644 index 00000000..016b29af --- /dev/null +++ b/test/ExerciseRunner/Context/ExecutionContextTest.php @@ -0,0 +1,102 @@ + '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(); + } + + public function testFactory(): void + { + $temporaryDirectory = System::randomTempDir(); + + $input = new Input('test', ['program' => $temporaryDirectory . '/solution.php']); + $exercise = new MockExercise(); + + $context = ExecutionContext::fromInputAndExercise($input, $exercise); + + //check that student execution directory uses the parent directory of the program from the input + static::assertSame($temporaryDirectory, $context->getStudentExecutionDirectory()); + static::assertSame($temporaryDirectory . '/solution.php', $context->getEntryPoint()); + + //check that reference execution directory is a random temporary directory + static::assertTrue(str_starts_with($context->getReferenceExecutionDirectory(), System::tempDir())); + } +} 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')); + } }