/** * 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'))); }
/** * Parse the students solution and check that there are usages of * 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. * @return ResultInterface The result of the check. */ public function check(ExerciseInterface $exercise, Input $input) { if (!$exercise instanceof FunctionRequirementsExerciseCheck) { throw new \InvalidArgumentException(); } $requiredFunctions = $exercise->getRequiredFunctions(); $bannedFunctions = $exercise->getBannedFunctions(); $code = file_get_contents($input->getArgument('program')); try { $ast = $this->parser->parse($code); } catch (Error $e) { return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getArgument('program')); } $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); }
/** * 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); }
public function testSetArgument() { $input = new Input('app'); $this->assertFalse($input->hasArgument('arg1')); $input->setArgument('arg1', 'some-value'); $this->assertEquals('some-value', $input->getArgument('arg1')); }
public function testInputIsUnchangedIfNoProgramArgument() { $exercise = new CliExerciseImpl(); $input = new Input('app', ['some-arg' => 'some-value']); $listener = new RealPathListener(); $listener->__invoke(new ExerciseRunnerEvent('some.event', $exercise, $input)); $this->assertEquals('some-value', $input->getArgument('some-arg')); }
/** * 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. * @return ResultInterface The result of the check. */ public function check(ExerciseInterface $exercise, Input $input) { $process = new Process(sprintf('%s -l %s', PHP_BINARY, $input->getArgument('program'))); $process->run(); if ($process->isSuccessful()) { return Success::fromCheck($this); } return Failure::fromCheckAndReason($this, $process->getErrorOutput()); }
/** * This check parses the `composer.lock` file and checks that the student * 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. * @return ResultInterface The result of the check. */ public function check(ExerciseInterface $exercise, Input $input) { if (!$exercise instanceof ComposerExerciseCheck) { throw new \InvalidArgumentException(); } if (!file_exists(sprintf('%s/composer.json', dirname($input->getArgument('program'))))) { return new Failure($this->getName(), 'No composer.json file found'); } if (!file_exists(sprintf('%s/composer.lock', dirname($input->getArgument('program'))))) { return new Failure($this->getName(), 'No composer.lock file found'); } if (!file_exists(sprintf('%s/vendor', dirname($input->getArgument('program'))))) { return new Failure($this->getName(), 'No vendor folder found'); } $lockFile = new LockFileParser(sprintf('%s/composer.lock', dirname($input->getArgument('program')))); $missingPackages = array_filter($exercise->getRequiredPackages(), function ($package) use($lockFile) { return !$lockFile->hasInstalledPackage($package); }); if (count($missingPackages) > 0) { return new Failure($this->getName(), sprintf('Lockfile doesn\'t include the following packages at any version: "%s"', implode('", "', $missingPackages))); } return new Success($this->getName()); }
/** * @param Input $input * @return ResultInterface */ public function check(Input $input) { $statements = $this->parser->parse(file_get_contents($input->getArgument('program'))); $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()); }
/** * Runs a student's solution by invoking PHP via the `php-cgi` binary, populating all the super globals with * the information from the request objects returned from the exercise. The exercise can return multiple * requests so the solution will be invoked for however many requests there are. * * Running only runs the student's solution, the reference solution is not run and no verification is performed, * the output of the student's solution is written directly to the output. * * Events dispatched (for each request): * * * cgi.run.student-execute.pre * * cgi.run.student.executing * * @param Input $input The command line arguments passed to the command. * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ public function run(Input $input, OutputInterface $output) { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.run.start', $this->exercise, $input)); $success = true; foreach ($this->exercise->getRequests() as $i => $request) { $event = $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.run.student-execute.pre', $request)); $process = $this->getProcess($input->getArgument('program'), $event->getRequest()); $output->writeTitle("Request"); $output->emptyLine(); $output->write($this->requestRenderer->renderRequest($request)); $output->writeTitle("Output"); $output->emptyLine(); $process->start(); $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.run.student.executing', $request, ['output' => $output])); $process->wait(function ($outputType, $outputBuffer) use($output) { $output->write($outputBuffer); }); $output->emptyLine(); if (!$process->isSuccessful()) { $success = false; } $output->lineBreak(); } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.run.finish', $this->exercise, $input)); return $success; }
/** * Runs a student's solution by invoking PHP from the CLI passing the arguments gathered from the exercise * as command line arguments to PHP. * * Running only runs the student's solution, the reference solution is not run and no verification is performed, * the output of the student's solution is written directly to the output. * * Events dispatched: * * * cli.run.student-execute.pre * * cli.run.student.executing * * @param Input $input The command line arguments passed to the command. * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ public function run(Input $input, OutputInterface $output) { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cli.run.start', $this->exercise, $input)); $success = true; foreach ($this->preserveOldArgFormat($this->exercise->getArgs()) as $i => $args) { /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.run.student-execute.pre', new ArrayObject($args))); $args = $event->getArgs(); if (count($args)) { $glue = max(array_map('strlen', $args->getArrayCopy())) > 30 ? "\n" : ', '; $output->writeTitle('Arguments'); $output->write(implode($glue, $args->getArrayCopy())); $output->emptyLine(); } $output->writeTitle("Output"); $process = $this->getPhpProcess($input->getArgument('program'), $args); $process->start(); $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.run.student.executing', $args, ['output' => $output])); $process->wait(function ($outputType, $outputBuffer) use($output) { $output->write($outputBuffer); }); $output->emptyLine(); if (!$process->isSuccessful()) { $success = false; } $output->lineBreak(); } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cli.run.finish', $this->exercise, $input)); return $success; }