/** * @param Event $event */ public function __invoke(Event $event) { $exercise = $event->getParameter('exercise'); if ($exercise instanceof SelfCheck) { $this->results->add($exercise->check($event->getParameter('fileName'))); } }
public function testVerifyDoesNotAddCompletedExerciseAndReturnsCorrectCodeOnFailure() { $file = tempnam(sys_get_temp_dir(), 'pws'); touch($file); $input = new Input('appName', ['program' => $file]); $exercise = new CliExerciseImpl(); $repo = new ExerciseRepository([$exercise]); $state = new UserState(); $state->setCurrentExercise('my-exercise'); $color = new Color(); $color->setForceStyle(true); $output = new StdOutput($color, $this->createMock(TerminalInterface::class)); $serializer = $this->createMock(UserStateSerializer::class); $serializer->expects($this->never())->method('serialize')->with($state); $renderer = $this->createMock(ResultsRenderer::class); $results = new ResultAggregator(); $results->add(new Failure($this->check, 'cba')); $dispatcher = $this->createMock(ExerciseDispatcher::class); $dispatcher->expects($this->once())->method('verify')->with($exercise, $input)->will($this->returnValue($results)); $renderer->expects($this->once())->method('render')->with($results, $exercise, $state, $output); $command = new VerifyCommand($repo, $dispatcher, $state, $serializer, $output, $renderer); $this->assertEquals(1, $command->__invoke($input)); $this->assertEquals([], $state->getCompletedExercises()); unlink($file); }
public function testIterator() { $results = [new Success($this->check), new Failure($this->check, 'nope')]; $resultAggregator = new ResultAggregator(); $resultAggregator->add($results[0]); $resultAggregator->add($results[1]); $this->assertEquals($results, iterator_to_array($resultAggregator)); }
public function testExerciseWithOutSelfCheck() { $exercise = $this->createMock(ExerciseInterface::class); $event = new Event('event', ['exercise' => $exercise, 'fileName' => 'some-file.php']); $results = new ResultAggregator(); $listener = new SelfCheckListener($results); $listener->__invoke($event); $this->assertTrue($results->isSuccessful()); $this->assertCount(0, $results); }
/** * @param ExerciseInterface $exercise * @param string $fileName * @return ResultAggregator * @throws CheckNotApplicableException * @throws ExerciseNotConfiguredException */ public function verify(ExerciseInterface $exercise, $fileName) { $exercise->configure($this); $runner = $this->runnerFactory->create($exercise, $this->eventDispatcher); $this->eventDispatcher->dispatch(new Event('verify.start', compact('exercise', 'fileName'))); $this->validateChecks($this->checksToRunBefore, $exercise); $this->validateChecks($this->checksToRunAfter, $exercise); foreach ($this->checksToRunBefore as $check) { $this->results->add($check->check($exercise, $fileName)); if (!$this->results->isSuccessful()) { return $this->results; } } $this->eventDispatcher->dispatch(new Event('verify.pre.execute', compact('exercise', 'fileName'))); try { $this->results->add($runner->verify($fileName)); } finally { $this->eventDispatcher->dispatch(new Event('verify.post.execute', compact('exercise', 'fileName'))); } foreach ($this->checksToRunAfter as $check) { $this->results->add($check->check($exercise, $fileName)); } $this->eventDispatcher->dispatch(new Event('verify.post.check', compact('exercise', 'fileName'))); $exercise->tearDown(); $this->eventDispatcher->dispatch(new Event('verify.finish', compact('exercise', 'fileName'))); return $this->results; }
/** * Verify a students solution against a specific exercise. Runs queued checks based on their position. Invokes the * correct runner for the exercise based on the exercise type. Various events are triggered throughout the process. * * @param ExerciseInterface $exercise The exercise instance. * @param Input $input The command line arguments passed to the command. * @return ResultAggregator Contains all the results injected via the runner, checks and events. * @throws CheckNotApplicableException If the check is not applicable to the exercise type. * @throws ExerciseNotConfiguredException If the exercise does not implement the correct interface based on * the checks required. */ public function verify(ExerciseInterface $exercise, Input $input) { $exercise->configure($this); $runner = $this->runnerManager->getRunner($exercise); foreach ($runner->getRequiredChecks() as $requiredCheck) { $this->requireCheck($requiredCheck); } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.start', $exercise, $input)); $this->validateChecks($this->checksToRunBefore, $exercise); $this->validateChecks($this->checksToRunAfter, $exercise); foreach ($this->checksToRunBefore as $check) { $this->results->add($check->check($exercise, $input)); if (!$this->results->isSuccessful()) { return $this->results; } } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.pre.execute', $exercise, $input)); try { $this->results->add($runner->verify($input)); } finally { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.execute', $exercise, $input)); } foreach ($this->checksToRunAfter as $check) { $this->results->add($check->check($exercise, $input)); } $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.check', $exercise, $input)); $exercise->tearDown(); $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.finish', $exercise, $input)); return $this->results; }
/** * Render the result set to the output and statistics on the number of exercises completed and * remaining. * * @param ResultAggregator $results The result set. * @param ExerciseInterface $exercise The exercise instance that was just attempted. * @param UserState $userState The current state of the student's progress. * @param OutputInterface $output The output instance. */ public function render(ResultAggregator $results, ExerciseInterface $exercise, UserState $userState, OutputInterface $output) { $successes = []; $failures = []; foreach ($results as $result) { if ($result instanceof SuccessInterface || $result instanceof ResultGroupInterface && $result->isSuccessful()) { $successes[] = sprintf(' ✔ Check: %s', $result->getCheckName()); } else { $failures[] = [$result, sprintf(' ✗ Check: %s', $result->getCheckName())]; } } $output->emptyLine(); $output->writeLine($this->center($this->style('*** RESULTS ***', ['magenta', 'bold']))); $output->emptyLine(); $messages = array_merge($successes, array_column($failures, 1)); $longest = max(array_map('mb_strlen', $messages)) + 4; foreach ($successes as $success) { $output->writeLine($this->center($this->style(str_repeat(' ', $longest), ['bg_green']))); $output->writeLine($this->center($this->style(mb_str_pad($success, $longest), ['bg_green', 'white', 'bold']))); $output->writeLine($this->center($this->style(str_repeat(' ', $longest), ['bg_green']))); $output->emptyLine(); } if ($results->isSuccessful()) { return $this->renderSuccessInformation($exercise, $userState, $output); } $this->renderErrorInformation($failures, $longest, $exercise, $output); }
/** * @param ResultAggregator $results * @param ExerciseInterface $exercise * @param UserState $userState * @param OutputInterface $output */ public function render(ResultAggregator $results, ExerciseInterface $exercise, UserState $userState, OutputInterface $output) { $successes = []; $failures = []; foreach ($results as $result) { if ($result instanceof SuccessInterface || $result instanceof ResultAggregator && $result->isSuccessful()) { $successes[] = sprintf(' ✔ Check: %s', $result->getCheckName()); } else { $failures[] = [$result, sprintf(' ✗ Check: %s', $result->getCheckName())]; } } $longest = max(array_map('strlen', array_merge($successes, array_column($failures, 1)))) + 2; $output->writeLines($this->padArray($this->styleArray($successes, ['green', 'bg_black', 'bold']), $longest)); if ($results->isSuccessful()) { return $this->renderSuccessInformation($exercise, $userState, $output); } $this->renderErrorInformation($failures, $longest, $exercise, $output); }
/** * @param EventInterface $event * @return EventInterface */ public function dispatch(EventInterface $event) { if (array_key_exists($event->getName(), $this->listeners)) { foreach ($this->listeners[$event->getName()] as $listener) { $listener($event); } } if (array_key_exists($event->getName(), $this->verifiers)) { foreach ($this->verifiers[$event->getName()] as $verifier) { $result = $verifier($event); //return type hints pls if ($result instanceof ResultInterface) { $this->resultAggregator->add($result); } else { //??!! } } } return $event; }
/** * Insert a verifier callback which will execute at the given event name much like normal listeners. * A verifier should return an object which implements `PhpSchool\PhpWorkshop\Result\FailureInterface` * or `PhpSchool\PhpWorkshop\Result\SuccessInterface`. This result object will be added to the result aggregator. * * @param string|array $eventName * @param callable $verifier */ public function insertVerifier($eventName, callable $verifier) { $this->attachListener($eventName, function (EventInterface $event) use($verifier) { $result = $verifier($event); //return type hints pls if ($result instanceof ResultInterface) { $this->resultAggregator->add($result); } else { //??!! } }); }
public function testFailureIsReturnedIfDatabaseVerificationFails() { $solution = SingleFileSolution::fromFile(realpath(__DIR__ . '/../res/database/solution.php')); $this->exercise->expects($this->once())->method('getSolution')->will($this->returnValue($solution)); $this->exercise->expects($this->once())->method('getArgs')->will($this->returnValue([1, 2, 3])); $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('verify')->with($this->isInstanceOf(PDO::class))->will($this->returnValue(false)); $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.php'); $this->assertFalse($results->isSuccessful()); $results = iterator_to_array($results); $this->assertSame('Database verification failed', $results[1]->getReason()); }
public function testRenderAllFailures() { $color = new Color(); $color->setForceStyle(true); $resultRendererFactory = new ResultRendererFactory(); $resultRendererFactory->registerRenderer(Failure::class, FailureRenderer::class, function (Failure $failure) { $renderer = $this->prophesize(FailureRenderer::class); $renderer->render(Argument::type(ResultsRenderer::class))->willReturn($failure->getReason() . "\n"); return $renderer->reveal(); }); $terminal = $this->prophesize(TerminalInterface::class); $terminal->getWidth()->willReturn(100); $terminal = $terminal->reveal(); $exerciseRepo = $this->prophesize(ExerciseRepository::class); $exerciseRepo->count()->willReturn(2); $renderer = new ResultsRenderer('app', $color, $terminal, $exerciseRepo->reveal(), (new Factory())->__invoke(), $resultRendererFactory); $resultSet = new ResultAggregator(); $resultSet->add(new Failure('Failure 1', 'Failure 1')); $resultSet->add(new Failure('Failure 2', 'Failure 2')); $this->expectOutputString($this->getExpectedOutput()); $renderer->render($resultSet, $this->createMock(ExerciseInterface::class), new UserState(), new StdOutput($color, $terminal)); }