/** * @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); }
/** * @param ExerciseInterface $exercise * @return ExerciseInterface */ private function validateExercise(ExerciseInterface $exercise) { $type = $exercise->getType(); $requiredInterface = $type->getExerciseInterface(); if (!$exercise instanceof $requiredInterface) { throw InvalidArgumentException::missingImplements($exercise, $requiredInterface); } return $exercise; }
/** * @param ExerciseInterface $exercise * @param EventDispatcher $eventDispatcher * @return ExerciseRunnerInterface */ public function create(ExerciseInterface $exercise, EventDispatcher $eventDispatcher) { switch ($exercise->getType()->getValue()) { case ExerciseType::CLI: return new CliRunner($exercise, $eventDispatcher); case ExerciseType::CGI: return new CgiRunner($exercise, $eventDispatcher); } throw new InvalidArgumentException(sprintf('Exercise Type: "%s" not supported', $exercise->getType()->getValue())); }
/** * @param ExerciseInterface $exercise * @param string $code * @return string */ public function patch(ExerciseInterface $exercise, $code) { if (null !== $this->defaultPatch) { $code = $this->applyPatch($this->defaultPatch, $code); } if ($exercise instanceof SubmissionPatchable) { $code = $this->applyPatch($exercise->getPatch(), $code); } return $code; }
public function testAlteringDatabaseInSolutionDoesNotEffectDatabaseInUserSolution() { $solution = SingleFileSolution::fromFile(realpath(__DIR__ . '/../res/database/solution-alter-db.php')); $this->exercise->expects($this->once())->method('getSolution')->will($this->returnValue($solution)); $this->exercise->expects($this->any())->method('getArgs')->will($this->returnValue([])); $this->exercise->expects($this->atLeastOnce())->method('getType')->will($this->returnValue(ExerciseType::CLI())); $this->exercise->expects($this->once())->method('configure')->will($this->returnCallback(function (ExerciseDispatcher $dispatcher) { $dispatcher->requireCheck(DatabaseCheck::class); })); $this->exercise->expects($this->once())->method('seed')->with($this->isInstanceOf(PDO::class))->will($this->returnCallback(function (PDO $db) { $db->exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER, gender TEXT)'); $stmt = $db->prepare('INSERT into users (name, age, gender) VALUES (:name, :age, :gender)'); $stmt->execute([':name' => 'Jimi Hendrix', ':age' => 27, ':gender' => 'Male']); })); $this->exercise->expects($this->once())->method('verify')->with($this->isInstanceOf(PDO::class))->will($this->returnCallback(function (PDO $db) { $users = $db->query('SELECT * FROM users'); $users = $users->fetchAll(PDO::FETCH_ASSOC); $this->assertEquals([['id' => 1, 'name' => 'Jimi Hendrix', 'age' => '27', 'gender' => 'Male'], ['id' => 2, 'name' => 'Kurt Cobain', 'age' => '27', 'gender' => 'Male']], $users); })); $results = new ResultAggregator(); $eventDispatcher = new EventDispatcher($results); $checkRepository = new CheckRepository([$this->check]); $dispatcher = new ExerciseDispatcher(new RunnerFactory(), $results, $eventDispatcher, $checkRepository); $dispatcher->verify($this->exercise, __DIR__ . '/../res/database/user-solution-alter-db.php'); }
/** * @param ExerciseInterface $exercise * @param string $fileName * @return ResultInterface */ public function check(ExerciseInterface $exercise, $fileName) { if (!$exercise instanceof ComposerExerciseCheck) { throw new \InvalidArgumentException(); } if (!file_exists(sprintf('%s/composer.json', dirname($fileName)))) { return new Failure($this->getName(), 'No composer.json file found'); } if (!file_exists(sprintf('%s/composer.lock', dirname($fileName)))) { return new Failure($this->getName(), 'No composer.lock file found'); } $lockFile = new LockFileParser(sprintf('%s/composer.lock', dirname($fileName))); $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()); }
public function testVerify() { $this->createExercise(); $this->exercise->expects($this->once())->method('configure')->with($this->exerciseDispatcher); $this->check->expects($this->once())->method('check')->will($this->returnValue(new Success('Success'))); $this->check->expects($this->once())->method('canRun')->with($this->exerciseType)->will($this->returnValue(true)); $this->check->expects($this->once())->method('getExerciseInterface')->will($this->returnValue(ExerciseInterface::class)); $this->mockRunner(); $this->runner->expects($this->once())->method('verify')->with($this->file)->will($this->returnValue($this->getMock(SuccessInterface::class))); $this->exerciseDispatcher->requireCheck(get_class($this->check), ExerciseDispatcher::CHECK_BEFORE); $result = $this->exerciseDispatcher->verify($this->exercise, $this->file); $this->assertInstanceOf(ResultAggregator::class, $result); $this->assertTrue($result->isSuccessful()); }
/** * @param ExerciseInterface $exercise * @param $interface * @return static */ public static function missingImplements(ExerciseInterface $exercise, $interface) { return new static(sprintf('Exercise: "%s" should implement interface: "%s"', $exercise->getName(), $interface)); }
/** * @param EventDispatcher $eventDispatcher * @param ExerciseInterface $exercise */ private function dispatchExerciseSelectedEvent(EventDispatcher $eventDispatcher, ExerciseInterface $exercise) { $eventDispatcher->dispatch(new Event(sprintf('exercise.selected.%s', AbstractExercise::normaliseName($exercise->getName())))); }
/** * Whether the factory supports this exercise type. * * @param ExerciseInterface $exercise * @return bool */ public function supports(ExerciseInterface $exercise) { return $exercise->getType()->getValue() === self::$type; }
/** * @param ExerciseInterface $exercise * @param UserState $userState * @param OutputInterface $output */ private function renderSuccessInformation(ExerciseInterface $exercise, UserState $userState, OutputInterface $output) { $output->emptyLine(); $output->writeLine($this->style(" PASS!", ['green', 'bg_default', 'bold'])); $output->emptyLine(); $output->writeLine("Here's the official solution in case you want to compare notes:"); $output->writeLine($this->lineBreak()); foreach ($exercise->getSolution()->getFiles() as $file) { $output->writeLine($this->style($file->getRelativePath(), ['bold', 'cyan', 'underline'])); $output->emptyLine(); $code = $file->getContents(); if (pathinfo($file->getRelativePath(), PATHINFO_EXTENSION) === 'php') { $code = $this->syntaxHighlighter->highlight($code); } //make sure there is a new line at the end $code = preg_replace('/\\n$/', '', $code) . "\n"; $output->write($code); $output->writeLine($this->lineBreak()); } $completedCount = count($userState->getCompletedExercises()); $numExercises = $this->exerciseRepository->count(); $output->writeLine(sprintf('You have %d challenges left.', $numExercises - $completedCount)); $output->writeLine(sprintf('Type "%s" to show the menu.', $this->appName)); $output->emptyLine(); }
/** * Static constructor to create an instance from the check & exercise. * * @param CheckInterface $check The check Instance. * @param ExerciseInterface $exercise The exercise Instance. * @return static */ public static function fromCheckAndExercise(CheckInterface $check, ExerciseInterface $exercise) { return new static(sprintf('Check: "%s" cannot process exercise: "%s" with type: "%s"', $check->getName(), $exercise->getName(), $exercise->getType())); }
/** * @param CheckInterface[] $checks * @param ExerciseInterface $exercise * @throws CheckNotApplicableException * @throws ExerciseNotConfiguredException */ private function validateChecks(array $checks, ExerciseInterface $exercise) { foreach ($checks as $check) { if (!$check->canRun($exercise->getType())) { throw CheckNotApplicableException::fromCheckAndExercise($check, $exercise); } $checkInterface = $check->getExerciseInterface(); if (!$exercise instanceof $checkInterface) { throw ExerciseNotConfiguredException::missingImplements($exercise, $checkInterface); } } }