public function testListenWithMultipleEvents() { $e1 = new Event('some-event', ['arg1' => 1, 'arg2' => 2]); $e2 = new Event('some-event', ['arg1' => 1, 'arg2' => 2]); $mockCallback1 = $this->getMock('stdClass', ['callback']); $mockCallback1->expects($this->exactly(2))->method('callback')->withConsecutive([$e1], [$e2])->will($this->returnValue(true)); $this->eventDispatcher->listen(['some-event', 'second-event'], [$mockCallback1, 'callback']); $this->eventDispatcher->dispatch($e1); $this->eventDispatcher->dispatch($e2); }
/** * 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; }
/** * 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; }
/** * @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); }); }
/** * @param ContainerInterface $container * @return EventDispatcher */ public function __invoke(ContainerInterface $container) { $dispatcher = new EventDispatcher($container->get(ResultAggregator::class)); $prepareSolutionListener = $container->get(PrepareSolutionListener::class); $dispatcher->listen('verify.start', $prepareSolutionListener); $dispatcher->listen('run.start', $prepareSolutionListener); $codePatcherListener = $container->get(CodePatchListener::class); $dispatcher->listen('verify.pre.execute', [$codePatcherListener, 'patch']); $dispatcher->listen('verify.post.execute', [$codePatcherListener, 'revert']); $dispatcher->listen('run.start', [$codePatcherListener, 'patch']); $dispatcher->listen('run.finish', [$codePatcherListener, 'revert']); $dispatcher->listen('verify.post.check', $container->get(SelfCheckListener::class)); return $dispatcher; }
public function testVerifyPostExecuteIsStillDispatchedEvenIfRunnerThrowsException() { $this->eventDispatcher->expects($this->exactly(3))->method('dispatch')->withConsecutive([$this->isInstanceOf(Event::class)], [$this->isInstanceOf(Event::class)], [$this->isInstanceOf(Event::class)]); $this->createExercise(); $this->mockRunner(); $this->runner->expects($this->once())->method('verify')->with($this->file)->will($this->throwException(new RuntimeException())); $this->setExpectedException(RuntimeException::class); $this->exerciseDispatcher->verify($this->exercise, $this->file); }
/** * @param string $eventName * @param array $listeners * @param ContainerInterface $container * @param EventDispatcher $dispatcher * @throws \PhpSchool\PhpWorkshop\Exception\InvalidArgumentException */ private function attachListeners($eventName, array $listeners, ContainerInterface $container, EventDispatcher $dispatcher) { array_walk($listeners, function ($listener) use($eventName, $dispatcher, $container) { if (is_callable($listener)) { return $dispatcher->listen($eventName, $listener); } if (!is_string($listener)) { throw new InvalidArgumentException(sprintf('Listener must be a callable or a container entry for a callable service.')); } if (!$container->has($listener)) { throw new InvalidArgumentException(sprintf('Container has no entry named: "%s"', $listener)); } $listener = $container->get($listener); if (!is_callable($listener)) { throw InvalidArgumentException::typeMisMatch('callable', $listener); } return $dispatcher->listen($eventName, $listener); }); }
/** * @param ExerciseInterface $exercise * @param string $fileName * @param OutputInterface $output * @return bool */ public function run(ExerciseInterface $exercise, $fileName, OutputInterface $output) { $exercise->configure($this); $this->eventDispatcher->dispatch(new Event('run.start', compact('exercise', 'fileName'))); try { $exitStatus = $this->runnerFactory->create($exercise, $this->eventDispatcher)->run($fileName, $output); } finally { $this->eventDispatcher->dispatch(new Event('run.finish', compact('exercise', 'fileName'))); } return $exitStatus; }
/** * Run a student's solution against a specific exercise. Does not invoke checks. Invokes the * correct runner for the exercise based on the exercise type. Various events are triggered throughout the process. * The output of the solution is written directly to the `OutputInterface` instance. * * @param ExerciseInterface $exercise The exercise instance. * @param Input $input The command line arguments passed to the command. * @param OutputInterface $output An output instance capable of writing to stdout. * @return bool Whether the solution ran successfully or not. */ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output) { $exercise->configure($this); $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('run.start', $exercise, $input)); try { $exitStatus = $this->runnerManager->getRunner($exercise)->run($input, $output); } finally { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('run.finish', $exercise, $input)); } return $exitStatus; }
/** * @param string $eventName * @param array $listeners * @param ContainerInterface $container * @param EventDispatcher $dispatcher * @throws \PhpSchool\PhpWorkshop\Exception\InvalidArgumentException */ private function attachListeners($eventName, array $listeners, ContainerInterface $container, EventDispatcher $dispatcher) { array_walk($listeners, function ($listener) use($eventName, $dispatcher, $container) { if ($listener instanceof ContainerListenerHelper) { if (!$container->has($listener->getService())) { throw new InvalidArgumentException(sprintf('Container has no entry named: "%s"', $listener->getService())); } return $dispatcher->listen($eventName, function (...$args) use($container, $listener) { $service = $container->get($listener->getService()); if (!method_exists($service, $listener->getMethod())) { throw new InvalidArgumentException(sprintf('Method "%s" does not exist on "%s"', $listener->getMethod(), get_class($service))); } $service->{$listener->getMethod()}(...$args); }); } if (!is_callable($listener)) { throw new InvalidArgumentException(sprintf('Listener must be a callable or a container entry for a callable service.')); } return $dispatcher->listen($eventName, $listener); }); }
/** * @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); $solutionDsn = sprintf('sqlite:%s', $this->solutionDatabasePath); $userDsn = sprintf('sqlite:%s', $this->userDatabasePath); $db = new PDO($userDsn); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $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.solution-execute.pre', function (CliExecuteEvent $e) use($solutionDsn) { $e->prependArg($solutionDsn); }); $eventDispatcher->listen(['cli.verify.user-execute.pre', 'cli.run.user-execute.pre'], function (CliExecuteEvent $e) use($userDsn) { $e->prependArg($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.solution-execute.fail', 'verify.finish', 'run.finish'], function (Event $e) use($db) { unset($db); @unlink($this->userDatabasePath); @unlink($this->solutionDatabasePath); rmdir($this->databaseDirectory); }); }
public function testListenersAndVerifiersAreCalledInOrderOfAttachment() { $e1 = new Event('first-event', ['arg1' => 1, 'arg2' => 2]); $counter = 0; $this->eventDispatcher->insertVerifier('first-event', function (Event $e) use(&$counter) { $this->assertEquals(0, $counter); $counter++; }); $this->eventDispatcher->listen('first-event', function (Event $e) use(&$counter) { $this->assertEquals(1, $counter); $counter++; }); $this->eventDispatcher->dispatch($e1); }
/** * @param string $fileName * @param OutputInterface $output * @return bool */ public function run($fileName, OutputInterface $output) { /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.run.user-execute.pre', new ArrayObject($this->exercise->getArgs()))); $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($fileName, $args); $process->start(); $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.run.executing', $args, ['output' => $output])); $process->wait(function ($outputType, $outputBuffer) use($output) { $output->writeLine($outputBuffer); }); return $process->isSuccessful(); }
/** * 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 string $fileName The absolute path to the student's solution. * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ public function run($fileName, OutputInterface $output) { $success = true; foreach ($this->exercise->getRequests() as $i => $request) { $event = $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.run.student-execute.pre', $request)); $process = $this->getProcess($fileName, $event->getRequest()); $output->writeTitle("Request"); $output->emptyLine(); $output->writeRequest($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(); } return $success; }
/** * @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())))); }
/** * @param CommandDefinition $command * @param callable $callable * @param Input $input * @return int */ private function callCommand(CommandDefinition $command, callable $callable, Input $input) { $this->eventDispatcher->dispatch(new Event\Event('route.pre.invoke')); $this->eventDispatcher->dispatch(new Event\Event(sprintf('route.pre.invoke.%s', $command->getName()))); return $callable($input); }