Skip to content

Commit

Permalink
Context
Browse files Browse the repository at this point in the history
  • Loading branch information
AydinHassan committed May 5, 2024
1 parent fb82157 commit c0a9650
Show file tree
Hide file tree
Showing 13 changed files with 497 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/Exercise/MockExercise.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace PhpSchool\PhpWorkshop\Exercise;

use PhpSchool\PhpWorkshop\ExerciseDispatcher;

class MockExercise extends AbstractExercise implements ExerciseInterface
{
public function getName(): string
{
return 'Mock Exercise';
}

public function getDescription(): string
{
return 'Mock Exercise';
}

public function getType(): ExerciseType
{
return ExerciseType::CUSTOM();
}

public function getProblem(): string
{
return 'problem-file.md';
}
}
56 changes: 56 additions & 0 deletions src/ExerciseRunner/Context/ExecutionContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Utils\Path;
use PhpSchool\PhpWorkshop\Utils\System;

class ExecutionContext
{
public function __construct(
private string $studentExecutionDirectory,
private string $referenceExecutionDirectory,
private ExerciseInterface $exercise,
private Input $input,
) {
}

public function getExercise(): ExerciseInterface
{
return $this->exercise;
}

public function getInput(): Input
{
return $this->input;
}

public function hasStudentSolution(): bool
{
return $this->input->hasArgument('program');
}

public function getEntryPoint(): string
{
if (!$this->hasStudentSolution()) {
throw new NoEntryPoint();
}

return Path::join(
$this->studentExecutionDirectory,
basename($this->input->getRequiredArgument('program'))
);
}

public function getStudentExecutionDirectory(): string
{
return $this->studentExecutionDirectory;
}

public function getReferenceExecutionDirectory(): string
{
return $this->referenceExecutionDirectory;
}
}
20 changes: 20 additions & 0 deletions src/ExerciseRunner/Context/ExecutionContextFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Utils\System;

class ExecutionContextFactory
{
public function fromInputAndExercise(Input $input, ExerciseInterface $exercise): ExecutionContext
{
return new ExecutionContext(
dirname($input->getRequiredArgument('program')),
System::randomTempDir(),
$exercise,
$input
);
}
}
13 changes: 13 additions & 0 deletions src/ExerciseRunner/Context/NoEntryPoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exception\RuntimeException;

class NoEntryPoint extends RuntimeException
{
public function __construct()
{
parent::__construct('No entry point provided');
}
}
20 changes: 20 additions & 0 deletions src/ExerciseRunner/Context/StaticExecutionContextFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Utils\System;
use Symfony\Component\Filesystem\Filesystem;

class StaticExecutionContextFactory extends ExecutionContextFactory
{
public function __construct(private TestContext $context)
{
}

public function fromInputAndExercise(Input $input, ExerciseInterface $exercise): ExecutionContext
{
return $this->context;
}
}
93 changes: 93 additions & 0 deletions src/ExerciseRunner/Context/TestContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exception\RuntimeException;
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Exercise\MockExercise;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
use PhpSchool\PhpWorkshop\Utils\System;
use Symfony\Component\Filesystem\Filesystem;
use PhpSchool\PhpWorkshop\Utils\Path;

class TestContext extends ExecutionContext
{
public Filesystem $filesystem;
public ExerciseInterface $exercise;

private function __construct(
ExerciseInterface $exercise = null,
Input $input = null
) {
$this->exercise = $exercise ?? new MockExercise();

$this->filesystem = new Filesystem();

parent::__construct(
System::randomTempDir(),
System::randomTempDir(),
$this->exercise,
$input ? $input : new Input('test', ['program' => 'solution.php']),
);
}

public function importStudentSolution(string $file): void
{
if (!$this->filesystem->exists($this->getStudentExecutionDirectory())) {
throw new RuntimeException(
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
);
}

copy($file, Path::join($this->getStudentExecutionDirectory(), 'solution.php'));
}

public function importStudentSolutionFolder(string $folder): void
{
if (!$this->filesystem->exists($this->getStudentExecutionDirectory())) {
throw new RuntimeException(
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
);
}

$this->filesystem->mirror($folder, $this->getStudentExecutionDirectory());
}

public function importReferenceSolution(SolutionInterface $solution): void
{
if (!$this->filesystem->exists($this->getReferenceExecutionDirectory())) {
throw new RuntimeException(
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
);
}

foreach ($solution->getFiles() as $file) {
$this->filesystem->copy(
$file->getAbsolutePath(),
Path::join($this->getReferenceExecutionDirectory(), $file->getRelativePath())
);
}
}

public static function withDirectories(Input $input = null, ExerciseInterface $exercise = null): self
{
$self = new self($exercise, $input);

$self->filesystem->mkdir($self->getStudentExecutionDirectory());
$self->filesystem->mkdir($self->getReferenceExecutionDirectory());

return $self;
}

public static function withoutDirectories(Input $input = null, ExerciseInterface $exercise = null): self
{
return new self($exercise, $input);
}

public function __destruct()
{
$this->filesystem->remove($this->getStudentExecutionDirectory());
$this->filesystem->remove($this->getReferenceExecutionDirectory());
}
}
5 changes: 5 additions & 0 deletions src/Utils/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public static function tempDir(string $path = ''): string
{
return Path::join(self::realpath(sys_get_temp_dir()), 'php-school', $path);
}

