From 822e30628a5cd0803c1a89711a01ec5c465cc57e Mon Sep 17 00:00:00 2001 From: Michael Woodward Date: Wed, 19 May 2021 21:39:41 +0100 Subject: [PATCH] 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 --- src/Listener/CodePatchListener.php | 44 ++++++++++---------- test/Asset/ProvidesSolutionExercise.php | 50 +++++++++++++++++++++++ test/Asset/provided-solution/solution.php | 3 ++ test/Listener/CodePatchListenerTest.php | 45 +++++++++++++------- 4 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 test/Asset/ProvidesSolutionExercise.php create mode 100644 test/Asset/provided-solution/solution.php diff --git a/src/Listener/CodePatchListener.php b/src/Listener/CodePatchListener.php index a1266167..6fc12f0b 100644 --- a/src/Listener/CodePatchListener.php +++ b/src/Listener/CodePatchListener.php @@ -5,11 +5,13 @@ namespace PhpSchool\PhpWorkshop\Listener; use PhpSchool\PhpWorkshop\CodePatcher; +use PhpSchool\PhpWorkshop\Event\EventInterface; use PhpSchool\PhpWorkshop\Event\ExerciseRunnerEvent; +use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution; use RuntimeException; /** - * Listener which patches student's solutions + * Listener which patches internal and student's solutions */ class CodePatchListener { @@ -19,9 +21,9 @@ class CodePatchListener private $codePatcher; /** - * @var string + * @var array */ - private $originalCode; + private $originalCode = []; /** * @param CodePatcher $codePatcher @@ -36,34 +38,34 @@ public function __construct(CodePatcher $codePatcher) */ public function patch(ExerciseRunnerEvent $event): void { - $fileName = $event->getInput()->getArgument('program'); + $files = [$event->getInput()->getArgument('program')]; - if (null === $fileName) { - return; + $exercise = $event->getExercise(); + if ($exercise instanceof ProvidesSolution) { + $files[] = $exercise->getSolution()->getEntryPoint(); } - $this->originalCode = (string) file_get_contents($fileName); - file_put_contents( - $fileName, - $this->codePatcher->patch($event->getExercise(), $this->originalCode) - ); + foreach (array_filter($files) as $fileName) { + $this->originalCode[$fileName] = (string) file_get_contents($fileName); + + file_put_contents( + $fileName, + $this->codePatcher->patch($event->getExercise(), $this->originalCode[$fileName]) + ); + } } /** - * @param ExerciseRunnerEvent $event + * @param EventInterface $event */ - public function revert(ExerciseRunnerEvent $event): void + public function revert(EventInterface $event): void { - if (null === $this->originalCode) { - throw new RuntimeException('Can only revert previously patched code'); - } - - $fileName = $event->getInput()->getArgument('program'); - - if (null === $fileName) { + if (null === $this->originalCode || empty($this->originalCode)) { return; } - file_put_contents($fileName, $this->originalCode); + foreach ($this->originalCode as $fileName => $contents) { + file_put_contents($fileName, $contents); + } } } diff --git a/test/Asset/ProvidesSolutionExercise.php b/test/Asset/ProvidesSolutionExercise.php new file mode 100644 index 00000000..b86f8dd9 --- /dev/null +++ b/test/Asset/ProvidesSolutionExercise.php @@ -0,0 +1,50 @@ +filesystem = new Filesystem(); $this->codePatcher = $this->createMock(CodePatcher::class); - $this->file = sprintf('%s/%s/submission.php', str_replace('\\', '/', sys_get_temp_dir()), $this->getName()); + $this->file = sprintf('%s/%s/submission.php', System::tempDir(), $this->getName()); mkdir(dirname($this->file), 0775, true); touch($this->file); + + $this->solution = sprintf('%s/%s/solution.php', System::tempDir(), $this->getName()); + touch($this->solution); } - public function testRevertThrowsExceptionIfPatchNotPreviouslyCalled(): void + public function testPatchUpdatesCode(): void { + file_put_contents($this->file, 'ORIGINAL CONTENT'); + $input = new Input('app', ['program' => $this->file]); $exercise = $this->createMock(ExerciseInterface::class); + $this->codePatcher + ->expects($this->once()) + ->method('patch') + ->with($exercise, 'ORIGINAL CONTENT') + ->willReturn('MODIFIED CONTENT'); + $listener = new CodePatchListener($this->codePatcher); $event = new ExerciseRunnerEvent('event', $exercise, $input); + $listener->patch($event); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can only revert previously patched code'); - $listener->revert($event); + self::assertStringEqualsFile($this->file, 'MODIFIED CONTENT'); } - public function testPatchUpdatesCode(): void + public function testRevertAfterPatch(): void { file_put_contents($this->file, 'ORIGINAL CONTENT'); @@ -67,29 +83,30 @@ public function testPatchUpdatesCode(): void $listener = new CodePatchListener($this->codePatcher); $event = new ExerciseRunnerEvent('event', $exercise, $input); $listener->patch($event); + $listener->revert($event); - $this->assertStringEqualsFile($this->file, 'MODIFIED CONTENT'); + self::assertStringEqualsFile($this->file, 'ORIGINAL CONTENT'); } - public function testRevertAfterPatch(): void + public function testPatchesProvidedSolution(): void { file_put_contents($this->file, 'ORIGINAL CONTENT'); $input = new Input('app', ['program' => $this->file]); - $exercise = $this->createMock(ExerciseInterface::class); + $exercise = new ProvidesSolutionExercise(); $this->codePatcher - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('patch') - ->with($exercise, 'ORIGINAL CONTENT') + ->withConsecutive([$exercise, 'ORIGINAL CONTENT'], [$exercise, "willReturn('MODIFIED CONTENT'); $listener = new CodePatchListener($this->codePatcher); $event = new ExerciseRunnerEvent('event', $exercise, $input); $listener->patch($event); - $listener->revert($event); - $this->assertStringEqualsFile($this->file, 'ORIGINAL CONTENT'); + self::assertStringEqualsFile($this->file, 'MODIFIED CONTENT'); + self::assertStringEqualsFile($exercise->getSolution()->getEntryPoint(), 'MODIFIED CONTENT'); } public function tearDown(): void