Skip to content

Commit 822e306

Browse files
committed
Patch internal solution entrypoint files
When patching a students given solution, we also need to patch our internal solution. This ensures that the comparison is like for like and the output solution would match what a student would have to provide
1 parent 173b6d4 commit 822e306

File tree

4 files changed

+107
-35
lines changed

4 files changed

+107
-35
lines changed

src/Listener/CodePatchListener.php

+23-21
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
namespace PhpSchool\PhpWorkshop\Listener;
66

77
use PhpSchool\PhpWorkshop\CodePatcher;
8+
use PhpSchool\PhpWorkshop\Event\EventInterface;
89
use PhpSchool\PhpWorkshop\Event\ExerciseRunnerEvent;
10+
use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution;
911
use RuntimeException;
1012

1113
/**
12-
* Listener which patches student's solutions
14+
* Listener which patches internal and student's solutions
1315
*/
1416
class CodePatchListener
1517
{
@@ -19,9 +21,9 @@ class CodePatchListener
1921
private $codePatcher;
2022

2123
/**
22-
* @var string
24+
* @var array<string, string>
2325
*/
24-
private $originalCode;
26+
private $originalCode = [];
2527

2628
/**
2729
* @param CodePatcher $codePatcher
@@ -36,34 +38,34 @@ public function __construct(CodePatcher $codePatcher)
3638
*/
3739
public function patch(ExerciseRunnerEvent $event): void
3840
{
39-
$fileName = $event->getInput()->getArgument('program');
41+
$files = [$event->getInput()->getArgument('program')];
4042

41-
if (null === $fileName) {
42-
return;
43+
$exercise = $event->getExercise();
44+
if ($exercise instanceof ProvidesSolution) {
45+
$files[] = $exercise->getSolution()->getEntryPoint();
4346
}
4447

45-
$this->originalCode = (string) file_get_contents($fileName);
46-
file_put_contents(
47-
$fileName,
48-
$this->codePatcher->patch($event->getExercise(), $this->originalCode)
49-
);
48+
foreach (array_filter($files) as $fileName) {
49+
$this->originalCode[$fileName] = (string) file_get_contents($fileName);
50+
51+
file_put_contents(
52+
$fileName,
53+
$this->codePatcher->patch($event->getExercise(), $this->originalCode[$fileName])
54+
);
55+
}
5056
}
5157

5258
/**
53-
* @param ExerciseRunnerEvent $event
59+
* @param EventInterface $event
5460
*/
55-
public function revert(ExerciseRunnerEvent $event): void
61+
public function revert(EventInterface $event): void
5662
{
57-
if (null === $this->originalCode) {
58-
throw new RuntimeException('Can only revert previously patched code');
59-
}
60-
61-
$fileName = $event->getInput()->getArgument('program');
62-
63-
if (null === $fileName) {
63+
if (null === $this->originalCode || empty($this->originalCode)) {
6464
return;
6565
}
6666

67-
file_put_contents($fileName, $this->originalCode);
67+
foreach ($this->originalCode as $fileName => $contents) {
68+
file_put_contents($fileName, $contents);
69+
}
6870
}
6971
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshopTest\Asset;
6+
7+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
8+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
9+
use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution;
10+
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
11+
use PhpSchool\PhpWorkshop\Solution\SingleFileSolution;
12+
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
13+
14+
class ProvidesSolutionExercise implements ExerciseInterface, ProvidesSolution
15+
{
16+
public function getName(): string
17+
{
18+
return 'exercise-provides-solution';
19+
}
20+
21+
public function getType(): ExerciseType
22+
{
23+
// TODO: Implement getType() method.
24+
}
25+
26+
public function getProblem(): string
27+
{
28+
// TODO: Implement getProblem() method.
29+
}
30+
31+
public function configure(ExerciseDispatcher $dispatcher): void
32+
{
33+
// TODO: Implement configure() method.
34+
}
35+
36+
public function getDescription(): string
37+
{
38+
// TODO: Implement getDescription() method.
39+
}
40+
41+
public function tearDown(): void
42+
{
43+
// TODO: Implement tearDown() method.
44+
}
45+
46+
public function getSolution(): SolutionInterface
47+
{
48+
return SingleFileSolution::fromFile(__DIR__ . '/provided-solution/solution.php');
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
echo 'Hello World';

test/Listener/CodePatchListenerTest.php

+31-14
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
88
use PhpSchool\PhpWorkshop\Input\Input;
99
use PhpSchool\PhpWorkshop\Listener\CodePatchListener;
10+
use PhpSchool\PhpWorkshop\Utils\System;
11+
use PhpSchool\PhpWorkshopTest\Asset\ProvidesSolutionExercise;
1012
use PHPUnit\Framework\TestCase;
11-
use RuntimeException;
1213
use Symfony\Component\Filesystem\Filesystem;
1314

1415
class CodePatchListenerTest extends TestCase
@@ -18,6 +19,11 @@ class CodePatchListenerTest extends TestCase
1819
*/
1920
private $file;
2021

22+
/**
23+
* @var string
24+
*/
25+
private $solution;
26+
2127
/**
2228
* @var Filesystem
2329
*/
@@ -33,25 +39,35 @@ public function setUp(): void
3339
$this->filesystem = new Filesystem();
3440
$this->codePatcher = $this->createMock(CodePatcher::class);
3541

36-
$this->file = sprintf('%s/%s/submission.php', str_replace('\\', '/', sys_get_temp_dir()), $this->getName());
42+
$this->file = sprintf('%s/%s/submission.php', System::tempDir(), $this->getName());
3743
mkdir(dirname($this->file), 0775, true);
3844
touch($this->file);
45+
46+
$this->solution = sprintf('%s/%s/solution.php', System::tempDir(), $this->getName());
47+
touch($this->solution);
3948
}
4049

41-
public function testRevertThrowsExceptionIfPatchNotPreviouslyCalled(): void
50+
public function testPatchUpdatesCode(): void
4251
{
52+
file_put_contents($this->file, 'ORIGINAL CONTENT');
53+
4354
$input = new Input('app', ['program' => $this->file]);
4455
$exercise = $this->createMock(ExerciseInterface::class);
4556

57+
$this->codePatcher
58+
->expects($this->once())
59+
->method('patch')
60+
->with($exercise, 'ORIGINAL CONTENT')
61+
->willReturn('MODIFIED CONTENT');
62+
4663
$listener = new CodePatchListener($this->codePatcher);
4764
$event = new ExerciseRunnerEvent('event', $exercise, $input);
65+
$listener->patch($event);
4866

49-
$this->expectException(RuntimeException::class);
50-
$this->expectExceptionMessage('Can only revert previously patched code');
51-
$listener->revert($event);
67+
self::assertStringEqualsFile($this->file, 'MODIFIED CONTENT');
5268
}
5369

54-
public function testPatchUpdatesCode(): void
70+
public function testRevertAfterPatch(): void
5571
{
5672
file_put_contents($this->file, 'ORIGINAL CONTENT');
5773

@@ -67,29 +83,30 @@ public function testPatchUpdatesCode(): void
6783
$listener = new CodePatchListener($this->codePatcher);
6884
$event = new ExerciseRunnerEvent('event', $exercise, $input);
6985
$listener->patch($event);
86+
$listener->revert($event);
7087

71-
$this->assertStringEqualsFile($this->file, 'MODIFIED CONTENT');
88+
self::assertStringEqualsFile($this->file, 'ORIGINAL CONTENT');
7289
}
7390

74-
public function testRevertAfterPatch(): void
91+
public function testPatchesProvidedSolution(): void
7592
{
7693
file_put_contents($this->file, 'ORIGINAL CONTENT');
7794

7895
$input = new Input('app', ['program' => $this->file]);
79-
$exercise = $this->createMock(ExerciseInterface::class);
96+
$exercise = new ProvidesSolutionExercise();
8097

8198
$this->codePatcher
82-
->expects($this->once())
99+
->expects($this->exactly(2))
83100
->method('patch')
84-
->with($exercise, 'ORIGINAL CONTENT')
101+
->withConsecutive([$exercise, 'ORIGINAL CONTENT'], [$exercise, "<?php\n\necho 'Hello World';\n"])
85102
->willReturn('MODIFIED CONTENT');
86103

87104
$listener = new CodePatchListener($this->codePatcher);
88105
$event = new ExerciseRunnerEvent('event', $exercise, $input);
89106
$listener->patch($event);
90-
$listener->revert($event);
91107

92-
$this->assertStringEqualsFile($this->file, 'ORIGINAL CONTENT');
108+
self::assertStringEqualsFile($this->file, 'MODIFIED CONTENT');
109+
self::assertStringEqualsFile($exercise->getSolution()->getEntryPoint(), 'MODIFIED CONTENT');
93110
}
94111

95112
public function tearDown(): void

0 commit comments

Comments
 (0)