Skip to content

Commit aa34ce3

Browse files
authored
test: add failing test (#36)
1 parent 2a8ad50 commit aa34ce3

File tree

3 files changed

+536
-9
lines changed

3 files changed

+536
-9
lines changed

tests/IgnoreWhitespaceTest.php

+91-9
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
*/
1616
final class IgnoreWhitespaceTest extends TestCase
1717
{
18-
public function testIgnoreWhitespaces(): void
18+
19+
/**
20+
* @return string[][]
21+
*/
22+
public function provideIgnoreWhitespaces(): array
1923
{
20-
$old = <<<'PHP'
24+
return [
25+
[
26+
<<<'OLD'
2127
<?php
2228
2329
function foo(\DateTimeImmutable $date)
@@ -29,18 +35,17 @@ function foo(\DateTimeImmutable $date)
2935
}
3036
}
3137

32-
PHP;
33-
$new = <<<'PHP'
38+
OLD,
39+
<<<'NEW'
3440
<?php
3541
3642
function foo(\DateTimeImmutable $date)
3743
{
3844
echo 'foo';
3945
}
4046

41-
PHP;
42-
43-
$expected = <<<'DIFF'
47+
NEW,
48+
<<<'DIFF'
4449
@@ -2,9 +2,5 @@
4550
4651
function foo(\DateTimeImmutable $date)
@@ -52,14 +57,91 @@ function foo(\DateTimeImmutable $date)
5257
- }
5358
}
5459

55-
DIFF;
60+
DIFF
61+
],
62+
[
63+
<<<'OLD'
64+
<?php
65+
66+
class Foo
67+
{
68+
function foo()
69+
{
70+
echo 'haha';
71+
return;
72+
73+
echo 'blabla';
74+
if (false) {
75+
76+
}
77+
}
78+
79+
}
80+
81+
OLD,
82+
<<<'NEW'
83+
<?php
84+
85+
class Foo
86+
{
87+
function foo()
88+
{
89+
echo 'haha';
90+
return;
91+
}
92+
93+
}
94+
95+
NEW,
96+
<<<'DIFF'
97+
@@ -6,11 +6,6 @@
98+
{
99+
echo 'haha';
100+
return;
101+
-
102+
- echo 'blabla';
103+
- if (false) {
104+
-
105+
- }
106+
}
107+
108+
}
109+
110+
DIFF
111+
],
112+
[
113+
file_get_contents(__DIR__ . '/data/WorkerCommandA.php'),
114+
file_get_contents(__DIR__ . '/data/WorkerCommandB.php'),
115+
<<<'DIFF'
116+
@@ -215,11 +215,6 @@
117+
{
118+
echo 'haha';
119+
return;
120+
-
121+
- echo 'blabla';
122+
- if (false) {
123+
-
124+
- }
125+
}
126+
127+
}
128+
129+
DIFF
130+
],
131+
];
132+
}
56133

134+
/**
135+
* @dataProvider provideIgnoreWhitespaces
136+
*/
137+
public function testIgnoreWhitespaces(string $old, string $new, string $expectedDiff): void
138+
{
57139
$diff = DiffHelper::calculate($old, $new, 'Unified', [
58140
'ignoreWhitespace' => true,
59141
], [
60142
'cliColorization' => RendererConstant::CLI_COLOR_DISABLE,
61143
]);
62144

63-
static::assertSame($expected, $diff);
145+
static::assertSame($expectedDiff, $diff);
64146
}
65147
}

