Skip to content

Commit 17a4756

Browse files
authored
Merge c4c5bc0 into d6374ab
2 parents d6374ab + c4c5bc0 commit 17a4756

File tree

7 files changed

+310
-0
lines changed

7 files changed

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

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+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshopTest\Result;
6+
7+
use PhpSchool\PhpWorkshop\Check\CheckInterface;
8+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class FileComparisonFailureTest extends TestCase
12+
{
13+
public function testGetters(): void
14+
{
15+
$check = $this->createMock(CheckInterface::class);
16+
$check
17+
->method('getName')
18+
->willReturn('Some Check');
19+
20+
$failure = new FileComparisonFailure($check, 'users.txt', 'Expected Output', 'Actual Output');
21+
$this->assertEquals('Expected Output', $failure->getExpectedValue());
22+
$this->assertEquals('Actual Output', $failure->getActualValue());
23+
$this->assertEquals('users.txt', $failure->getFileName());
24+
}
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshopTest\ResultRenderer;
6+
7+
use PhpSchool\PhpWorkshop\Check\CheckInterface;
8+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
9+
use PhpSchool\PhpWorkshop\ResultRenderer\FileComparisonFailureRenderer;
10+
11+
class FileComparisonFailureRendererTest extends AbstractResultRendererTest
12+
{
13+
public function testRender(): void
14+
{
15+
$failure = new FileComparisonFailure(
16+
$this->createMock(CheckInterface::class),
17+
'some-file.text',
18+
'EXPECTED OUTPUT',
19+
'ACTUAL OUTPUT'
20+
);
21+
$renderer = new FileComparisonFailureRenderer($failure);
22+
23+
$expected = " \e[33m\e[1mYOUR OUTPUT FOR: \e[0m\e[0m\e[32m\e[1msome-file.text\e[0m\e[0m\n";
24+
$expected .= " \e[31m\"ACTUAL OUTPUT\"\e[0m\n\n";
25+
$expected .= " \e[33m\e[1mEXPECTED OUTPUT FOR: \e[0m\e[0m\e[32m\e[1msome-file.text\e[0m\e[0m\n";
26+
$expected .= " \e[32m\"EXPECTED OUTPUT\"\e[0m\n";
27+
28+
$this->assertEquals($expected, $renderer->render($this->getRenderer()));
29+
}
30+
}

0 commit comments

Comments
 (0)