Skip to content

Use context objects in dispatcher and runners #289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading