Skip to content

Commit

Permalink
Merge pull request #289 from php-school/05-07-use_context_objects_in_…
Browse files Browse the repository at this point in the history
…dispatcher_and_runners

Use context objects in dispatcher and runners
  • Loading branch information
AydinHassan authored May 17, 2024
2 parents 6515c60 + b86f356 commit bb84725
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 281 deletions.
16 changes: 12 additions & 4 deletions app/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
use PhpSchool\PhpWorkshop\ExerciseRenderer;
use PhpSchool\PhpWorkshop\ExerciseRepository;
use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager;
use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CgiRunnerFactory;
use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CliRunnerFactory;
use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CustomVerifyingRunnerFactory;
use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\ServerRunnerFactory;
use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager;
use PhpSchool\PhpWorkshop\Factory\EventDispatcherFactory;
use PhpSchool\PhpWorkshop\Factory\MenuFactory;
Expand Down Expand Up @@ -134,7 +134,7 @@
$c->get(RunnerManager::class),
$c->get(ResultAggregator::class),
$c->get(EventDispatcher::class),
$c->get(CheckRepository::class)
$c->get(CheckRepository::class),
);
},
ResultAggregator::class => create(ResultAggregator::class),
Expand Down Expand Up @@ -193,8 +193,16 @@
//Exercise Runners
RunnerManager::class => function (ContainerInterface $c) {
$manager = new RunnerManager();
$manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class), $c->get(ProcessFactory::class)));
$manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class), $c->get(ProcessFactory::class)));
$manager->addFactory(new CliRunnerFactory(
$c->get(EventDispatcher::class),
$c->get(ProcessFactory::class),
$c->get(EnvironmentManager::class)
));
$manager->addFactory(new CgiRunnerFactory(
$c->get(EventDispatcher::class),
$c->get(ProcessFactory::class),
$c->get(EnvironmentManager::class)
));
$manager->addFactory(new CustomVerifyingRunnerFactory());
return $manager;
},
Expand Down
52 changes: 17 additions & 35 deletions src/ExerciseDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PhpSchool\PhpWorkshop\Exception\ExerciseNotConfiguredException;
use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException;
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Output\OutputInterface;
Expand All @@ -29,49 +30,26 @@ class ExerciseDispatcher
/**
* @var array<SimpleCheckInterface>
*/
private $checksToRunBefore = [];
private array $checksToRunBefore = [];

/**
* @var array<SimpleCheckInterface>
*/
private $checksToRunAfter = [];
private array $checksToRunAfter = [];

/**
* @var RunnerManager
*/
private $runnerManager;

/**
* @var ResultAggregator
*/
private $results;

/**
* @var EventDispatcher
*/
private $eventDispatcher;

/**
* @var CheckRepository
*/
private $checkRepository;

/**
* @param RunnerManager $runnerManager Factory capable of building an exercise runner based on the exercise type.
* @param ResultAggregator $resultAggregator
* @param ResultAggregator $results
* @param EventDispatcher $eventDispatcher
* @param CheckRepository $checkRepository
*/
public function __construct(
RunnerManager $runnerManager,
ResultAggregator $resultAggregator,
EventDispatcher $eventDispatcher,
CheckRepository $checkRepository
private RunnerManager $runnerManager,
private ResultAggregator $results,
private EventDispatcher $eventDispatcher,
private CheckRepository $checkRepository,
) {
$this->runnerManager = $runnerManager;
$this->results = $resultAggregator;
$this->eventDispatcher = $eventDispatcher;
$this->checkRepository = $checkRepository;
}