public static function randomTempDir(): string
{
return Path::join(self::realpath(sys_get_temp_dir()), 'php-school', bin2hex(random_bytes(4)));
}
}
31 changes: 31 additions & 0 deletions test/ExerciseRunner/Context/ExecutionContextFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace PhpSchool\PhpWorkshopTest\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exercise\MockExercise;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContextFactory;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Utils\System;
use PHPUnit\Framework\TestCase;

class ExecutionContextFactoryTest extends TestCase
{
public function testFactory(): void
{
$factory = new ExecutionContextFactory();

$temporaryDirectory = System::randomTempDir();

$input = new Input('test', ['program' => $temporaryDirectory . '/solution.php']);
$exercise = new MockExercise();

$context = $factory->fromInputAndExercise($input, $exercise);

//check that student execution directory uses the parent directory of the program from the input
static::assertSame($temporaryDirectory, $context->getStudentExecutionDirectory());
static::assertSame($temporaryDirectory . '/solution.php', $context->getEntryPoint());

//check that reference execution directory is a random temporary directory
static::assertTrue(str_starts_with($context->getReferenceExecutionDirectory(), System::tempDir()));
}
}
84 changes: 84 additions & 0 deletions test/ExerciseRunner/Context/ExecutionContextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace PhpSchool\PhpWorkshopTest\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\Exercise\MockExercise;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\NoEntryPoint;
use PhpSchool\PhpWorkshop\Input\Input;
use PHPUnit\Framework\TestCase;

class ExecutionContextTest extends TestCase
{
public function testGetters(): void
{
$exercise = new MockExercise();
$input = new Input('test', ['program' => 'solution.php']);
$context = new ExecutionContext(
'/student-dir',
'/reference-dir',
$exercise,
$input
);

static::assertSame($exercise, $context->getExercise());
static::assertSame($input, $context->getInput());
static::assertSame('/student-dir', $context->getStudentExecutionDirectory());
static::assertSame('/reference-dir', $context->getReferenceExecutionDirectory());
}

public function testHasStudentSolution(): void
{
$exercise = new MockExercise();
$input = new Input('test', ['program' => 'solution.php']);
$context = new ExecutionContext(
'/student-dir',
'/reference-dir',
$exercise,
$input
);

static::assertTrue($context->hasStudentSolution());

$exercise = new MockExercise();
$input = new Input('test');
$context = new ExecutionContext(
'/student-dir',
'/reference-dir',
$exercise,
$input
);

static::assertFalse($context->hasStudentSolution());
}

public function testGetEntryPoint(): void
{
$exercise = new MockExercise();
$input = new Input('test', ['program' => 'solution.php']);
$context = new ExecutionContext(
'/student-dir',
'/reference-dir',
$exercise,
$input
);

static::assertSame('/student-dir/solution.php', $context->getEntryPoint());
}

public function testGetEntryPointThrowsExceptionWhenNoStudentSolution(): void
{
static::expectException(NoEntryPoint::class);

$exercise = new MockExercise();
$input = new Input('test');
$context = new ExecutionContext(
'/student-dir',
'/reference-dir',
$exercise,
$input
);

$context->getEntryPoint();
}
}
15 changes: 15 additions & 0 deletions test/ExerciseRunner/Context/NoEntryPointTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace PhpSchool\PhpWorkshopTest\ExerciseRunner\Context;

use PhpSchool\PhpWorkshop\ExerciseRunner\Context\NoEntryPoint;
use PHPUnit\Framework\TestCase;

class NoEntryPointTest extends TestCase
{
public function testException(): void
{
$e = new NoEntryPoint();
static::assertSame('No entry point provided', $e->getMessage());
}
}
Loading

0 comments on commit c0a9650

Please sign in to comment.