Skip to content
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
22 changes: 11 additions & 11 deletions app/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@
use PhpSchool\PhpWorkshop\Output\OutputInterface;
use PhpSchool\PhpWorkshop\Output\StdOutput;
use PhpSchool\PhpWorkshop\Patch;
use PhpSchool\PhpWorkshop\Process\DockerProcessFactory;
use PhpSchool\PhpWorkshop\Process\HostProcessFactory;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
use PhpSchool\PhpWorkshop\Process\DockerSandbox;
use PhpSchool\PhpWorkshop\Process\HostSandbox;
use PhpSchool\PhpWorkshop\Process\Sandbox;
use PhpSchool\PhpWorkshop\Result\Cgi\CgiResult;
use PhpSchool\PhpWorkshop\Result\Cgi\GenericFailure as CgiGenericFailure;
use PhpSchool\PhpWorkshop\Result\Cgi\RequestFailure as CgiRequestFailure;
Expand Down Expand Up @@ -195,28 +195,28 @@
$manager = new RunnerManager();
$manager->addFactory(new CliRunnerFactory(
$c->get(EventDispatcher::class),
$c->get(ProcessFactory::class),
$c->get(Sandbox::class),
$c->get(EnvironmentManager::class),
));
$manager->addFactory(new CgiRunnerFactory(
$c->get(EventDispatcher::class),
$c->get(ProcessFactory::class),
$c->get(Sandbox::class),
$c->get(EnvironmentManager::class),
));
$manager->addFactory(new CustomVerifyingRunnerFactory());
return $manager;
},