/**
Expand Down Expand Up @@ -129,6 +107,8 @@ public function requireCheck(string $requiredCheck): void
*/
public function verify(ExerciseInterface $exercise, Input $input): ResultAggregator
{
$context = ExecutionContext::fromInputAndExercise($input, $exercise);

$runner = $this->runnerManager->getRunner($exercise);

$exercise->defineListeners($this->eventDispatcher);
Expand All @@ -143,7 +123,7 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega
$this->validateChecks($this->checksToRunAfter, $exercise);

foreach ($this->checksToRunBefore as $check) {
$this->results->add($check->check($exercise, $input));
$this->results->add($check->check($context->getExercise(), $context->getInput()));

if (!$this->results->isSuccessful()) {
return $this->results;
Expand All @@ -153,13 +133,13 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.pre.execute', $exercise, $input));

try {
$this->results->add($runner->verify($input));
$this->results->add($runner->verify($context));
} finally {
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.execute', $exercise, $input));
}

foreach ($this->checksToRunAfter as $check) {
$this->results->add($check->check($exercise, $input));
$this->results->add($check->check($context->getExercise(), $context->getInput()));
}

$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.post.check', $exercise, $input));
Expand All @@ -181,11 +161,13 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega
*/
public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output): bool
{
$context = ExecutionContext::fromInputAndExercise($input, $exercise);

$exercise->defineListeners($this->eventDispatcher);

/** @var PhpLintCheck $lint */
$lint = $this->checkRepository->getByClass(PhpLintCheck::class);
$result = $lint->check($exercise, $input);
$result = $lint->check($context->getExercise(), $context->getInput());

if ($result instanceof FailureInterface) {
throw CouldNotRunException::fromFailure($result);
Expand All @@ -196,7 +178,7 @@ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $
try {
$exitStatus = $this->runnerManager
->getRunner($exercise)
->run($input, $output);
->run($context, $output);
} finally {
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('run.finish', $exercise, $input));
}
Expand Down
68 changes: 40 additions & 28 deletions src/ExerciseRunner/CgiRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use PhpSchool\PhpWorkshop\Exception\SolutionExecutionException;
use PhpSchool\PhpWorkshop\Exercise\CgiExercise;
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Output\OutputInterface;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
Expand Down Expand Up @@ -63,7 +64,8 @@ class CgiRunner implements ExerciseRunnerInterface
public function __construct(
private CgiExercise $exercise,
private EventDispatcher $eventDispatcher,
private ProcessFactory $processFactory
private ProcessFactory $processFactory,
private EnvironmentManager $environmentManager
) {
}

Expand Down Expand Up @@ -99,37 +101,42 @@ public function getRequiredChecks(): array
* * cgi.verify.student.executing
* * cgi.verify.student-execute.fail (if the student's solution fails to execute)
*
* @param Input $input The command line arguments passed to the command.
* @param ExecutionContext $context The current execution context, containing the exercise, input and working directories.
* @return CgiResult The result of the check.
*/
public function verify(Input $input): ResultInterface
public function verify(ExecutionContext $context): ResultInterface
{
$scenario = $this->exercise->defineTestScenario();

$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.start', $this->exercise, $input));
$this->environmentManager->prepareStudent($context, $scenario);
$this->environmentManager->prepareReference($context, $scenario);

$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.start', $this->exercise, $context->getInput()));

$result = new CgiResult(
array_map(
function (RequestInterface $request) use ($input) {
return $this->doVerify($request, $input);
function (RequestInterface $request) use ($context) {
return $this->doVerify($request, $context);
},
$scenario->getExecutions()
)
);
$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.finish', $this->exercise, $input));

$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.finish', $this->exercise, $context->getInput()));
return $result;
}

private function doVerify(RequestInterface $request, Input $input): CgiResultInterface
private function doVerify(RequestInterface $request, ExecutionContext $context): CgiResultInterface
{
try {
/** @var CgiExecuteEvent $event */
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.verify.reference-execute.pre', $this->exercise, $input, $request)
new CgiExecuteEvent('cgi.verify.reference-execute.pre', $this->exercise, $context->getInput(), $request)
);
$solutionResponse = $this->executePhpFile(
$input,
$this->exercise->getSolution()->getEntryPoint()->getAbsolutePath(),
$context,
$context->getReferenceExecutionDirectory(),
$this->exercise->getSolution()->getEntryPoint()->getRelativePath(),
$event->getRequest(),
'reference'
);
Expand All @@ -138,7 +145,7 @@ private function doVerify(RequestInterface $request, Input $input): CgiResultInt
new CgiExecuteEvent(
'cgi.verify.reference-execute.fail',
$this->exercise,
$input,
$context->getInput(),
$request,
['exception' => $e]
)
Expand All @@ -149,11 +156,12 @@ private function doVerify(RequestInterface $request, Input $input): CgiResultInt
try {
/** @var CgiExecuteEvent $event */
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.verify.student-execute.pre', $this->exercise, $input, $request)
new CgiExecuteEvent('cgi.verify.student-execute.pre', $this->exercise, $context->getInput(), $request)
);
$userResponse = $this->executePhpFile(
$input,
$input->getRequiredArgument('program'),
$context,
$context->getStudentExecutionDirectory(),
$context->getEntryPoint(),
$event->getRequest(),
'student'
);
Expand All @@ -162,7 +170,7 @@ private function doVerify(RequestInterface $request, Input $input): CgiResultInt
new CgiExecuteEvent(
'cgi.verify.student-execute.fail',
$this->exercise,
$input,
$context->getInput(),
$request,
['exception' => $e]
)
Expand Down Expand Up @@ -202,16 +210,17 @@ private function getHeaders(ResponseInterface $response): array
* @return ResponseInterface
*/
private function executePhpFile(
Input $input,
ExecutionContext $context,
string $workingDirectory,
string $fileName,
RequestInterface $request,
string $type
): ResponseInterface {
$process = $this->getPhpProcess(dirname($fileName), basename($fileName), $request);
$process = $this->getPhpProcess($workingDirectory, $fileName, $request);

$process->start();
$this->eventDispatcher->dispatch(
new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $this->exercise, $input, $request)
new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $this->exercise, $context->getInput(), $request)
);
$process->wait();

Expand Down Expand Up @@ -280,25 +289,27 @@ private function getPhpProcess(string $workingDirectory, string $fileName, Reque
* * cgi.run.student-execute.pre
* * cgi.run.student.executing
*
* @param Input $input The command line arguments passed to the command.
* @param ExecutionContext $context The current execution context, containing the exercise, input and working directories.
* @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): bool
public function run(ExecutionContext $context, OutputInterface $output): bool
{
$scenario = $this->exercise->defineTestScenario();

$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.start', $this->exercise, $input));
$this->environmentManager->prepareStudent($context, $scenario);

$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.start', $this->exercise, $context->getInput()));

$success = true;
foreach ($scenario->getExecutions() as $i => $request) {
/** @var CgiExecuteEvent $event */
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.run.student-execute.pre', $this->exercise, $input, $request)
new CgiExecuteEvent('cgi.run.student-execute.pre', $this->exercise, $context->getInput(), $request)
);
$process = $this->getPhpProcess(
dirname($input->getRequiredArgument('program')),
$input->getRequiredArgument('program'),
$context->getStudentExecutionDirectory(),
$context->getEntryPoint(),
$event->getRequest()
);

Expand All @@ -307,7 +318,7 @@ public function run(Input $input, OutputInterface $output): bool
new CgiExecuteEvent(
'cgi.run.student.executing',
$this->exercise,
$input,
$context->getInput(),
$request,
['output' => $output]
)
Expand All @@ -324,10 +335,11 @@ public function run(Input $input, OutputInterface $output): bool
$output->lineBreak();

$this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.run.student-execute.post', $this->exercise, $input, $request)
new CgiExecuteEvent('cgi.run.student-execute.post', $this->exercise, $context->getInput(), $request)
);
}
$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.finish', $this->exercise, $input));

$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.finish', $this->exercise, $context->getInput()));
return $success;
}
}
Loading

0 comments on commit bb84725

Please sign in to comment.