/** * @param ExerciseInterface $exercise * @param string $fileName * @return ResultInterface */ public function check(ExerciseInterface $exercise, $fileName) { if (!$exercise instanceof FunctionRequirementsExerciseCheck) { throw new \InvalidArgumentException(); } $requiredFunctions = $exercise->getRequiredFunctions(); $bannedFunctions = $exercise->getBannedFunctions(); $code = file_get_contents($fileName); try { $ast = $this->parser->parse($code); } catch (Error $e) { return Failure::fromCheckAndCodeParseFailure($this, $e, $fileName); } $visitor = new FunctionVisitor($requiredFunctions, $bannedFunctions); $traverser = new NodeTraverser(); $traverser->addVisitor($visitor); $traverser->traverse($ast); $bannedFunctions = []; if ($visitor->hasUsedBannedFunctions()) { $bannedFunctions = array_map(function (FuncCall $node) { return ['function' => $node->name->__toString(), 'line' => $node->getLine()]; }, $visitor->getBannedUsages()); } $missingFunctions = []; if (!$visitor->hasMetFunctionRequirements()) { $missingFunctions = $visitor->getMissingRequirements(); } if (!empty($bannedFunctions) || !empty($missingFunctions)) { return new FunctionRequirementsFailure($this, $bannedFunctions, $missingFunctions); } return Success::fromCheck($this); }
/** * 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. * @return ResultInterface The result of the check. */ public function check(ExerciseInterface $exercise, Input $input) { if (file_exists($input->getArgument('program'))) { return Success::fromCheck($this); } return Failure::fromCheckAndReason($this, sprintf('File: "%s" does not exist', $input->getArgument('program'))); }
/** * Simply check that the file exists. * * @param ExerciseInterface $exercise The exercise to check against. * @param string $fileName The absolute path to the student's solution. * @return ResultInterface The result of the check. */ public function check(ExerciseInterface $exercise, $fileName) { if (file_exists($fileName)) { return Success::fromCheck($this); } return Failure::fromCheckAndReason($this, sprintf('File: "%s" does not exist', $fileName)); }
/** * @param RequestInterface $request * @param string $fileName * @return ResultInterface */ private function checkRequest(RequestInterface $request, $fileName) { try { $event = $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.verify.solution-execute.pre', $request)); $solutionResponse = $this->executePhpFile($this->exercise->getSolution()->getEntryPoint(), $event->getRequest(), 'solution'); } catch (CodeExecutionException $e) { $this->eventDispatcher->dispatch(new Event('cgi.verify.solution-execute.fail', ['exception' => $e])); throw new SolutionExecutionException($e->getMessage()); } try { $event = $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.verify.user-execute.pre', $request)); $userResponse = $this->executePhpFile($fileName, $event->getRequest(), 'user'); } catch (CodeExecutionException $e) { $this->eventDispatcher->dispatch(new Event('cgi.verify.user-execute.fail', ['exception' => $e])); return Failure::fromNameAndCodeExecutionFailure($this->getName(), $e); } $solutionBody = (string) $solutionResponse->getBody(); $userBody = (string) $userResponse->getBody(); $solutionHeaders = $this->getHeaders($solutionResponse); $userHeaders = $this->getHeaders($userResponse); if ($solutionBody !== $userBody || $solutionHeaders !== $userHeaders) { return new CgiOutRequestFailure($request, $solutionBody, $userBody, $solutionHeaders, $userHeaders); } return new Success($this->getName()); }
/** * @param EventDispatcher $eventDispatcher */ public function attach(EventDispatcher $eventDispatcher) { $studentClient = CouchDBClient::create(['dbname' => static::$studentDb]); $solutionClient = CouchDBClient::create(['dbname' => static::$solutionDb]); $studentClient->createDatabase($studentClient->getDatabase()); $solutionClient->createDatabase($solutionClient->getDatabase()); $eventDispatcher->listen('verify.start', function (Event $e) use($studentClient, $solutionClient) { $e->getParameter('exercise')->seed($studentClient); $this->replicateDbFromStudentToSolution($studentClient, $solutionClient); }); $eventDispatcher->listen('run.start', function (Event $e) use($studentClient) { $e->getParameter('exercise')->seed($studentClient); }); $eventDispatcher->listen('cli.verify.reference-execute.pre', function (CliExecuteEvent $e) { $e->prependArg('phpschool'); }); $eventDispatcher->listen(['cli.verify.student-execute.pre', 'cli.run.student-execute.pre'], function (CliExecuteEvent $e) { $e->prependArg('phpschool-student'); }); $eventDispatcher->insertVerifier('verify.finish', function (Event $e) use($studentClient) { $verifyResult = $e->getParameter('exercise')->verify($studentClient); if (false === $verifyResult) { return Failure::fromNameAndReason($this->getName(), 'Database verification failed'); } return Success::fromCheck($this); }); $eventDispatcher->listen(['cli.verify.reference-execute.fail', 'verify.finish', 'run.finish'], function (Event $e) use($studentClient, $solutionClient) { $studentClient->deleteDatabase(static::$studentDb); $solutionClient->deleteDatabase(static::$solutionDb); }); }
public function testFailureFromCodeParseException() { $e = new Error('Something went wrong yo'); $failure = Failure::fromCheckAndCodeParseFailure($this->check, $e, 'exercise.php'); $this->assertInstanceOf(ResultInterface::class, $failure); $this->assertEquals('File: "exercise.php" could not be parsed. Error: "Something went wrong yo on unknown line"', $failure->getReason()); $this->assertEquals('Some Check', $failure->getCheckName()); }
/** * @param ExerciseInterface $exercise * @param string $fileName * @return Failure|Success */ public function check(ExerciseInterface $exercise, $fileName) { $process = new Process(sprintf('%s -l %s', PHP_BINARY, $fileName)); $process->run(); if ($process->isSuccessful()) { return Success::fromCheck($this); } return Failure::fromCheckAndReason($this, $process->getErrorOutput()); }
/** * This check grabs the contents of the student's solution and * 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. * @return ResultInterface The result of the check. */ public function check(ExerciseInterface $exercise, Input $input) { $code = file_get_contents($input->getArgument('program')); try { $this->parser->parse($code); } catch (Error $e) { return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getArgument('program')); } return Success::fromCheck($this); }
/** * @param ExerciseInterface $exercise * @param string $fileName * @return ResultInterface */ public function check(ExerciseInterface $exercise, $fileName) { $code = file_get_contents($fileName); try { $this->parser->parse($code); } catch (Error $e) { return Failure::fromCheckAndCodeParseFailure($this, $e, $fileName); } return Success::fromCheck($this); }
/** * @param ExerciseDispatcher $exerciseDispatcher */ public function configure(ExerciseDispatcher $exerciseDispatcher) { $eventDispatcher = $exerciseDispatcher->getEventDispatcher(); $appendArgsListener = function (CliExecuteEvent $event) { $event->appendArg('127.0.0.1'); $event->appendArg($this->getRandomPort()); }; $eventDispatcher->listen('cli.verify.reference-execute.pre', $appendArgsListener); $eventDispatcher->listen('cli.verify.student-execute.pre', $appendArgsListener); $eventDispatcher->listen('cli.run.student-execute.pre', $appendArgsListener); $eventDispatcher->listen('cli.verify.reference.executing', function (CliExecuteEvent $event) { $args = $event->getArgs()->getArrayCopy(); $client = $this->socketFactory->createClient(...$args); //wait for server to boot usleep(100000); $client->connect(); $client->readAll(); //wait for shutdown usleep(100000); }); $eventDispatcher->insertVerifier('cli.verify.student.executing', function (CliExecuteEvent $event) { $args = $event->getArgs()->getArrayCopy(); $client = $this->socketFactory->createClient(...$args); //wait for server to boot usleep(100000); try { $client->connect(); } catch (Exception $e) { return Failure::fromNameAndReason($this->getName(), $e->getMessage()); } $out = $client->readAll(); //wait for shutdown usleep(100000); $date = new \DateTime(); //match the current date but any seconds //since we can't mock time in PHP easily if (!preg_match(sprintf('/^%s:([0-5][0-9]|60)\\n$/', $date->format('Y-m-d H:i')), $out)) { return new StdOutFailure($this->getName(), $date->format("Y-m-d H:i:s\n"), $out); } return new Success($this->getName()); }); $eventDispatcher->listen('cli.run.student.executing', function (CliExecuteEvent $event) { /** @var OutputInterface $output */ $output = $event->getParameter('output'); $args = $event->getArgs()->getArrayCopy(); $client = $this->socketFactory->createClient(...$args); //wait for server to boot usleep(100000); $client->connect(); $out = $client->readAll(); //wait for shutdown usleep(100000); $output->write($out); }); }
/** * @param RequestInterface $request The request that caused the failure. * @param string|null $reason */ public function __construct(RequestInterface $request, $reason = null) { $this->request = $request; parent::__construct(static::$name, $reason); }
/** * @param string $fileName * @return ResultInterface */ public function check($fileName) { $statements = $this->parser->parse(file_get_contents($fileName)); $include = null; foreach ($statements as $statement) { if ($statement instanceof Include_) { $include = $statement; break; } } if (null === $include) { return Failure::fromNameAndReason($this->getName(), 'No require statement found'); } return new Success($this->getName()); }
/** * Simply print the reason. * * @param ResultsRenderer $renderer * @return string */ public function render(ResultsRenderer $renderer) { return $renderer->center($this->result->getReason()) . "\n"; }
/** * Here we attach to various events to seed, verify and inject the DSN's * to the student & reference solution programs's CLI arguments. * * @param EventDispatcher $eventDispatcher */ public function attach(EventDispatcher $eventDispatcher) { if (file_exists($this->databaseDirectory)) { throw new \RuntimeException(sprintf('Database directory: "%s" already exists', $this->databaseDirectory)); } mkdir($this->databaseDirectory, 0777, true); try { $db = new PDO($this->userDsn); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (\PDOException $e) { rmdir($this->databaseDirectory); throw $e; } $eventDispatcher->listen('verify.start', function (Event $e) use($db) { $e->getParameter('exercise')->seed($db); //make a copy - so solution can modify without effecting database user has access to copy($this->userDatabasePath, $this->solutionDatabasePath); }); $eventDispatcher->listen('run.start', function (Event $e) use($db) { $e->getParameter('exercise')->seed($db); }); $eventDispatcher->listen('cli.verify.reference-execute.pre', function (CliExecuteEvent $e) { $e->prependArg($this->solutionDsn); }); $eventDispatcher->listen(['cli.verify.student-execute.pre', 'cli.run.student-execute.pre'], function (CliExecuteEvent $e) { $e->prependArg($this->userDsn); }); $eventDispatcher->insertVerifier('verify.finish', function (Event $e) use($db) { $verifyResult = $e->getParameter('exercise')->verify($db); if (false === $verifyResult) { return Failure::fromNameAndReason($this->getName(), 'Database verification failed'); } return new Success('Database Verification Check'); }); $eventDispatcher->listen(['cli.verify.reference-execute.fail', 'verify.finish', 'run.finish'], function (Event $e) use($db) { unset($db); @unlink($this->userDatabasePath); @unlink($this->solutionDatabasePath); rmdir($this->databaseDirectory); }); }
/** * @param string $fileName * @return ResultInterface */ public function verify($fileName) { //arrays are not pass-by-ref $args = new ArrayObject($this->exercise->getArgs()); try { $event = $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.verify.solution-execute.pre', $args)); $solutionOutput = $this->executePhpFile($this->exercise->getSolution()->getEntryPoint(), $event->getArgs(), 'solution'); } catch (CodeExecutionException $e) { $this->eventDispatcher->dispatch(new Event('cli.verify.solution-execute.fail', ['exception' => $e])); throw new SolutionExecutionException($e->getMessage()); } try { $event = $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.verify.user-execute.pre', $args)); $userOutput = $this->executePhpFile($fileName, $event->getArgs(), 'user'); } catch (CodeExecutionException $e) { $this->eventDispatcher->dispatch(new Event('cli.verify.user-execute.fail', ['exception' => $e])); return Failure::fromNameAndCodeExecutionFailure($this->getName(), $e); } if ($solutionOutput === $userOutput) { return new Success($this->getName()); } return StdOutFailure::fromNameAndOutput($this->getName(), $solutionOutput, $userOutput); }
/** * @param ResultsRenderer $renderer * @return string */ public function render(ResultsRenderer $renderer) { return ' ' . $this->result->getReason() . "\n"; }
/** * @param ArrayObject $args The arguments that caused the failure. * @param string|null $reason The reason (if any) of the failure. */ public function __construct(ArrayObject $args, $reason = null) { $this->args = $args; parent::__construct(static::$name, $reason); }