Skip to content

Commit e67b81f

Browse files
committed
File comparison check
1 parent d6374ab commit e67b81f

File tree

5 files changed

+250
-0
lines changed

5 files changed

+250
-0
lines changed

app/config.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
declare(strict_types=1);
44

55
use Colors\Color;
6+
use PhpSchool\PhpWorkshop\Check\FileComparisonCheck;
67
use PhpSchool\PhpWorkshop\Listener\InitialCodeListener;
78
use PhpSchool\PhpWorkshop\Logger\Logger;
9+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
10+
use PhpSchool\PhpWorkshop\ResultRenderer\FileComparisonFailureRenderer;
811
use Psr\Log\LoggerInterface;
912
use function DI\create;
1013
use function DI\factory;
@@ -116,6 +119,7 @@
116119
$c->get(ComposerCheck::class),
117120
$c->get(FunctionRequirementsCheck::class),
118121
$c->get(DatabaseCheck::class),
122+
$c->get(FileComparisonCheck::class)
119123
]);
120124
},
121125
CommandRouter::class => function (ContainerInterface $c) {
@@ -252,6 +256,7 @@
252256
},
253257
DatabaseCheck::class => create(),
254258
ComposerCheck::class => create(),
259+
FileComparisonCheck::class => create(),
255260

256261
//Utils
257262
Filesystem::class => create(),
@@ -323,6 +328,7 @@ function (CgiResult $result) use ($c) {
323328
$factory->registerRenderer(CliRequestFailure::class, CliRequestFailureRenderer::class);
324329

325330
$factory->registerRenderer(ComparisonFailure::class, ComparisonFailureRenderer::class);
331+
$factory->registerRenderer(FileComparisonFailure::class, FileComparisonFailureRenderer::class);
326332

327333
return $factory;
328334
},

