Skip to content

Commit 88d8200

Browse files
authored
Merge pull request #281 from php-school/context
Add context objects
2 parents 91d8e92 + a009c67 commit 88d8200

File tree

9 files changed

+453
-0
lines changed

9 files changed

+453
-0
lines changed

Diff for: src/Exercise/MockExercise.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\Exercise;
4+
5+
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
6+
7+
class MockExercise extends AbstractExercise implements ExerciseInterface
8+
{
9+
public function getName(): string
10+
{
11+
return 'Mock Exercise';
12+
}
13+
14+
public function getDescription(): string
15+
{
16+
return 'Mock Exercise';
17+
}
18+
19+
public function getType(): ExerciseType
20+
{
21+
return ExerciseType::CUSTOM();
22+
}
23+
24+
public function getProblem(): string
25+
{
26+
return 'problem-file.md';
27+
}
28+
}

Diff for: src/ExerciseRunner/Context/ExecutionContext.php

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;
4+
5+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
6+
use PhpSchool\PhpWorkshop\Input\Input;
7+
use PhpSchool\PhpWorkshop\Utils\Path;
8+
use PhpSchool\PhpWorkshop\Utils\System;
9+
10+
class ExecutionContext
11+
{
12+
public function __construct(
13+
private string $studentExecutionDirectory,
14+
private string $referenceExecutionDirectory,
15+
private ExerciseInterface $exercise,
16+
private Input $input,
17+
) {
18+
}
19+
20+
public static function fromInputAndExercise(Input $input, ExerciseInterface $exercise): ExecutionContext
21+
{
22+
$program = $input->hasArgument('program') ? dirname($input->getRequiredArgument('program')) : (string) getcwd();
23+
24+
return new self(
25+
$program,
26+
System::randomTempDir(),
27+
$exercise,
28+
$input
29+
);
30+
}
31+
32+
public function getExercise(): ExerciseInterface
33+
{
34+
return $this->exercise;
35+
}
36+
37+
public function getInput(): Input
38+
{
39+
return $this->input;
40+
}
41+
42+
public function hasStudentSolution(): bool
43+
{
44+
return $this->input->hasArgument('program');
45+
}
46+
47+
public function getEntryPoint(): string
48+
{
49+
if (!$this->hasStudentSolution()) {
50+
throw new NoEntryPoint();
51+
}
52+
53+
return Path::join(
54+
$this->studentExecutionDirectory,
55+
basename($this->input->getRequiredArgument('program'))
56+
);
57+
}
58+
59+
public function getStudentExecutionDirectory(): string
60+
{
61+
return $this->studentExecutionDirectory;
62+
}
63+
64+
public function getReferenceExecutionDirectory(): string
65+
{
66+
return $this->referenceExecutionDirectory;
67+
}
68+
}

Diff for: src/ExerciseRunner/Context/NoEntryPoint.php

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;
4+
5+
use PhpSchool\PhpWorkshop\Exception\RuntimeException;
6+
7+
class NoEntryPoint extends RuntimeException
8+
{
9+
public function __construct()
10+
{
11+
parent::__construct('No entry point provided');
12+
}
13+
}

Diff for: src/ExerciseRunner/Context/TestContext.php

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;
4+
5+
use PhpSchool\PhpWorkshop\Exception\RuntimeException;
6+
use PhpSchool\PhpWorkshop\Exercise\CgiExercise;
7+
use PhpSchool\PhpWorkshop\Exercise\CliExercise;
8+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
9+
use PhpSchool\PhpWorkshop\Exercise\MockExercise;
10+
use PhpSchool\PhpWorkshop\Input\Input;
11+
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
12+
use PhpSchool\PhpWorkshop\Utils\System;
13+
use Symfony\Component\Filesystem\Filesystem;
14+
use PhpSchool\PhpWorkshop\Utils\Path;
15+
16+
class TestContext extends ExecutionContext
17+
{
18+
private Filesystem $filesystem;
19+
private ExerciseInterface $exercise;
20+
private bool $studentSolutionDirWasCreated = false;
21+
private bool $referenceSolutionDirWasCreated = false;
22+
23+
public function __construct(
24+
ExerciseInterface $exercise = null,
25+
Input $input = null,
26+
string $studentDirectory = null,
27+
) {
28+
$this->exercise = $exercise ?? new MockExercise();
29+
30+
$this->filesystem = new Filesystem();
31+
32+
if ($studentDirectory === null) {
33+
$studentDirectory = System::randomTempDir();
34+
}
35+
36+
parent::__construct(
37+
$studentDirectory,
38+
System::randomTempDir(),
39+
$this->exercise,
40+
$input ? $input : new Input('test', ['program' => 'solution.php']),
41+
);
42+
}
43+
44+
public function createStudentSolutionDirectory(): void
45+
{
46+
$this->filesystem->mkdir($this->getStudentExecutionDirectory());
47+
$this->studentSolutionDirWasCreated = true;
48+
}
49+
50+
public function createReferenceSolutionDirectory(): void
51+
{
52+
$this->filesystem->mkdir($this->getReferenceExecutionDirectory());
53+
$this->referenceSolutionDirWasCreated = true;
54+
}
55+
56+
public function importStudentFileFromString(string $content, string $filename = 'solution.php'): void
57+
{
58+
if (!$this->studentSolutionDirWasCreated) {
59+
throw new RuntimeException(
60+
sprintf('Student execution directory not created. Call %s::createStudentSolutionDirectory() first.', self::class)
61+
);
62+
}
63+
64+
file_put_contents(Path::join($this->getStudentExecutionDirectory(), $filename), $content);
65+
}
66+
67+
public function importReferenceFileFromString(string $content, string $filename = 'solution.php'): void
68+
{
69+
if (!$this->referenceSolutionDirWasCreated) {
70+
throw new RuntimeException(
71+
sprintf('Reference execution directory not created. Call %s::createReferenceSolutionDirectory() first.', self::class)
72+
);
73+
}
74+
75+
file_put_contents(Path::join($this->getReferenceExecutionDirectory(), $filename), $content);
76+
}
77+
78+
public static function fromExerciseAndStudentSolution(ExerciseInterface $exercise, string $file): self
79+
{
80+
if (file_exists($file)) {
81+
$file = (string) realpath($file);
82+
}
83+
84+
$input = new Input('test', ['program' => $file]);
85+
return new self(
86+
exercise: $exercise,
87+
input: $input,
88+
studentDirectory: dirname($file)
89+
);
90+
}
91+
92+
public function __destruct()
93+
{
94+
if ($this->studentSolutionDirWasCreated) {
95+
$this->filesystem->remove($this->getStudentExecutionDirectory());
96+
}
97+
98+
if ($this->referenceSolutionDirWasCreated) {
99+
$this->filesystem->remove($this->getReferenceExecutionDirectory());
100+
}
101+
}
102+
}

