Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 29eeaf7

Browse files
committedMay 7, 2024·
Context
1 parent be53db0 commit 29eeaf7

13 files changed

+576
-0
lines changed
 

‎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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 function getExercise(): ExerciseInterface
21+
{
22+
return $this->exercise;
23+
}
24+
25+
public function getInput(): Input
26+
{
27+
return $this->input;
28+
}
29+
30+
public function hasStudentSolution(): bool
31+
{
32+
return $this->input->hasArgument('program');
33+
}
34+
35+
public function getEntryPoint(): string
36+
{
37+
if (!$this->hasStudentSolution()) {
38+
throw new NoEntryPoint();
39+
}
40+
41+
return Path::join(
42+
$this->studentExecutionDirectory,
43+
basename($this->input->getRequiredArgument('program'))
44+
);
45+
}
46+
47+
public function getStudentExecutionDirectory(): string
48+
{
49+
return $this->studentExecutionDirectory;
50+
}
51+
52+
public function getReferenceExecutionDirectory(): string
53+
{
54+
return $this->referenceExecutionDirectory;
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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\System;
8+
9+
class ExecutionContextFactory
10+
{
11+
public function fromInputAndExercise(Input $input, ExerciseInterface $exercise): ExecutionContext
12+
{
13+
return new ExecutionContext(
14+
dirname($input->getRequiredArgument('program')),
15+
System::randomTempDir(),
16+
$exercise,
17+
$input
18+
);
19+
}
20+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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\System;
8+
use Symfony\Component\Filesystem\Filesystem;
9+
10+
class StaticExecutionContextFactory extends ExecutionContextFactory
11+
{
12+
public function __construct(private TestContext $context)
13+
{
14+
}
15+
16+
public function fromInputAndExercise(Input $input, ExerciseInterface $exercise): ExecutionContext
17+
{
18+
return $this->context;
19+
}
20+
}
+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\ExerciseRunner\Context;
4+
5+
use PhpSchool\PhpWorkshop\Exception\RuntimeException;
6+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
7+
use PhpSchool\PhpWorkshop\Exercise\MockExercise;
8+
use PhpSchool\PhpWorkshop\Input\Input;
9+
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
10+
use PhpSchool\PhpWorkshop\Utils\System;
11+
use Symfony\Component\Filesystem\Filesystem;
12+
use PhpSchool\PhpWorkshop\Utils\Path;
13+
14+
class TestContext extends ExecutionContext
15+
{
16+
public Filesystem $filesystem;
17+
public ExerciseInterface $exercise;
18+
19+
private function __construct(
20+
ExerciseInterface $exercise = null,
21+
Input $input = null
22+
) {
23+
$this->exercise = $exercise ?? new MockExercise();
24+
25+
$this->filesystem = new Filesystem();
26+
27+
parent::__construct(
28+
System::randomTempDir(),
29+
System::randomTempDir(),
30+
$this->exercise,
31+
$input ? $input : new Input('test', ['program' => 'solution.php']),
32+
);
33+
}
34+
35+
public function importStudentFileFromString(string $content, string $filename = 'solution.php'): void
36+
{
37+
if (!$this->filesystem->exists($this->getStudentExecutionDirectory())) {
38+
throw new RuntimeException(
39+
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
40+
);
41+
}
42+
43+
file_put_contents(Path::join($this->getStudentExecutionDirectory(), $filename), $content);
44+
}
45+
46+
public function importStudentSolution(string $file): void
47+
{
48+
if (!$this->filesystem->exists($this->getStudentExecutionDirectory())) {
49+
throw new RuntimeException(
50+
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
51+
);
52+
}
53+
54+
copy($file, Path::join($this->getStudentExecutionDirectory(), 'solution.php'));
55+
}
56+
57+
public function importStudentSolutionFolder(string $folder): void
58+
{
59+
if (!$this->filesystem->exists($this->getStudentExecutionDirectory())) {
60+
throw new RuntimeException(
61+
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
62+
);
63+
}
64+
65+
$this->filesystem->mirror($folder, $this->getStudentExecutionDirectory());
66+
}
67+
68+
public function importReferenceFileFromString(string $content, string $filename = 'solution.php'): void
69+
{
70+
if (!$this->filesystem->exists($this->getReferenceExecutionDirectory())) {
71+
throw new RuntimeException(
72+
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
73+
);
74+
}
75+
76+
file_put_contents(Path::join($this->getReferenceExecutionDirectory(), $filename), $content);
77+
}
78+
79+
public function importReferenceSolution(SolutionInterface $solution): void
80+
{
81+
if (!$this->filesystem->exists($this->getReferenceExecutionDirectory())) {
82+
throw new RuntimeException(
83+
sprintf('Execution directories not created. Use %s::withDirectories() method instead.', self::class)
84+
);
85+
}
86+
87+
foreach ($solution->getFiles() as $file) {
88+
$this->filesystem->copy(
89+
$file->getAbsolutePath(),
90+
Path::join($this->getReferenceExecutionDirectory(), $file->getRelativePath())
91+
);
92+
}
93+
}
94+
95+
public static function withDirectories(Input $input = null, ExerciseInterface $exercise = null): self
96+
{
97+
$self = new self($exercise, $input);
98+
99+
$self->filesystem->mkdir($self->getStudentExecutionDirectory());
100+
$self->filesystem->mkdir($self->getReferenceExecutionDirectory());
101+
102+
return $self;
103+
}
104+
105+
public static function withoutDirectories(Input $input = null, ExerciseInterface $exercise = null): self
106+
{
107+
return new self($exercise, $input);
108+
}
109+
110+
public function __destruct()
111+
{
112+
$this->filesystem->remove($this->getStudentExecutionDirectory());
113+
$this->filesystem->remove($this->getReferenceExecutionDirectory());
114+
}
115+
}

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

0 commit comments

Comments
 (0)
Please sign in to comment.