Skip to content

Commit 471c0e2

Browse files
committed
Make project (unit) testable
1 parent d93b64e commit 471c0e2

14 files changed

+200
-32
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ vendor
33
composer.lock
44
.phpcstd.ini
55
cghooks.lock
6+
.phpunit.result.cache

Diff for: .phpcstd.dist.ini

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
; Define a list of source folders to check against
1010
source[] = composer.json
1111
source[] = src
12+
source[] = tests
1213

1314
; Runs the command with --fix enabled before continuing as usual
1415
autofix = false

Diff for: composer.json

+9-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@
3232
"phpstan/phpstan": "^0.12",
3333
"phpstan/phpstan-deprecation-rules": "^0.12",
3434
"phpstan/phpstan-strict-rules": "^0.12",
35-
"psalm/phar": "^4.3",
35+
"phpunit/phpunit": "^9.5",
36+
"psalm/plugin-phpunit": "^0.15.0",
3637
"squizlabs/php_codesniffer": "^3.5",
37-
"symplify/easy-coding-standard-prefixed": "^9"
38+
"symplify/easy-coding-standard-prefixed": "^9",
39+
"vimeo/psalm": "^4.3"
3840
},
3941
"suggest": {
4042
"ergebnis/composer-normalize": "Normalizes composer.json files",
@@ -63,6 +65,11 @@
6365
"Spaceemotion\\PhpCodingStandard\\": "src/"
6466
}
6567
},
68+
"autoload-dev": {
69+
"psr-4": {
70+
"Tests\\": "tests/"
71+
}
72+
},
6673
"minimum-stability": "dev",
6774
"prefer-stable": true,
6875
"bin": [

Diff for: ecs.php

+6
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@
3131
'imports_order' => ['class', 'function', 'const'],
3232
]]);
3333

34+
$services->set(PhpCsFixer\Fixer\PhpUnit\PhpUnitMethodCasingFixer::class)
35+
->call('configure', [[
36+
'case' => 'snake_case',
37+
]]);
38+
3439
$parameters = $containerConfigurator->parameters();
3540
$parameters->set(Option::LINE_ENDING, "\n");
3641
$parameters->set(Option::PATHS, [
3742
__DIR__ . '/bin',
3843
__DIR__ . '/src',
44+
__DIR__ . '/tests',
3945
]);
4046