tests/data/WorkerCommandA.php

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Command;
4+
5+
use Clue\React\NDJson\Decoder;
6+
use Clue\React\NDJson\Encoder;
7+
use PHPStan\Analyser\FileAnalyser;
8+
use PHPStan\Analyser\NodeScopeResolver;
9+
use PHPStan\DependencyInjection\Container;
10+
use PHPStan\Rules\Registry;
11+
use React\EventLoop\StreamSelectLoop;
12+
use React\Socket\ConnectionInterface;
13+
use React\Socket\TcpConnector;
14+
use React\Stream\ReadableStreamInterface;
15+
use React\Stream\WritableStreamInterface;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Input\InputOption;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
22+
class WorkerCommand extends Command
23+
{
24+
25+
private const NAME = 'worker';
26+
27+
/** @var string[] */
28+
private array $composerAutoloaderProjectPaths;
29+
30+
/**
31+
* @param string[] $composerAutoloaderProjectPaths
32+
*/
33+
public function __construct(
34+
array $composerAutoloaderProjectPaths
35+
)
36+
{
37+
parent::__construct();
38+
$this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths;
39+
}
40+
41+
protected function configure(): void
42+
{
43+
$this->setName(self::NAME)
44+
->setDescription('(Internal) Support for parallel analysis.')
45+
->setDefinition([
46+
new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'),
47+
new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'),
48+
new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'),
49+
new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'),
50+
new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'),
51+
new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'),
52+
new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'),
53+
new InputOption('port', null, InputOption::VALUE_REQUIRED),
54+
new InputOption('identifier', null, InputOption::VALUE_REQUIRED),
55+
]);
56+
}
57+
58+
protected function execute(InputInterface $input, OutputInterface $output): int
59+
{
60+
$paths = $input->getArgument('paths');
61+
$memoryLimit = $input->getOption('memory-limit');
62+
$autoloadFile = $input->getOption('autoload-file');
63+
$configuration = $input->getOption('configuration');
64+
$level = $input->getOption(AnalyseCommand::OPTION_LEVEL);
65+
$pathsFile = $input->getOption('paths-file');
66+
$allowXdebug = $input->getOption('xdebug');
67+
$port = $input->getOption('port');
68+
$identifier = $input->getOption('identifier');
69+
70+
if (
71+
!is_array($paths)
72+
|| (!is_string($memoryLimit) && $memoryLimit !== null)
73+
|| (!is_string($autoloadFile) && $autoloadFile !== null)
74+
|| (!is_string($configuration) && $configuration !== null)
75+
|| (!is_string($level) && $level !== null)
76+
|| (!is_string($pathsFile) && $pathsFile !== null)
77+
|| (!is_bool($allowXdebug))
78+
|| !is_string($port)
79+
|| !is_string($identifier)
80+
) {
81+
throw new \PHPStan\ShouldNotHappenException();
82+
}
83+
84+
try {
85+
$inceptionResult = CommandHelper::begin(
86+
$input,
87+
$output,
88+
$paths,
89+
$pathsFile,
90+
$memoryLimit,
91+
$autoloadFile,
92+
$this->composerAutoloaderProjectPaths,
93+
$configuration,
94+
null,
95+
$level,
96+
$allowXdebug,
97+
false
98+
);
99+
} catch (\PHPStan\Command\InceptionNotSuccessfulException $e) {
100+
return 1;
101+
}
102+
$loop = new StreamSelectLoop();
103+
104+
$container = $inceptionResult->getContainer();
105+
106+
$analysedFiles = $inceptionResult->getFiles();
107+
108+
/** @var NodeScopeResolver $nodeScopeResolver */
109+
$nodeScopeResolver = $container->getByType(NodeScopeResolver::class);
110+
$nodeScopeResolver->setAnalysedFiles($analysedFiles);
111+
112+
$analysedFiles = array_fill_keys($analysedFiles, true);
113+
114+
$tcpConector = new TcpConnector($loop);
115+
$tcpConector->connect(sprintf('127.0.0.1:%d', $port))->done(function (ConnectionInterface $connection) use ($container, $identifier, $analysedFiles): void {
116+
$out = new Encoder($connection);
117+
$in = new Decoder($connection, true, 512, 0, $container->getParameter('parallel')['buffer']);
118+
$out->write(['action' => 'hello', 'identifier' => $identifier]);
119+
$this->runWorker($container, $out, $in, $analysedFiles);
120+
});
121+
122+
$loop->run();
123+
124+
return 0;
125+
}
126+
127+
/**
128+
* @param Container $container
129+
* @param WritableStreamInterface $out
130+
* @param ReadableStreamInterface $in
131+
* @param array<string, true> $analysedFiles
132+
*/
133+
private function runWorker(
134+
Container $container,
135+
WritableStreamInterface $out,
136+
ReadableStreamInterface $in,
137+
array $analysedFiles
138+
): void
139+
{
140+
$handleError = static function (\Throwable $error) use ($out): void {
141+
$out->write([
142+
'action' => 'result',
143+
'result' => [
144+
'errors' => [$error->getMessage()],
145+
'dependencies' => [],
146+
'filesCount' => 0,
147+
'internalErrorsCount' => 1,
148+
],
149+
]);
150+
$out->end();
151+
};
152+
$out->on('error', $handleError);
153+
154+
/** @var FileAnalyser $fileAnalyser */
155+
$fileAnalyser = $container->getByType(FileAnalyser::class);
156+
157+
/** @var Registry $registry */
158+
$registry = $container->getByType(Registry::class);
159+
160+
// todo collectErrors (from Analyser)
161+
$in->on('data', static function (array $json) use ($fileAnalyser, $registry, $out, $analysedFiles): void {
162+
$action = $json['action'];
163+
if ($action !== 'analyse') {
164+
return;
165+
}
166+
167+
$internalErrorsCount = 0;
168+
$files = $json['files'];
169+
$errors = [];
170+
$dependencies = [];
171+
foreach ($files as $file) {
172+
try {
173+
$fileAnalyserResult = $fileAnalyser->analyseFile($file, $analysedFiles, $registry, null);
174+
$fileErrors = $fileAnalyserResult->getErrors();
175+
$dependencies[$file] = $fileAnalyserResult->getDependencies();
176+
foreach ($fileErrors as $fileError) {
177+
$errors[] = $fileError;
178+
}
179+
} catch (\Throwable $t) {
180+
$internalErrorsCount++;
181+
$internalErrorMessage = sprintf('Internal error: %s in file %s', $t->getMessage(), $file);
182+
$internalErrorMessage .= sprintf(
183+
'%sRun PHPStan with --debug option and post the stack trace to:%s%s',
184+
"\n",
185+
"\n",
186+
'https://github.com/phpstan/phpstan/issues/new'
187+
);
188+
$errors[] = $internalErrorMessage;
189+
}
190+
}
191+
192+
$out->write([
193+
'action' => 'result',
194+
'result' => [
195+
'errors' => $errors,
196+
'dependencies' => $dependencies,
197+
'filesCount' => count($files),
198+
'internalErrorsCount' => $internalErrorsCount,
199+
]]);
200+
});
201+
$in->on('error', $handleError);
202+
}
203+
204+
public function haha(int $i): void
205+
{
206+
function () use ($i) {
207+
208+
};
209+
}
210+
211+
/**
212+
* @param int $b
213+
*/
214+
public function doLorem(int $a): void
215+
{
216+
echo 'haha';
217+
return;
218+
219+
echo 'blabla';
220+
if (false) {
221+
222+
}
223+
}
224+
225+
}

0 commit comments

Comments
 (0)