diff --git a/src/Check/CodeExistsCheck.php b/src/Check/CodeExistsCheck.php index 859e3d29..79067833 100644 --- a/src/Check/CodeExistsCheck.php +++ b/src/Check/CodeExistsCheck.php @@ -11,6 +11,7 @@ use PhpParser\Parser; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\ResultInterface; @@ -18,14 +19,8 @@ class CodeExistsCheck implements SimpleCheckInterface { - /** - * @var Parser - */ - private $parser; - - public function __construct(Parser $parser) + public function __construct(private Parser $parser) { - $this->parser = $parser; } public function getName(): string @@ -34,10 +29,12 @@ public function getName(): string } /** + * @param ExecutionContext $context The current execution context, containing the exercise, input and working directories. + * * Check solution provided contains code * Note: We don't care if it's valid code at this point */ - public function check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { $noopHandler = new class implements ErrorHandler { public function handleError(Error $error): void @@ -45,7 +42,7 @@ public function handleError(Error $error): void } }; - $code = (string) file_get_contents($input->getRequiredArgument('program')); + $code = (string) file_get_contents($context->getEntryPoint()); $statements = $this->parser->parse($code, $noopHandler); $empty = null === $statements || empty($statements); diff --git a/src/Check/CodeParseCheck.php b/src/Check/CodeParseCheck.php index 670ec1b1..4be59113 100644 --- a/src/Check/CodeParseCheck.php +++ b/src/Check/CodeParseCheck.php @@ -8,6 +8,7 @@ use PhpParser\Parser; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\ResultInterface; @@ -19,17 +20,8 @@ */ class CodeParseCheck implements SimpleCheckInterface { - /** - * @var Parser - */ - private $parser; - - /** - * @param Parser $parser - */ - public function __construct(Parser $parser) + public function __construct(private Parser $parser) { - $this->parser = $parser; } /** @@ -45,18 +37,17 @@ public function getName(): string * attempts to parse it with `nikic/php-parser`. If any exceptions are thrown * by the parser, it is treated as a failure. * - * @param ExerciseInterface $exercise The exercise to check against. - * @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 check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { - $code = (string) file_get_contents($input->getRequiredArgument('program')); + $code = (string) file_get_contents($context->getEntryPoint()); try { $this->parser->parse($code); } catch (Error $e) { - return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getRequiredArgument('program')); + return Failure::fromCheckAndCodeParseFailure($this, $e, $context->getEntryPoint()); } return Success::fromCheck($this); diff --git a/src/Check/ComposerCheck.php b/src/Check/ComposerCheck.php index 6711f1b2..9e099082 100644 --- a/src/Check/ComposerCheck.php +++ b/src/Check/ComposerCheck.php @@ -9,6 +9,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\ComposerExerciseCheck; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\ComposerFailure; use PhpSchool\PhpWorkshop\Result\Failure; @@ -34,30 +35,29 @@ public function getName(): string * installed a set of required packages. If they did not a failure is returned, otherwise, * a success is returned. * - * @param ExerciseInterface $exercise The exercise to check against. - * @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. - * @noinspection SpellCheckingInspection */ - public function check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { + $exercise = $context->getExercise(); if (!$exercise instanceof ComposerExerciseCheck) { throw new InvalidArgumentException(); } - if (!file_exists(sprintf('%s/composer.json', dirname($input->getRequiredArgument('program'))))) { + if (!file_exists(sprintf('%s/composer.json', $context->getStudentExecutionDirectory()))) { return ComposerFailure::fromCheckAndMissingFileOrFolder($this, 'composer.json'); } - if (!file_exists(sprintf('%s/composer.lock', dirname($input->getRequiredArgument('program'))))) { + if (!file_exists(sprintf('%s/composer.lock', $context->getStudentExecutionDirectory()))) { return ComposerFailure::fromCheckAndMissingFileOrFolder($this, 'composer.lock'); } - if (!file_exists(sprintf('%s/vendor', dirname($input->getRequiredArgument('program'))))) { + if (!file_exists(sprintf('%s/vendor', $context->getStudentExecutionDirectory()))) { return ComposerFailure::fromCheckAndMissingFileOrFolder($this, 'vendor'); } - $lockFile = new LockFileParser(sprintf('%s/composer.lock', dirname($input->getRequiredArgument('program')))); + $lockFile = new LockFileParser(sprintf('%s/composer.lock', $context->getStudentExecutionDirectory())); $missingPackages = array_filter($exercise->getRequiredPackages(), function ($package) use ($lockFile) { return !$lockFile->hasInstalledPackage($package); }); diff --git a/src/Check/DatabaseCheck.php b/src/Check/DatabaseCheck.php index 0a55f4ec..658865b0 100644 --- a/src/Check/DatabaseCheck.php +++ b/src/Check/DatabaseCheck.php @@ -25,30 +25,11 @@ class DatabaseCheck implements ListenableCheckInterface { use TemporaryDirectoryTrait; - /** - * @var string - */ - private $databaseDirectory; - - /** - * @var string - */ - private $userDatabasePath; - - /** - * @var string - */ - private $solutionDatabasePath; - - /** - * @var string - */ - private $userDsn; - - /** - * @var string - */ - private $solutionDsn; + private string $databaseDirectory; + private string $userDatabasePath; + private string $solutionDatabasePath; + private string $userDsn; + private string $solutionDsn; /** * Setup paths and DSN's. diff --git a/src/Check/FileComparisonCheck.php b/src/Check/FileComparisonCheck.php index 3acefd68..87bf5ddb 100644 --- a/src/Check/FileComparisonCheck.php +++ b/src/Check/FileComparisonCheck.php @@ -10,6 +10,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution; use PhpSchool\PhpWorkshop\ExerciseCheck\FileComparisonExerciseCheck; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\FileComparisonFailure; @@ -34,12 +35,12 @@ public function getName(): string /** * Simply check that the file exists. * - * @param ExerciseInterface&ProvidesSolution $exercise The exercise to check against. - * @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 check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { + $exercise = $context->getExercise(); if (!$exercise instanceof FileComparisonExerciseCheck) { throw new InvalidArgumentException(); } @@ -47,8 +48,8 @@ public function check(ExerciseInterface $exercise, Input $input): ResultInterfac foreach ($exercise->getFilesToCompare() as $key => $file) { [$options, $file] = $this->getOptionsAndFile($key, $file); - $studentFile = Path::join(dirname($input->getRequiredArgument('program')), $file); - $referenceFile = Path::join($exercise->getSolution()->getBaseDirectory(), $file); + $studentFile = Path::join($context->getStudentExecutionDirectory(), $file); + $referenceFile = Path::join($context->getReferenceExecutionDirectory(), $file); if (!file_exists($referenceFile)) { throw SolutionFileDoesNotExistException::fromExpectedFile($file); diff --git a/src/Check/FileExistsCheck.php b/src/Check/FileExistsCheck.php index 5dba56e8..b8a9ee25 100644 --- a/src/Check/FileExistsCheck.php +++ b/src/Check/FileExistsCheck.php @@ -6,6 +6,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\ResultInterface; @@ -27,19 +28,18 @@ public function getName(): string /** * Simply check that the file exists. * - * @param ExerciseInterface $exercise The exercise to check against. - * @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 check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { - if (file_exists($input->getRequiredArgument('program'))) { + if (file_exists($context->getEntryPoint())) { return Success::fromCheck($this); } return Failure::fromCheckAndReason( $this, - sprintf('File: "%s" does not exist', $input->getRequiredArgument('program')) + sprintf('File: "%s" does not exist', $context->getEntryPoint()) ); } diff --git a/src/Check/FunctionRequirementsCheck.php b/src/Check/FunctionRequirementsCheck.php index 4c14f851..d644a661 100644 --- a/src/Check/FunctionRequirementsCheck.php +++ b/src/Check/FunctionRequirementsCheck.php @@ -12,6 +12,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\NodeVisitor\FunctionVisitor; use PhpSchool\PhpWorkshop\Result\Failure; @@ -25,17 +26,8 @@ */ class FunctionRequirementsCheck implements SimpleCheckInterface { - /** - * @var Parser - */ - private $parser; - - /** - * @param Parser $parser - */ - public function __construct(Parser $parser) + public function __construct(private Parser $parser) { - $this->parser = $parser; } /** @@ -51,12 +43,12 @@ public function getName(): string * required functions and that banned functions are not used. The requirements * are pulled from the exercise. * - * @param ExerciseInterface $exercise The exercise to check against. - * @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 check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { + $exercise = $context->getExercise(); if (!$exercise instanceof FunctionRequirementsExerciseCheck) { throw new InvalidArgumentException(); } @@ -64,12 +56,12 @@ public function check(ExerciseInterface $exercise, Input $input): ResultInterfac $requiredFunctions = $exercise->getRequiredFunctions(); $bannedFunctions = $exercise->getBannedFunctions(); - $code = (string) file_get_contents($input->getRequiredArgument('program')); + $code = (string) file_get_contents($context->getEntryPoint()); try { $ast = $this->parser->parse($code) ?? []; } catch (Error $e) { - return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getRequiredArgument('program')); + return Failure::fromCheckAndCodeParseFailure($this, $e, $context->getEntryPoint()); } $visitor = new FunctionVisitor($requiredFunctions, $bannedFunctions); diff --git a/src/Check/PhpLintCheck.php b/src/Check/PhpLintCheck.php index c90c3fbd..f05e427d 100644 --- a/src/Check/PhpLintCheck.php +++ b/src/Check/PhpLintCheck.php @@ -6,6 +6,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\ResultInterface; use PhpSchool\PhpWorkshop\Result\Success; @@ -30,14 +31,13 @@ public function getName(): string /** * Simply check the student's solution can be linted with `php -l`. * - * @param ExerciseInterface $exercise The exercise to check against. - * @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 check(ExerciseInterface $exercise, Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { $finder = new ExecutableFinder(); - $process = new Process([$finder->find('php'), '-l', $input->getArgument('program')]); + $process = new Process([$finder->find('php'), '-l', $context->getEntryPoint()]); $process->run(); if ($process->isSuccessful()) { diff --git a/src/Check/SimpleCheckInterface.php b/src/Check/SimpleCheckInterface.php index 5b4bf78a..b1fbf63e 100644 --- a/src/Check/SimpleCheckInterface.php +++ b/src/Check/SimpleCheckInterface.php @@ -6,6 +6,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\ResultInterface; @@ -46,11 +47,10 @@ public function canRun(ExerciseType $exerciseType): bool; * successful then an instance of `PhpSchool\PhpWorkshop\Result\FailureInterface` * should be returned. * - * @param ExerciseInterface $exercise The exercise to check against. - * @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 check(ExerciseInterface $exercise, Input $input): ResultInterface; + public function check(ExecutionContext $context): ResultInterface; /** * Either `static::CHECK_BEFORE` | `static::CHECK_AFTER`. diff --git a/src/ExerciseDispatcher.php b/src/ExerciseDispatcher.php index 5e9c5b30..a9004aa5 100644 --- a/src/ExerciseDispatcher.php +++ b/src/ExerciseDispatcher.php @@ -123,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($context->getExercise(), $context->getInput())); + $this->results->add($check->check($context)); if (!$this->results->isSuccessful()) { return $this->results; @@ -139,7 +139,7 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega } foreach ($this->checksToRunAfter as $check) { - $this->results->add($check->check($context->getExercise(), $context->getInput())); + $this->results->add($check->check($context)); } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.check', $exercise, $input)); @@ -167,7 +167,7 @@ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $ /** @var PhpLintCheck $lint */ $lint = $this->checkRepository->getByClass(PhpLintCheck::class); - $result = $lint->check($context->getExercise(), $context->getInput()); + $result = $lint->check($context); if ($result instanceof FailureInterface) { throw CouldNotRunException::fromFailure($result); diff --git a/test/Check/CodeExistsCheckTest.php b/test/Check/CodeExistsCheckTest.php index aee1e8ef..5998b0da 100644 --- a/test/Check/CodeExistsCheckTest.php +++ b/test/Check/CodeExistsCheckTest.php @@ -6,81 +6,55 @@ use PhpSchool\PhpWorkshop\Check\CodeExistsCheck; use PhpSchool\PhpWorkshop\Check\SimpleCheckInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; +use PhpSchool\PhpWorkshopTest\BaseTest; use PHPUnit\Framework\TestCase; use PhpSchool\PhpWorkshop\Check\FileExistsCheck; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\Success; -class CodeExistsCheckTest extends TestCase +class CodeExistsCheckTest extends BaseTest { - /** - * @var string - */ - private $testDir; - - /** - * @var FileExistsCheck - */ - private $check; - - /** - * @var ExerciseInterface - */ - private $exercise; - - /** - * @var string - */ - private $file; + private CodeExistsCheck $check; public function setUp(): void { - $this->testDir = sprintf( - '%s/%s/%s', - str_replace('\\', '/', sys_get_temp_dir()), - basename(str_replace('\\', '/', get_class($this))), - $this->getName() - ); - - mkdir($this->testDir, 0777, true); $this->check = new CodeExistsCheck((new ParserFactory())->create(ParserFactory::PREFER_PHP7)); - $this->exercise = $this->createMock(ExerciseInterface::class); + } + + public function testCheckMeta(): void + { $this->assertEquals('Code Exists Check', $this->check->getName()); $this->assertEquals(ExerciseInterface::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_BEFORE, $this->check->getPosition()); $this->assertTrue($this->check->canRun(ExerciseType::CGI())); $this->assertTrue($this->check->canRun(ExerciseType::CLI())); - - $this->file = sprintf('%s/submission.php', $this->testDir); - touch($this->file); } public function testSuccess(): void { - file_put_contents($this->file, 'createStudentSolutionDirectory(); + $context->importStudentFileFromString('assertInstanceOf( Success::class, - $this->check->check($this->exercise, new Input('app', ['program' => $this->file])) + $this->check->check($context) ); } public function testFailure(): void { - file_put_contents($this->file, 'createStudentSolutionDirectory(); + $context->importStudentFileFromString('check->check($this->exercise, new Input('app', ['program' => $this->file])); + $failure = $this->check->check($context); $this->assertInstanceOf(Failure::class, $failure); $this->assertEquals('No code was found', $failure->getReason()); } - - public function tearDown(): void - { - unlink($this->file); - rmdir($this->testDir); - } } diff --git a/test/Check/CodeParseCheckTest.php b/test/Check/CodeParseCheckTest.php index 2c313cc8..f9614628 100644 --- a/test/Check/CodeParseCheckTest.php +++ b/test/Check/CodeParseCheckTest.php @@ -7,9 +7,12 @@ use PhpSchool\PhpWorkshop\Check\SimpleCheckInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\Success; +use PhpSchool\PhpWorkshop\Utils\Path; +use PhpSchool\PhpWorkshopTest\BaseTest; use PHPUnit\Framework\TestCase; use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; @@ -17,64 +20,53 @@ class CodeParseCheckTest extends TestCase { use AssertionRenames; - /** - * @var SimpleCheckInterface - */ - private $check; - - /** - * @var string - */ - private $file; + private CodeParseCheck $check; public function setUp(): void { $this->check = new CodeParseCheck((new ParserFactory())->create(ParserFactory::PREFER_PHP7)); + } + + public function testCheckMeta(): void + { $this->assertEquals('Code Parse Check', $this->check->getName()); $this->assertEquals(ExerciseInterface::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_BEFORE, $this->check->getPosition()); $this->assertTrue($this->check->canRun(ExerciseType::CGI())); $this->assertTrue($this->check->canRun(ExerciseType::CLI())); - - $this->file = sprintf('%s/%s/submission.php', str_replace('\\', '/', sys_get_temp_dir()), $this->getName()); - mkdir(dirname($this->file), 0775, true); - touch($this->file); } public function testUnParseableCodeReturnsFailure(): void { - file_put_contents($this->file, 'createStudentSolutionDirectory(); + $context->importStudentFileFromString('check->check( - $this->createMock(ExerciseInterface::class), - new Input('app', ['program' => $this->file]) - ); + $result = $this->check->check($context); $this->assertInstanceOf(Failure::class, $result); $this->assertEquals('Code Parse Check', $result->getCheckName()); $this->assertMatchesRegularExpression( - sprintf('|^File: "%s" could not be parsed\. Error: "|', preg_quote($this->file)), + sprintf( + '|^File: "%s" could not be parsed\. Error: "|', + preg_quote( + Path::join($context->getStudentExecutionDirectory(), 'solution.php') + ) + ), $result->getReason() ); } public function testParseableCodeReturnsSuccess(): void { - file_put_contents($this->file, 'createStudentSolutionDirectory(); + $context->importStudentFileFromString('check->check( - $this->createMock(ExerciseInterface::class), - new Input('app', ['program' => $this->file]) - ); + $result = $this->check->check($context); $this->assertInstanceOf(Success::class, $result); $this->assertEquals('Code Parse Check', $result->getCheckName()); } - - public function tearDown(): void - { - unlink($this->file); - rmdir(dirname($this->file)); - } } diff --git a/test/Check/ComposerCheckTest.php b/test/Check/ComposerCheckTest.php index 45b36931..bf2b6446 100644 --- a/test/Check/ComposerCheckTest.php +++ b/test/Check/ComposerCheckTest.php @@ -8,6 +8,7 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\ComposerExerciseCheck; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\ComposerFailure; use PhpSchool\PhpWorkshop\Result\Failure; @@ -17,20 +18,17 @@ class ComposerCheckTest extends TestCase { - /** - * @var ComposerCheck - */ - private $check; - - /** - * @var ExerciseInterface - */ - private $exercise; + private ComposerCheck $check; + private ComposerExercise $exercise; public function setUp(): void { $this->check = new ComposerCheck(); $this->exercise = new ComposerExercise(); + } + + public function testCheckMeta(): void + { $this->assertEquals('Composer Dependency Check', $this->check->getName()); $this->assertEquals(ComposerExerciseCheck::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_BEFORE, $this->check->getPosition()); @@ -44,14 +42,13 @@ public function testExceptionIsThrownIfNotValidExercise(): void $exercise = $this->createMock(ExerciseInterface::class); $this->expectException(InvalidArgumentException::class); - $this->check->check($exercise, new Input('app')); + $this->check->check(new TestContext()); } public function testCheckReturnsFailureIfNoComposerFile(): void { $result = $this->check->check( - $this->exercise, - new Input('app', ['program' => 'invalid/solution']) + new TestContext($this->exercise) ); $this->assertInstanceOf(ComposerFailure::class, $result); @@ -62,11 +59,13 @@ public function testCheckReturnsFailureIfNoComposerFile(): void public function testCheckReturnsFailureIfNoComposerLockFile(): void { - $result = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $this->exercise, - new Input('app', ['program' => __DIR__ . '/../res/composer/not-locked/solution.php']) + __DIR__ . '/../res/composer/not-locked/solution.php' ); + $result = $this->check->check($context); + $this->assertInstanceOf(ComposerFailure::class, $result); $this->assertSame('Composer Dependency Check', $result->getCheckName()); $this->assertTrue($result->isMissingComponent()); @@ -75,11 +74,13 @@ public function testCheckReturnsFailureIfNoComposerLockFile(): void public function testCheckReturnsFailureIfNoVendorFolder(): void { - $result = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $this->exercise, - new Input('app', ['program' => __DIR__ . '/../res/composer/no-vendor/solution.php']) + __DIR__ . '/../res/composer/no-vendor/solution.php' ); + $result = $this->check->check($context); + $this->assertInstanceOf(ComposerFailure::class, $result); $this->assertSame('Composer Dependency Check', $result->getCheckName()); $this->assertTrue($result->isMissingComponent()); @@ -88,9 +89,6 @@ public function testCheckReturnsFailureIfNoVendorFolder(): void /** * @dataProvider dependencyProvider - * - * @param string $dependency - * @param string $solutionFile */ public function testCheckReturnsFailureIfDependencyNotRequired(string $dependency, string $solutionFile): void { @@ -99,7 +97,9 @@ public function testCheckReturnsFailureIfDependencyNotRequired(string $dependenc ->method('getRequiredPackages') ->willReturn([$dependency]); - $result = $this->check->check($exercise, new Input('app', ['program' => $solutionFile])); + $context = TestContext::fromExerciseAndStudentSolution($exercise, $solutionFile); + + $result = $this->check->check($context); $this->assertInstanceOf(ComposerFailure::class, $result); $this->assertSame('Composer Dependency Check', $result->getCheckName()); @@ -120,11 +120,13 @@ public function dependencyProvider(): array public function testCheckReturnsSuccessIfCorrectLockFile(): void { - $result = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $this->exercise, - new Input('app', ['program' => __DIR__ . '/../res/composer/good-solution/solution.php']) + __DIR__ . '/../res/composer/good-solution/solution.php' ); + $result = $this->check->check($context); + $this->assertInstanceOf(Success::class, $result); $this->assertSame('Composer Dependency Check', $result->getCheckName()); } diff --git a/test/Check/FileComparisonCheckTest.php b/test/Check/FileComparisonCheckTest.php index 78f61a82..b2db8b7a 100644 --- a/test/Check/FileComparisonCheckTest.php +++ b/test/Check/FileComparisonCheckTest.php @@ -7,24 +7,28 @@ use PhpSchool\PhpWorkshop\Exception\SolutionFileDoesNotExistException; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseCheck\FileComparisonExerciseCheck; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\FileComparisonFailure; use PhpSchool\PhpWorkshop\Solution\SingleFileSolution; +use PhpSchool\PhpWorkshop\Solution\SolutionInterface; use PhpSchool\PhpWorkshopTest\Asset\FileComparisonExercise; use PhpSchool\PhpWorkshopTest\BaseTest; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\Success; +use PHPUnit\Framework\TestCase; -class FileComparisonCheckTest extends BaseTest +class FileComparisonCheckTest extends TestCase { - /** - * @var FileComparisonCheck - */ - private $check; + private FileComparisonCheck $check; public function setUp(): void { $this->check = new FileComparisonCheck(); + } + + public function testCheckMeta(): void + { $this->assertEquals('File Comparison Check', $this->check->getName()); $this->assertEquals(FileComparisonExerciseCheck::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_AFTER, $this->check->getPosition()); @@ -39,19 +43,19 @@ public function testExceptionIsThrownIfReferenceFileDoesNotExist(): void $this->expectExceptionMessage('File: "some-file.txt" does not exist in solution folder'); $exercise = new FileComparisonExercise(['some-file.txt']); - $exercise->setSolution(new SingleFileSolution($this->getTemporaryFile('solution/solution.php'))); + $context = new TestContext($exercise); - $this->check->check($exercise, new Input('app', ['program' => 'my-solution.php'])); + $this->check->check($context); } public function testFailureIsReturnedIfStudentsFileDoesNotExist(): void { - $referenceFile = $this->getTemporaryFile('solution/some-file.txt', "name,age\nAydin,33\nMichael,29\n"); - $exercise = new FileComparisonExercise(['some-file.txt']); - $exercise->setSolution(new SingleFileSolution($this->getTemporaryFile('solution/solution.php'))); + $context = new TestContext($exercise); + $context->createReferenceSolutionDirectory(); + $context->importReferenceFileFromString("name,age\nAydin,33\nMichael,29\n", 'some-file.txt'); - $failure = $this->check->check($exercise, new Input('app', ['program' => 'my-solution.php'])); + $failure = $this->check->check($context); $this->assertInstanceOf(Failure::class, $failure); $this->assertEquals('File: "some-file.txt" does not exist', $failure->getReason()); @@ -59,15 +63,14 @@ public function testFailureIsReturnedIfStudentsFileDoesNotExist(): void public function testFailureIsReturnedIfStudentFileDosNotMatchReferenceFile(): void { - $referenceFile = $this->getTemporaryFile('solution/some-file.txt', "name,age\nAydin,33\nMichael,29\n"); - $studentFile = $this->getTemporaryFile('student/some-file.txt', "somegibberish"); - $exercise = new FileComparisonExercise(['some-file.txt']); - $exercise->setSolution(new SingleFileSolution($this->getTemporaryFile('solution/solution.php'))); + $context = new TestContext($exercise); + $context->createStudentSolutionDirectory(); + $context->createReferenceSolutionDirectory(); + $context->importStudentFileFromString("somegibberish", 'some-file.txt'); + $context->importReferenceFileFromString("name,age\nAydin,33\nMichael,29\n", 'some-file.txt'); - $failure = $this->check->check($exercise, new Input('app', [ - 'program' => $this->getTemporaryFile('student/my-solution.php') - ])); + $failure = $this->check->check($context); $this->assertInstanceOf(FileComparisonFailure::class, $failure); $this->assertEquals($failure->getFileName(), 'some-file.txt'); @@ -77,37 +80,27 @@ public function testFailureIsReturnedIfStudentFileDosNotMatchReferenceFile(): vo public function testSuccessIsReturnedIfFilesMatch(): void { - $referenceFile = $this->getTemporaryFile('solution/some-file.txt', "name,age\nAydin,33\nMichael,29\n"); - $studentFile = $this->getTemporaryFile('student/some-file.txt', "name,age\nAydin,33\nMichael,29\n"); - $exercise = new FileComparisonExercise(['some-file.txt']); - $exercise->setSolution(new SingleFileSolution($this->getTemporaryFile('solution/solution.php'))); - - $this->assertInstanceOf( - Success::class, - $this->check->check($exercise, new Input('app', [ - 'program' => $this->getTemporaryFile('student/my-solution.php') - ])) - ); + + $context = new TestContext($exercise); + $context->createStudentSolutionDirectory(); + $context->createReferenceSolutionDirectory(); + $context->importStudentFileFromString("name,age\nAydin,33\nMichael,29\n", 'some-file.txt'); + $context->importReferenceFileFromString("name,age\nAydin,33\nMichael,29\n", 'some-file.txt'); + + $this->assertInstanceOf(Success::class, $this->check->check($context)); } public function testFailureIsReturnedIfFileDoNotMatchUsingStrip(): void { - $referenceFile = $this->getTemporaryFile( - 'solution/some-file.txt', - "01:03name,age\n04:05Aydin,33\n17:21Michael,29\n" - ); - $studentFile = $this->getTemporaryFile( - 'student/some-file.txt', - "01:04name,age\n06:76Aydin,34\n99:00Michael,29\n" - ); - $exercise = new FileComparisonExercise(['some-file.txt' => ['strip' => '/\d{2}:\d{2}/']]); - $exercise->setSolution(new SingleFileSolution($this->getTemporaryFile('solution/solution.php'))); + $context = new TestContext($exercise); + $context->createStudentSolutionDirectory(); + $context->createReferenceSolutionDirectory(); + $context->importStudentFileFromString("01:04name,age\n06:76Aydin,34\n99:00Michael,29\n", 'some-file.txt'); + $context->importReferenceFileFromString("01:03name,age\n04:05Aydin,33\n17:21Michael,29\n", 'some-file.txt'); - $failure = $this->check->check($exercise, new Input('app', [ - 'program' => $this->getTemporaryFile('student/my-solution.php') - ])); + $failure = $this->check->check($context); $this->assertInstanceOf(FileComparisonFailure::class, $failure); $this->assertEquals($failure->getFileName(), 'some-file.txt'); diff --git a/test/Check/FileExistsCheckTest.php b/test/Check/FileExistsCheckTest.php index 8afb8c8a..e4dfc0c8 100644 --- a/test/Check/FileExistsCheckTest.php +++ b/test/Check/FileExistsCheckTest.php @@ -4,7 +4,9 @@ use PhpSchool\PhpWorkshop\Check\SimpleCheckInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; +use PhpSchool\PhpWorkshop\Utils\Path; use PHPUnit\Framework\TestCase; use PhpSchool\PhpWorkshop\Check\FileExistsCheck; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; @@ -13,27 +15,15 @@ class FileExistsCheckTest extends TestCase { - /** - * @var string - */ - private $testDir; - - /** - * @var FileExistsCheck - */ - private $check; - - /** - * @var ExerciseInterface - */ - private $exercise; + private FileExistsCheck $check; public function setUp(): void { - $this->testDir = sprintf('%s/%s', sys_get_temp_dir(), $this->getName()); - mkdir($this->testDir, 0777, true); $this->check = new FileExistsCheck(); - $this->exercise = $this->createMock(ExerciseInterface::class); + } + + public function testCheckMeta(): void + { $this->assertEquals('File Exists Check', $this->check->getName()); $this->assertEquals(ExerciseInterface::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_BEFORE, $this->check->getPosition()); @@ -44,26 +34,28 @@ public function setUp(): void public function testSuccess(): void { - $file = sprintf('%s/test.txt', $this->testDir); - touch($file); + $context = new TestContext(); + $context->createStudentSolutionDirectory(); + $context->importStudentFileFromString('assertInstanceOf( Success::class, - $this->check->check($this->exercise, new Input('app', ['program' => $file])) + $this->check->check($context) ); - unlink($file); } public function testFailure(): void { - $file = sprintf('%s/test.txt', $this->testDir); - $failure = $this->check->check($this->exercise, new Input('app', ['program' => $file])); - $this->assertInstanceOf(Failure::class, $failure); - $this->assertEquals(sprintf('File: "%s" does not exist', $file), $failure->getReason()); - } + $context = new TestContext(); - public function tearDown(): void - { - rmdir($this->testDir); + $failure = $this->check->check($context); + $this->assertInstanceOf(Failure::class, $failure); + $this->assertEquals( + sprintf( + 'File: "%s" does not exist', + Path::join($context->getStudentExecutionDirectory(), 'solution.php') + ), + $failure->getReason() + ); } } diff --git a/test/Check/FunctionRequirementsCheckTest.php b/test/Check/FunctionRequirementsCheckTest.php index 70a10249..924e58fb 100644 --- a/test/Check/FunctionRequirementsCheckTest.php +++ b/test/Check/FunctionRequirementsCheckTest.php @@ -7,6 +7,7 @@ use PhpParser\ParserFactory; use PhpSchool\PhpWorkshop\Check\SimpleCheckInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshopTest\Asset\FunctionRequirementsExercise; use PHPUnit\Framework\TestCase; @@ -19,20 +20,9 @@ class FunctionRequirementsCheckTest extends TestCase { - /** - * @var FunctionRequirementsCheck - */ - private $check; - - /** - * @var ExerciseInterface - */ - private $exercise; - - /** - * @var Parser - */ - private $parser; + private FunctionRequirementsCheck $check; + private FunctionRequirementsExercise $exercise; + private Parser $parser; public function setUp(): void { @@ -40,6 +30,10 @@ public function setUp(): void $this->parser = $parserFactory->create(ParserFactory::PREFER_PHP7); $this->check = new FunctionRequirementsCheck($this->parser); $this->exercise = new FunctionRequirementsExercise(); + } + + public function testCheckMeta(): void + { $this->assertEquals('Function Requirements Check', $this->check->getName()); $this->assertEquals(FunctionRequirementsExerciseCheck::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_AFTER, $this->check->getPosition()); @@ -53,25 +47,35 @@ public function testExceptionIsThrownIfNotValidExercise(): void $exercise = $this->createMock(ExerciseInterface::class); $this->expectException(InvalidArgumentException::class); - $this->check->check($exercise, new Input('app')); + $this->check->check(new TestContext($exercise)); } public function testFailureIsReturnedIfCodeCouldNotBeParsed(): void { - $file = __DIR__ . '/../res/function-requirements/fail-invalid-code.php'; - $failure = $this->check->check($this->exercise, new Input('app', ['program' => $file])); - $this->assertInstanceOf(Failure::class, $failure); + $context = TestContext::fromExerciseAndStudentSolution( + $this->exercise, + __DIR__ . '/../res/function-requirements/fail-invalid-code.php' + ); + + $failure = $this->check->check($context); - $message = sprintf('File: "%s" could not be parsed. Error: "Syntax error, unexpected T_ECHO on line 4"', $file); + $this->assertInstanceOf(Failure::class, $failure); + $message = sprintf( + 'File: "%s/fail-invalid-code.php" could not be parsed. Error: "Syntax error, unexpected T_ECHO on line 4"', + $context->getStudentExecutionDirectory() + ); $this->assertEquals($message, $failure->getReason()); } public function testFailureIsReturnedIfBannedFunctionsAreUsed(): void { - $failure = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $this->exercise, - new Input('app', ['program' => __DIR__ . '/../res/function-requirements/fail-banned-function.php']) + __DIR__ . '/../res/function-requirements/fail-banned-function.php' ); + + $failure = $this->check->check($context); + $this->assertInstanceOf(FunctionRequirementsFailure::class, $failure); $this->assertEquals([['function' => 'file', 'line' => 3]], $failure->getBannedFunctions()); $this->assertEquals([], $failure->getMissingFunctions()); @@ -90,12 +94,14 @@ public function testFailureIsReturnedIfNotAllRequiredFunctionsHaveBeenUsed(): vo ->method('getRequiredFunctions') ->willReturn(['file_get_contents', 'implode']); - $failure = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $exercise, - new Input('app', ['program' => __DIR__ . '/../res/function-requirements/fail-banned-function.php']) + __DIR__ . '/../res/function-requirements/fail-banned-function.php' ); - $this->assertInstanceOf(FunctionRequirementsFailure::class, $failure); + $failure = $this->check->check($context); + + $this->assertInstanceOf(FunctionRequirementsFailure::class, $failure); $this->assertEquals(['file_get_contents', 'implode'], $failure->getMissingFunctions()); $this->assertEquals([], $failure->getBannedFunctions()); } @@ -113,10 +119,13 @@ public function testSuccess(): void ->method('getRequiredFunctions') ->willReturn(['file_get_contents']); - $success = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $exercise, - new Input('app', ['program' => __DIR__ . '/../res/function-requirements/success.php']) + __DIR__ . '/../res/function-requirements/success.php' ); + + $success = $this->check->check($context); + $this->assertInstanceOf(Success::class, $success); } } diff --git a/test/Check/PhpLintCheckTest.php b/test/Check/PhpLintCheckTest.php index 82c3025d..b905f4ba 100644 --- a/test/Check/PhpLintCheckTest.php +++ b/test/Check/PhpLintCheckTest.php @@ -4,6 +4,8 @@ use PhpSchool\PhpWorkshop\Check\SimpleCheckInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\TestContext; use PhpSchool\PhpWorkshop\Input\Input; use PHPUnit\Framework\TestCase; use PhpSchool\PhpWorkshop\Check\PhpLintCheck; @@ -16,20 +18,17 @@ class PhpLintCheckTest extends TestCase { use AssertionRenames; - /** - * @var PhpLintCheck - */ - private $check; - - /** - * @var ExerciseInterface - */ - private $exercise; + private PhpLintCheck $check; + private ExerciseInterface $exercise; public function setUp(): void { $this->check = new PhpLintCheck(); $this->exercise = $this->createMock(ExerciseInterface::class); + } + + public function testCheckMeta(): void + { $this->assertEquals('PHP Code Check', $this->check->getName()); $this->assertEquals(ExerciseInterface::class, $this->check->getExerciseInterface()); $this->assertEquals(SimpleCheckInterface::CHECK_BEFORE, $this->check->getPosition()); @@ -40,18 +39,28 @@ public function setUp(): void public function testSuccess(): void { + $context = TestContext::fromExerciseAndStudentSolution( + $this->exercise, + __DIR__ . '/../res/lint/pass.php' + ); + + $res = $this->check->check($context); + $this->assertInstanceOf( Success::class, - $this->check->check($this->exercise, new Input('app', ['program' => __DIR__ . '/../res/lint/pass.php'])) + $this->check->check($context) ); } public function testFailure(): void { - $failure = $this->check->check( + $context = TestContext::fromExerciseAndStudentSolution( $this->exercise, - new Input('app', ['program' => __DIR__ . '/../res/lint/fail.php']) + __DIR__ . '/../res/lint/fail.php' ); + + $failure = $this->check->check($context); + $this->assertInstanceOf(Failure::class, $failure); $this->assertMatchesRegularExpression( "/(PHP )?Parse error:\W+syntax error, unexpected end of file, expecting ['\"][,;]['\"] or ['\"][;,]['\"]/", diff --git a/test/ExerciseDispatcherTest.php b/test/ExerciseDispatcherTest.php index f5897f58..4792b602 100644 --- a/test/ExerciseDispatcherTest.php +++ b/test/ExerciseDispatcherTest.php @@ -219,7 +219,9 @@ public function testVerify(): void $check->method('canRun')->with($exercise->getType())->willReturn(true); $check->method('getPosition')->willReturn(SimpleCheckInterface::CHECK_BEFORE); $check->method('getExerciseInterface')->willReturn(ExerciseInterface::class); - $check->method('check')->with($exercise, $input)->willReturn(new Success('Success!')); + $check->method('check') + ->with($this->isInstanceOf(ExecutionContext::class)) + ->willReturn(new Success('Success!')); $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([get_class($check)]); @@ -263,7 +265,7 @@ public function testVerifyOnlyRunsRequiredChecks(): void $check1 ->method('check') - ->with($exercise, $input) + ->with($this->isInstanceOf(ExecutionContext::class)) ->willReturn(new Success('Success!')); $check2 = $this @@ -274,7 +276,7 @@ public function testVerifyOnlyRunsRequiredChecks(): void $check2 ->expects($this->never()) ->method('check') - ->with($exercise, $input); + ->with($this->isInstanceOf(ExecutionContext::class)); $runner = $this->createMock(ExerciseRunnerInterface::class); $runner @@ -313,13 +315,17 @@ public function testVerifyWithBeforeAndAfterRequiredChecks(): void $check1->method('canRun')->with($exercise->getType())->willReturn(true); $check1->method('getPosition')->willReturn(SimpleCheckInterface::CHECK_BEFORE); $check1->method('getExerciseInterface')->willReturn(ExerciseInterface::class); - $check1->method('check')->with($exercise, $input)->willReturn(new Success('Success!')); + $check1->method('check') + ->with($this->isInstanceOf(ExecutionContext::class)) + ->willReturn(new Success('Success!')); $check2 = $this->createMock(SimpleCheckInterface::class); $check2->method('canRun')->with($exercise->getType())->willReturn(true); $check2->method('getPosition')->willReturn(SimpleCheckInterface::CHECK_AFTER); $check2->method('getExerciseInterface')->willReturn(ExerciseInterface::class); - $check2->method('check')->with($exercise, $input)->willReturn(new Success('Success!')); + $check2->method('check') + ->with($this->isInstanceOf(ExecutionContext::class)) + ->willReturn(new Success('Success!')); $runner = $this->createMock(ExerciseRunnerInterface::class); $runner->method('getRequiredChecks')->willReturn([get_class($check1), get_class($check2)]); @@ -365,7 +371,7 @@ public function testWhenBeforeChecksFailTheyReturnImmediately(): void $check1 ->method('check') - ->with($exercise, $input) + ->with($this->isInstanceOf(ExecutionContext::class)) ->willReturn(new Failure('Failure', 'nope')); $check2 = $this @@ -388,7 +394,7 @@ public function testWhenBeforeChecksFailTheyReturnImmediately(): void $check2 ->expects($this->never()) ->method('check') - ->with($exercise, $input); + ->with($this->isInstanceOf(ExecutionContext::class)); $runner = $this->createMock(ExerciseRunnerInterface::class); $runner