Diff for: src/Utils/System.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public static function tempDir(string $path = ''): string
2323
{
2424
return Path::join(self::realpath(sys_get_temp_dir()), 'php-school', $path);
2525
}
26+
27+
public static function randomTempDir(): string
28+
{
29+
return Path::join(self::realpath(sys_get_temp_dir()), 'php-school', bin2hex(random_bytes(4)));
30+
}
2631
}

Diff for: test/ExerciseRunner/Context/ExecutionContextTest.php

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshopTest\ExerciseRunner\Context;
4+
5+
use PhpSchool\PhpWorkshop\Exercise\MockExercise;
6+
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
7+
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\NoEntryPoint;
8+
use PhpSchool\PhpWorkshop\Input\Input;
9+
use PhpSchool\PhpWorkshop\Utils\System;
10+
use PHPUnit\Framework\TestCase;
11+
12+
class ExecutionContextTest extends TestCase
13+
{
14+
public function testGetters(): void
15+
{
16+
$exercise = new MockExercise();
17+
$input = new Input('test', ['program' => 'solution.php']);
18+
$context = new ExecutionContext(
19+
'/student-dir',
20+
'/reference-dir',
21+
$exercise,
22+
$input
23+
);
24+
25+
static::assertSame($exercise, $context->getExercise());
26+
static::assertSame($input, $context->getInput());
27+
static::assertSame('/student-dir', $context->getStudentExecutionDirectory());
28+
static::assertSame('/reference-dir', $context->getReferenceExecutionDirectory());
29+
}
30+
31+
public function testHasStudentSolution(): void
32+
{
33+
$exercise = new MockExercise();
34+
$input = new Input('test', ['program' => 'solution.php']);
35+
$context = new ExecutionContext(
36+
'/student-dir',
37+
'/reference-dir',
38+
$exercise,
39+
$input
40+
);
41+
42+
static::assertTrue($context->hasStudentSolution());
43+
44+
$exercise = new MockExercise();
45+
$input = new Input('test');
46+
$context = new ExecutionContext(
47+
'/student-dir',
48+
'/reference-dir',
49+
$exercise,
50+
$input
51+
);
52+
53+
static::assertFalse($context->hasStudentSolution());
54+
}
55+
56+
public function testGetEntryPoint(): void
57+
{
58+
$exercise = new MockExercise();
59+
$input = new Input('test', ['program' => 'solution.php']);
60+
$context = new ExecutionContext(
61+
'/student-dir',
62+
'/reference-dir',
63+
$exercise,
64+
$input
65+
);
66+
67+
static::assertSame('/student-dir/solution.php', $context->getEntryPoint());
68+
}
69+
70+
public function testGetEntryPointThrowsExceptionWhenNoStudentSolution(): void
71+
{
72+
static::expectException(NoEntryPoint::class);
73+
74+
$exercise = new MockExercise();
75+
$input = new Input('test');
76+
$context = new ExecutionContext(
77+
'/student-dir',
78+
'/reference-dir',
79+
$exercise,
80+
$input
81+
);
82+
83+
$context->getEntryPoint();
84+
}
85+
86+
public function testFactory(): void
87+
{
88+
$temporaryDirectory = System::randomTempDir();
89+
90+
$input = new Input('test', ['program' => $temporaryDirectory . '/solution.php']);
91+
$exercise = new MockExercise();
92+
93+
$context = ExecutionContext::fromInputAndExercise($input, $exercise);
94+
95+
//check that student execution directory uses the parent directory of the program from the input
96+
static::assertSame($temporaryDirectory, $context->getStudentExecutionDirectory());
97+
static::assertSame($temporaryDirectory . '/solution.php', $context->getEntryPoint());
98+
99+
//check that reference execution directory is a random temporary directory
100+
static::assertTrue(str_starts_with($context->getReferenceExecutionDirectory(), System::tempDir()));
101+
}
102+
}

Diff for: test/ExerciseRunner/Context/NoEntryPointTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshopTest\ExerciseRunner\Context;
4+
5+
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\NoEntryPoint;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class NoEntryPointTest extends TestCase
9+
{
10+
public function testException(): void
11+
{
12+
$e = new NoEntryPoint();
13+
static::assertSame('No entry point provided', $e->getMessage());
14+
}
15+
}

0 commit comments

Comments
 (0)