src/Check/FileComparisonCheck.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Check;
6+
7+
use InvalidArgumentException;
8+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
9+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
10+
use PhpSchool\PhpWorkshop\ExerciseCheck\FileComparisonExerciseCheck;
11+
use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck;
12+
use PhpSchool\PhpWorkshop\Input\Input;
13+
use PhpSchool\PhpWorkshop\Result\Failure;
14+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
15+
use PhpSchool\PhpWorkshop\Result\ResultInterface;
16+
use PhpSchool\PhpWorkshop\Result\Success;
17+
18+
/**
19+
* This check verifies that any additional files which should be created by a student, match the ones
20+
* created by the reference solution.
21+
*/
22+
class FileComparisonCheck implements SimpleCheckInterface
23+
{
24+
/**
25+
* Return the check's name.
26+
*/
27+
public function getName(): string
28+
{
29+
return 'File Comparison Check';
30+
}
31+
32+
/**
33+
* Simply check that the file exists.
34+
*
35+
* @param ExerciseInterface $exercise The exercise to check against.
36+
* @param Input $input The command line arguments passed to the command.
37+
* @return ResultInterface The result of the check.
38+
*/
39+
public function check(ExerciseInterface $exercise, Input $input): ResultInterface
40+
{
41+
if (!$exercise instanceof FileComparisonExerciseCheck) {
42+
throw new InvalidArgumentException();
43+
}
44+
45+
foreach ($exercise->getFilesToCompare() as $file) {
46+
$studentFile = dirname($input->getRequiredArgument('program')) . '/' . ltrim($file, '/');
47+
$referenceFile = $exercise->getSolution()->getBaseDirectory() . '/' . ltrim($file, '/');
48+
49+
if (!file_exists($referenceFile)) {
50+
throw new Exception('Reference file does not exit');
51+
}
52+
53+
if (!file_exists($studentFile)) {
54+
return Failure::fromCheckAndReason(
55+
$this,
56+
sprintf('File: "%s" does not exist', $file)
57+
);
58+
}
59+
60+
$actual = file_get_contents($studentFile);
61+
$expected = file_get_contents($referenceFile);
62+
63+
if ($expected !== $actual) {
64+
return new FileComparisonFailure($this, $file, $expected, $actual);
65+
}
66+
}
67+
68+
return Success::fromCheck($this);
69+
}
70+
71+
/**
72+
* This check can run on any exercise type.
73+
*
74+
* @param ExerciseType $exerciseType
75+
* @return bool
76+
*/
77+
public function canRun(ExerciseType $exerciseType): bool
78+
{
79+
return in_array($exerciseType->getValue(), [ExerciseType::CGI, ExerciseType::CLI], true);
80+
}
81+
82+
public function getExerciseInterface(): string
83+
{
84+
return FileComparisonExerciseCheck::class;
85+
}
86+
87+
/**
88+
* This check must run after executing the solution because the files will not exist otherwise.
89+
*/
90+
public function getPosition(): string
91+
{
92+
return SimpleCheckInterface::CHECK_AFTER;
93+
}
94+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\ExerciseCheck;
6+
7+
/**
8+
* This interface should be implemented when you require the check `\PhpSchool\PhpWorkshop\Check\FileComparisonCheck` in your
9+
* exercise.
10+
*/
11+
interface FileComparisonExerciseCheck
12+
{
13+
public function getFilesToCompare(): array;
14+
}

src/Result/FileComparisonFailure.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Result;
6+
7+
use PhpSchool\PhpWorkshop\Check\CheckInterface;
8+
9+
/**
10+
* Result to represent a failed file comparison
11+
*/
12+
class FileComparisonFailure implements FailureInterface
13+
{
14+
use ResultTrait;
15+
16+
/**
17+
* @var string
18+
*/
19+
private $fileName;
20+
21+
/**
22+
* @var string
23+
*/
24+
private $expectedValue;
25+
26+
/**
27+
* @var string
28+
*/
29+
private $actualValue;
30+
31+
/**
32+
* @param CheckInterface $check The check that produced this result.
33+
* @param string $fileName
34+
* @param string $expectedValue
35+
* @param string $actualValue
36+
*/
37+
public function __construct(CheckInterface $check, string $fileName, string $expectedValue, string $actualValue)
38+
{
39+
$this->check = $check;
40+
$this->fileName = $fileName;
41+
$this->expectedValue = $expectedValue;
42+
$this->actualValue = $actualValue;
43+
}
44+
45+
/**
46+
* Get the name of the file to be verified
47+
*
48+
* @return string
49+
*/
50+
public function getFileName(): string
51+
{
52+
return $this->fileName;
53+
}
54+
55+
/**
56+
* Get the expected value.
57+
*
58+
* @return string
59+
*/
60+
public function getExpectedValue(): string
61+
{
62+
return $this->expectedValue;
63+
}
64+
65+
/**
66+
* Get the actual value.
67+
*
68+
* @return string
69+
*/
70+
public function getActualValue(): string
71+
{
72+
return $this->actualValue;
73+
}
74+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\ResultRenderer;
6+
7+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
8+
9+
/**
10+
* Renderer for `PhpSchool\PhpWorkshop\Result\FileComparisonFailure`.
11+
*/
12+
class FileComparisonFailureRenderer implements ResultRendererInterface
13+
{
14+
/**
15+
* @var FileComparisonFailure
16+
*/
17+
private $result;
18+
19+
/**
20+
* @param FileComparisonFailure $result The failure.
21+
*/
22+
public function __construct(FileComparisonFailure $result)
23+
{
24+
$this->result = $result;
25+
}
26+
27+
/**
28+
* Print the actual and expected output.
29+
*
30+
* @param ResultsRenderer $renderer
31+
* @return string
32+
*/
33+
public function render(ResultsRenderer $renderer): string
34+
{
35+
return sprintf(
36+
" %s%s\n%s\n\n %s%s\n%s\n",
37+
$renderer->style('YOUR OUTPUT FOR: ', ['bold', 'yellow']),
38+
$renderer->style($this->result->getFileName(), ['bold', 'green']),
39+
$this->indent($renderer->style(sprintf('"%s"', $this->result->getActualValue()), 'red')),
40+
$renderer->style('EXPECTED OUTPUT FOR: ', ['bold', 'yellow']),
41+
$renderer->style($this->result->getFileName(), ['bold', 'green']),
42+
$this->indent($renderer->style(sprintf('"%s"', $this->result->getExpectedValue()), 'green'))
43+
);
44+
}
45+
46+
/**
47+
* @param string $string
48+
* @return string
49+
*/
50+
private function indent(string $string): string
51+
{
52+
return implode(
53+
"\n",
54+
array_map(
55+
function ($line) {
56+
return sprintf(' %s', $line);
57+
},
58+
explode("\n", $string)
59+
)
60+
);
61+
}
62+
}

0 commit comments

Comments
 (0)