4147
$parameters->set(Option::SETS, [

Diff for: phpcs.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
<arg name="encoding" value="utf-8"/>
44

55
<!-- Ruleset base -->
6-
<rule ref="PSR12" />
6+
<rule ref="PSR12">
7+
<!-- Allow method names like "it_validates_a_request" in tests -->
8+
<exclude name="PSR1.Methods.CamelCapsMethodName">
9+
<exclude-pattern>tests/*</exclude-pattern>
10+
</exclude>
11+
</rule>
712

813
<!-- Some custom formatting preferences -->
914
<rule ref="Generic.Arrays.ArrayIndent">

Diff for: phpstan.neon

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ parameters:
88
paths:
99
- bin
1010
- src
11+
- tests
1112

1213
excludes_analyse:
1314
- ecs.php

Diff for: phpunit.xml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
5+
bootstrap="vendor/autoload.php"
6+
colors="true"
7+
>
8+
<testsuites>
9+
<testsuite name="Unit">
10+
<directory>tests</directory>
11+
</testsuite>
12+
</testsuites>
13+
</phpunit>

Diff for: psalm.xml

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
88
>
99
<projectFiles>
10-
<directory name="src" />
10+
<directory name="src"/>
11+
<directory name="tests"/>
1112
<ignoreFiles>
12-
<directory name="vendor" />
13-
<file name="ecs.php" />
13+
<directory name="vendor"/>
14+
<file name="ecs.php"/>
1415
</ignoreFiles>
1516
</projectFiles>
1617

@@ -27,4 +28,8 @@
2728
</errorLevel>
2829
</UnresolvableInclude>
2930
</issueHandlers>
31+
32+
<plugins>
33+
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
34+
</plugins>
3035
</psalm>

Diff for: src/Cli.php

+29-16
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class Cli
5959
/** @var string[] */
6060
private $flags;
6161

62-
/** @var string[] */
62+
/** @var string[][] */
6363
private $parameters;
6464

6565
/** @var Config */
@@ -68,15 +68,14 @@ class Cli
6868
/**
6969
* @param string[] $arguments
7070
*
71-
* @SuppressWarnings(PHPMD.ExitExpression)
71+
* @throws ExitException
7272
*/
7373
public function __construct(array $arguments)
7474
{
7575
[$this->flags, $this->parameters, $this->files] = $this->parseFlags(array_slice($arguments, 1));
7676

7777
if ($this->hasFlag(self::FLAG_HELP)) {
78-
$this->showHelp();
79-
exit(0);
78+
throw new ExitException($this->showHelp());
8079
}
8180

8281
$this->lintStaged();
@@ -137,6 +136,14 @@ public function hasFlag(string $flag): bool
137136
return in_array($flag, $this->flags, true);
138137
}
139138

139+
/**
140+
* @return string[]
141+
*/
142+
public function getParameter(string $parameter): array
143+
{
144+
return $this->parameters[$parameter] ?? [];
145+
}
146+
140147
public static function isOnWindows(): bool
141148
{
142149
if (defined('PHP_OS_FAMILY')) {
@@ -150,7 +157,7 @@ public static function isOnWindows(): bool
150157
* Calls 'git diff' to determine changed files.
151158
* Also exists if no files have been changed.
152159
*
153-
* @SuppressWarnings(PHPMD.ExitExpression)
160+
* @throws ExitException
154161
*/
155162
private function lintStaged(): void
156163
{
@@ -168,33 +175,36 @@ private function lintStaged(): void
168175
}, $output));
169176

170177
if ($this->files === []) {
171-
echo 'No files staged. Skipping.';
172-
exit(0);
178+
throw new ExitException('No files staged. Skipping.');
173179
}
174180
}
175181

176182
/**
177183
* Shows an overview of all available flags and options.
184+
*
185+
* @return string
178186
*/
179-
private function showHelp(): void
187+
private function showHelp(): string
180188
{
181-
echo 'Usage:' . PHP_EOL;
182-
echo ' phpcstd [options] <files or folders>' . PHP_EOL . PHP_EOL;
189+
$help = 'Usage:' . PHP_EOL;
190+
$help .= ' phpcstd [options] <files or folders>' . PHP_EOL . PHP_EOL;
183191

184-
echo 'Options:' . PHP_EOL;
192+
$help .= 'Options:' . PHP_EOL;
185193

186194
foreach (self::OPTIONS as $flag => $message) {
187-
echo " --{$flag}" . PHP_EOL . " ${message}" . PHP_EOL . PHP_EOL;
195+
$help .= " --{$flag}" . PHP_EOL . " ${message}" . PHP_EOL . PHP_EOL;
188196
}
197+
198+
return $help;
189199
}
190200

191201
/**
192202
* @param Tool[] $tools
193203
*/
194204
private function executeContext(array $tools, Context $context): bool
195205
{
196-
$disabled = $this->parseList($this->parameters[self::PARAMETER_DISABLE] ?? '');
197-
$only = $this->parseList($this->parameters[self::PARAMETER_ONLY] ?? '');
206+
$disabled = $this->getParameter(self::PARAMETER_DISABLE);
207+
$only = $this->getParameter(self::PARAMETER_ONLY);
198208

199209
$continue = $this->hasFlag(self::FLAG_CONTINUE) || $this->config->shouldContinue();
200210
$success = true;
@@ -237,7 +247,10 @@ private function executeContext(array $tools, Context $context): bool
237247

238248
/**
239249
* @param mixed[] $options
240-
* @return string[][]
250+
*
251+
* @return (mixed|string|string[])[][]
252+
*
253+
* @psalm-return array{0: list<string>, 1: array<string, list<string>>, 2: list<mixed>}
241254
*/
242255
private function parseFlags(array $options): array
243256
{
@@ -260,7 +273,7 @@ private function parseFlags(array $options): array
260273
}
261274

262275
if ($value !== '') {
263-
$parameters[$flag] = $value;
276+
$parameters[$flag] = $this->parseList($value);
264277
continue;
265278
}
266279

Diff for: src/ExitException.php

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Spaceemotion\PhpCodingStandard;
6+
7+
use Exception;
8+
9+
class ExitException extends Exception
10+
{
11+
}

Diff for: src/main.php

+14-10
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66

77
require_once 'init.php';
88

9-
exit((new Cli($argv))->start([
10-
new Tools\ComposerNormalize(),
11-
new Tools\PhpParallelLint(),
12-
new Tools\EasyCodingStandard(),
13-
new Tools\PhpCodeSniffer(),
14-
new Tools\PhpMessDetector(),
15-
new Tools\Phpstan(),
16-
new Tools\Psalm(),
17-
new Tools\Phan(),
18-
]) ? 0 : 1);
9+
try {
10+
exit((new Cli($argv))->start([
11+
new Tools\ComposerNormalize(),
12+
new Tools\PhpParallelLint(),
13+
new Tools\EasyCodingStandard(),
14+
new Tools\PhpCodeSniffer(),
15+
new Tools\PhpMessDetector(),
16+
new Tools\Phpstan(),
17+
new Tools\Psalm(),
18+
new Tools\Phan(),
19+
]) ? 0 : 1);
20+
} catch (ExitException $e) {
21+
echo $e->getMessage();
22+
}

Diff for: tests/CliTest.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use Spaceemotion\PhpCodingStandard\Cli;
8+
9+
class CliTest extends TestCase
10+
{
11+
public function test_it_starts_up_without_arguments(): void
12+
{
13+
$cli = $this->createCliInstance();
14+
15+
self::assertTrue($cli->start([]));
16+
17+
self::assertNotEmpty($this->getActualOutputForAssertion());
18+
}
19+
20+
public function test_it_runs_tools(): void
21+
{
22+
$this->expectOutputRegex('/PhpUnit/');
23+
24+
$cli = $this->createCliInstance();
25+
26+
$tool = new TestTool();
27+
28+
self::assertTrue($cli->start([$tool]));
29+
}
30+
31+
public function test_it_parses_flags_and_parameters(): void
32+
{
33+
$cli = $this->createCliInstance(
34+
'--fast',
35+
'--only=foo,bar,baz'
36+
);
37+
38+
self::assertTrue($cli->hasFlag(Cli::FLAG_FAST));
39+
40+
self::assertSame(
41+
['foo', 'bar', 'baz'],
42+
$cli->getParameter(Cli::PARAMETER_ONLY)
43+
);
44+
45+
self::assertNotEmpty($this->getActualOutputForAssertion());
46+
}
47+
48+
public function test_it_shows_options_when_help_flag_is_passed(): void
49+
{
50+
$this->expectExit('--' . Cli::FLAG_FAST);
51+
52+
$this->createCliInstance('--help');
53+
}
54+
}

Diff for: tests/TestCase.php

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use PHPUnit\Framework\TestCase as BaseTestCase;
8+
use Spaceemotion\PhpCodingStandard\Cli;
9+
use Spaceemotion\PhpCodingStandard\ExitException;
10+
11+
class TestCase extends BaseTestCase
12+
{
13+
protected function createCliInstance(string ...$arguments): Cli
14+
{
15+
return new Cli(array_merge(['phpcstd'], $arguments));
16+
}
17+
18+
protected function expectExit(string $message): void
19+
{
20+
$this->expectException(ExitException::class);
21+
$this->expectExceptionMessage($message);
22+
}
23+
}

Diff for: tests/TestTool.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use Spaceemotion\PhpCodingStandard\Context;
8+
use Spaceemotion\PhpCodingStandard\Tools\Tool;
9+
10+
class TestTool extends Tool
11+
{
12+
public function __construct(string $name = 'PhpUnit')
13+
{
14+
$this->name = $name;
15+
}
16+
17+
/**
18+
* @return true
19+
*/
20+
public function run(Context $context): bool
21+
{
22+
return true;
23+
}
24+
}

0 commit comments

Comments
 (0)