ProcessFactory::class => function (ContainerInterface $c) {
$processFactory = getenv('PROCESS_FACTORY');
Sandbox::class => function (ContainerInterface $c) {
$sandbox = getenv('SANDBOX');

return match ($processFactory) {
'docker' => new DockerProcessFactory(
return match ($sandbox) {
'docker' => new DockerSandbox(
$c->get('basePath'),
$c->get('appName'),
$c->get('phpschoolGlobalDir') . '/composer-cache',
),
default => new HostProcessFactory(),
default => new HostSandbox(),
};
},

Expand Down Expand Up @@ -278,7 +278,7 @@
},
PrepareSolutionListener::class => function (ContainerInterface $c) {
return new PrepareSolutionListener(
$c->get(ProcessFactory::class),
$c->get(Sandbox::class),
);
},
CodePatchListener::class => function (ContainerInterface $c) {
Expand Down
9 changes: 4 additions & 5 deletions src/Exception/CodeExecutionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,24 @@
namespace PhpSchool\PhpWorkshop\Exception;

use RuntimeException;
use Symfony\Component\Process\Process;
use PhpSchool\PhpWorkshop\Process\SandboxExecution;

/**
* Represents the situation where some PHP code could not be executed successfully.
*/
class CodeExecutionException extends RuntimeException
{
/**
* Static constructor to create an instance from a failed `Symfony\Component\Process\Process` instance.
* Static constructor to create an instance from a failed sandbox execution.
*
* @param Process $process The `Symfony\Component\Process\Process` instance which failed.
* @return self
*/
public static function fromProcess(Process $process): self
public static function fromExecution(SandboxExecution $execution): self
{
return new self(
sprintf(
'PHP Code failed to execute. Error: "%s"',
trim($process->getErrorOutput() ?: $process->getOutput()),
trim($execution->getErrorOutput() ?: $execution->getOutput()),
),
);
}
Expand Down
32 changes: 16 additions & 16 deletions src/ExerciseRunner/CgiRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Output\OutputInterface;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
use PhpSchool\PhpWorkshop\Process\Sandbox;
use PhpSchool\PhpWorkshop\Process\ProcessInput;
use PhpSchool\PhpWorkshop\Process\SandboxExecution;
use PhpSchool\PhpWorkshop\Result\Cgi\CgiResult;
use PhpSchool\PhpWorkshop\Result\Cgi\RequestFailure;
use PhpSchool\PhpWorkshop\Result\Cgi\GenericFailure;
Expand All @@ -31,7 +32,6 @@
use PhpSchool\PhpWorkshop\Result\ResultInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Process\Process;

/**
* The `CGI` runner. This runner executes solutions as if they were behind a web-server. They populate the `$_SERVER`,
Expand Down Expand Up @@ -59,7 +59,7 @@ class CgiRunner implements ExerciseRunnerInterface
public function __construct(
private CgiExercise $exercise,
private EventDispatcher $eventDispatcher,
private ProcessFactory $processFactory,
private Sandbox $sandbox,
private EnvironmentManager $environmentManager,
) {}

Expand Down Expand Up @@ -215,21 +215,21 @@ private function executePhpFile(
RequestInterface $request,
string $type,
): ResponseInterface {
$process = $this->getPhpProcess($workingDirectory, $fileName, $request, $scenario->getExposedPorts());
$execution = $this->getPhpExecution($workingDirectory, $fileName, $request, $scenario->getExposedPorts());

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

if (!$process->isSuccessful()) {
throw CodeExecutionException::fromProcess($process);
if (!$execution->isSuccessful()) {
throw CodeExecutionException::fromExecution($execution);
}

//if no status line, pre-pend 200 OK
$output = $process->getOutput();
$error = $process->getErrorOutput();
$output = $execution->getOutput();
$error = $execution->getErrorOutput();
if (!preg_match('/^HTTP\/([1-9]\d*\.\d) ([1-5]\d{2})(\s+(.+))?\\r\\n/', $output)) {
$output = "HTTP/1.0 200 OK\r\n" . $output;
}
Expand All @@ -240,7 +240,7 @@ private function executePhpFile(
/**
* @param list<int> $exposedPorts
*/
private function getPhpProcess(string $workingDirectory, string $fileName, RequestInterface $request, array $exposedPorts): Process
private function getPhpExecution(string $workingDirectory, string $fileName, RequestInterface $request, array $exposedPorts): SandboxExecution
{
$env = [
'REQUEST_METHOD' => $request->getMethod(),
Expand Down Expand Up @@ -272,7 +272,7 @@ private function getPhpProcess(string $workingDirectory, string $fileName, Reque
$content,
);

return $this->processFactory->create($processInput);
return $this->sandbox->create($processInput);
}

/**
Expand Down Expand Up @@ -308,14 +308,14 @@ public function run(ExecutionContext $context, OutputInterface $output): bool
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.run.student-execute.pre', $context, $scenario, $request),
);
$process = $this->getPhpProcess(
$execution = $this->getPhpExecution(
$context->getStudentExecutionDirectory(),
basename($context->getEntryPoint()),
$event->getRequest(),
$scenario->getExposedPorts(),
);

$process->start();
$execution->start();
$this->eventDispatcher->dispatch(
new CgiExecuteEvent(
'cgi.run.student.executing',
Expand All @@ -325,12 +325,12 @@ public function run(ExecutionContext $context, OutputInterface $output): bool
['output' => $output],
),
);
$process->wait(function ($outputType, $outputBuffer) use ($output) {
$execution->wait(function ($outputType, $outputBuffer) use ($output) {
$output->write($outputBuffer);
});
$output->emptyLine();

if (!$process->isSuccessful()) {
if (!$execution->isSuccessful()) {
$success = false;
}

Expand Down
30 changes: 15 additions & 15 deletions src/ExerciseRunner/CliRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Output\OutputInterface;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
use PhpSchool\PhpWorkshop\Process\Sandbox;
use PhpSchool\PhpWorkshop\Process\ProcessInput;
use PhpSchool\PhpWorkshop\Process\SandboxExecution;
use PhpSchool\PhpWorkshop\Result\Cli\RequestFailure;
use PhpSchool\PhpWorkshop\Result\Cli\CliResult;
use PhpSchool\PhpWorkshop\Result\Cli\GenericFailure;
use PhpSchool\PhpWorkshop\Result\Cli\Success;
use PhpSchool\PhpWorkshop\Result\Cli\ResultInterface as CliResultInterface;
use PhpSchool\PhpWorkshop\Result\ResultInterface;
use PhpSchool\PhpWorkshop\Utils\Collection;
use Symfony\Component\Process\Process;

/**
* The `CLI` runner. This runner executes solutions as PHP CLI scripts, passing the arguments
Expand Down Expand Up @@ -59,7 +59,7 @@ class CliRunner implements ExerciseRunnerInterface
public function __construct(
private CliExercise $exercise,
private EventDispatcher $eventDispatcher,
private ProcessFactory $processFactory,
private Sandbox $sandbox,
private EnvironmentManager $environmentManager,
) {}

Expand Down Expand Up @@ -220,24 +220,24 @@ public function run(ExecutionContext $context, OutputInterface $output): bool

$args = $event->getArgs();

$process = $this->getPhpProcess(
$execution = $this->getPhpExecution(
$context->getStudentExecutionDirectory(),
basename($context->getEntryPoint()),
$args,
$scenario->getExposedPorts(),
);

$process->start();
$execution->start();
$this->eventDispatcher->dispatch(
new CliExecuteEvent('cli.run.student.executing', $context, $scenario, $args, ['output' => $output]),
);
$process->wait(function ($outputType, $outputBuffer) use ($output) {
$execution->wait(function ($outputType, $outputBuffer) use ($output) {
$output->write($outputBuffer);
});

$output->emptyLine();

if (!$process->isSuccessful()) {
if (!$execution->isSuccessful()) {
$success = false;
}

Expand All @@ -258,28 +258,28 @@ public function run(ExecutionContext $context, OutputInterface $output): bool
*/
private function executePhpFile(ExecutionContext $context, CliScenario $scenario, string $workingDirectory, string $fileName, Collection $args, string $type): string
{
$process = $this->getPhpProcess($workingDirectory, $fileName, $args, $scenario->getExposedPorts());
$execution = $this->getPhpExecution($workingDirectory, $fileName, $args, $scenario->getExposedPorts());

$process->start();
$execution->start();
$this->eventDispatcher->dispatch(
new CliExecuteEvent(sprintf('cli.verify.%s.executing', $type), $context, $scenario, $args),
);
$process->wait();
$execution->wait();

if (!$process->isSuccessful()) {
throw CodeExecutionException::fromProcess($process);
if (!$execution->isSuccessful()) {
throw CodeExecutionException::fromExecution($execution);
}

return $process->getOutput();
return $execution->getOutput();
}

/**
* @param Collection<int, string> $args
* @param list<int> $exposedPorts
*/
private function getPhpProcess(string $workingDirectory, string $fileName, Collection $args, array $exposedPorts): Process
private function getPhpExecution(string $workingDirectory, string $fileName, Collection $args, array $exposedPorts): SandboxExecution
{
return $this->processFactory->create(
return $this->sandbox->create(
new ProcessInput('php', [$fileName, ...$args->getArrayCopy()], $workingDirectory, [], $exposedPorts),
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/ExerciseRunner/Factory/CgiRunnerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner;
use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager;
use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
use PhpSchool\PhpWorkshop\Process\Sandbox;

/**
* Factory class for `CgiRunner`
Expand All @@ -25,7 +25,7 @@ class CgiRunnerFactory implements ExerciseRunnerFactoryInterface
*/
private static string $type = ExerciseType::CGI;

public function __construct(private EventDispatcher $eventDispatcher, private ProcessFactory $processFactory, private EnvironmentManager $environmentManager) {}
public function __construct(private EventDispatcher $eventDispatcher, private Sandbox $sandbox, private EnvironmentManager $environmentManager) {}

/**
* Whether the factory supports this exercise type.
Expand Down Expand Up @@ -56,6 +56,6 @@ public function configureInput(CommandDefinition $commandDefinition): void
*/
public function create(ExerciseInterface $exercise): ExerciseRunnerInterface
{
return new CgiRunner($exercise, $this->eventDispatcher, $this->processFactory, $this->environmentManager);
return new CgiRunner($exercise, $this->eventDispatcher, $this->sandbox, $this->environmentManager);
}
}
6 changes: 3 additions & 3 deletions src/ExerciseRunner/Factory/CliRunnerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner;
use PhpSchool\PhpWorkshop\ExerciseRunner\EnvironmentManager;
use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
use PhpSchool\PhpWorkshop\Process\Sandbox;

/**
* Factory class for `CliRunner`
Expand All @@ -25,7 +25,7 @@ class CliRunnerFactory implements ExerciseRunnerFactoryInterface
*/
private static string $type = ExerciseType::CLI;

public function __construct(private EventDispatcher $eventDispatcher, private ProcessFactory $processFactory, private EnvironmentManager $environmentManager) {}
public function __construct(private EventDispatcher $eventDispatcher, private Sandbox $sandbox, private EnvironmentManager $environmentManager) {}

/**
* Whether the factory supports this exercise type.
Expand Down Expand Up @@ -56,6 +56,6 @@ public function configureInput(CommandDefinition $commandDefinition): void
*/
public function create(ExerciseInterface $exercise): ExerciseRunnerInterface
{
return new CliRunner($exercise, $this->eventDispatcher, $this->processFactory, $this->environmentManager);
return new CliRunner($exercise, $this->eventDispatcher, $this->sandbox, $this->environmentManager);
}
}
10 changes: 5 additions & 5 deletions src/Listener/PrepareSolutionListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
use PhpSchool\PhpWorkshop\Event\ExerciseRunnerEvent;
use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution;
use PhpSchool\PhpWorkshop\Exception\RuntimeException;
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
use PhpSchool\PhpWorkshop\Process\Sandbox;
use PhpSchool\PhpWorkshop\Process\ProcessInput;

/**
* Listener to install composer deps for an exercise solution
*/
class PrepareSolutionListener
{
public function __construct(private ProcessFactory $processFactory)
public function __construct(private Sandbox $sandbox)
{
$this->processFactory = $processFactory;
$this->sandbox = $sandbox;
}

/**
Expand All @@ -40,12 +40,12 @@ public function __invoke(ExerciseRunnerEvent $event): void
//prepare composer deps
//only install if vendor folder not available
if (!file_exists(sprintf('%s/vendor', $event->getContext()->getReferenceExecutionDirectory()))) {
$process = $this->processFactory->create(
$execution = $this->sandbox->create(
new ProcessInput('composer', ['install', '--no-interaction'], $event->getContext()->getReferenceExecutionDirectory(), [], []),
);

try {
$process->mustRun();
$execution->mustRun();
} catch (\Symfony\Component\Process\Exception\RuntimeException $e) {
throw new RuntimeException('Composer dependencies could not be installed', 0, $e);
}
Expand Down
Loading
Loading