Passed
Push — master ( de2f1c...b3c10d )
by Dave
02:37 queued 14s
created

testRelativePathFlagHasNoAffectWhenUsingAbsolutePaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 0
dl 0
loc 27
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DaveLiddament\StaticAnalysisResultsBaseliner\Tests\Integration;
6
7
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\ProjectRoot;
8
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\RelativeFileName;
9
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\Path;
10
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\StringUtils;
11
use DaveLiddament\StaticAnalysisResultsBaseliner\Framework\Command\CreateBaseLineCommand;
12
use DaveLiddament\StaticAnalysisResultsBaseliner\Framework\Command\ListHistoryAnalysersCommand;
13
use DaveLiddament\StaticAnalysisResultsBaseliner\Framework\Command\ListResultsParsesCommand;
14
use DaveLiddament\StaticAnalysisResultsBaseliner\Framework\Command\RemoveBaseLineFromResultsCommand;
15
use DaveLiddament\StaticAnalysisResultsBaseliner\Framework\Container\Container;
16
use DaveLiddament\StaticAnalysisResultsBaseliner\Plugins\GitDiffHistoryAnalyser\internal\GitCliWrapper;
17
use DaveLiddament\StaticAnalysisResultsBaseliner\Tests\Helpers\ResourceLoaderTrait;
18
use PHPUnit\Framework\TestCase;
19
use Symfony\Component\Console\Application;
20
use Symfony\Component\Console\Tester\CommandTester;
21
use Symfony\Component\Filesystem\Filesystem;
22
23
// TODO this is getting a bit big. Split into multiple files.
24
final class EndToEndTest extends TestCase
25
{
26
    use ResourceLoaderTrait;
27
    use TestDirectoryTrait;
28
29
    private const COMMIT_1_DIRECTORY = 'integration/commit1';
30
    private const COMMIT_1_RESULTS = 'commit1.json';
31
32
    private const COMMIT_2_DIRECTORY = 'integration/commit2';
33
    private const COMMIT_2_RESULTS = 'commit2.json';
34
    private const COMMIT_2_BASELINE_REMOVED_EXPECTED_RESULTS = 'baseline-removed.json';
35
    private const COMMIT_2_BASELINE_REMOVED_NO_WARNINGS_EXPECTED_RESULTS = 'baseline-removed-no-warnings.json';
36
37
    private const COMMIT_3_DIRECTORY = 'integration/commit3';
38
    private const COMMIT_3_RESULTS = 'commit3.json';
39
40
    private const INVALID_RESULTS = 'invalid_analysis_results.json';
41
42
    /**
43
     * @var Filesystem
44
     */
45
    private $fileSystem;
46
47
    /**
48
     * @var GitCliWrapper
49
     */
50
    private $gitWrapper;
51
52
    /**
53
     * @var ProjectRoot
54
     */
55
    private $projectRoot;
56
57
    /**
58
     * @var Application
59
     */
60
    private $application;
61
62
    protected function setUp(): void
63
    {
64
        $this->fileSystem = new Filesystem();
65
        $this->gitWrapper = new GitCliWrapper();
66
        $container = new Container();
67
        $this->application = $container->getApplication();
68
    }
69
70
    public function testInvalidConfig(): void
71
    {
72
        $this->createTestDirectory();
73
74
        $arguments = [
75
            '--input-format' => 'rubbish',
76
            'baseline-file' => $this->getBaselineFilePath(),
77
        ];
78
79
        $this->runCommand(
80
            CreateBaseLineCommand::COMMAND_NAME,
81
            $arguments,
82
            11,
83
            self::COMMIT_1_RESULTS,
84
        );
85
86
        // Only delete test directory if tests passed. Keep to investigate test failures
87
        $this->removeTestDirectory();
88
    }
89
90
    public function testInvalidAnalysisResults(): void
91
    {
92
        $this->createTestDirectory();
93
        $this->gitWrapper->init($this->projectRoot);
94
        $this->commit(self::COMMIT_1_DIRECTORY);
95
96
        $arguments = [
97
            'baseline-file' => $this->getBaselineFilePath(),
98
            '--project-root' => (string) $this->projectRoot,
99
        ];
100
101
        $this->runCommand(
102
            CreateBaseLineCommand::COMMAND_NAME,
103
            $arguments,
104
            13,
105
            self::INVALID_RESULTS,
106
        );
107
108
        // Only delete test directory if tests passed. Keep to investigate test failures
109
        $this->removeTestDirectory();
110
    }
111
112
    public function testInvalidProjectRoot(): void
113
    {
114
        $this->createTestDirectory();
115
        $this->gitWrapper->init($this->projectRoot);
116
        $this->commit(self::COMMIT_1_DIRECTORY);
117
118
        $arguments = [
119
            'baseline-file' => $this->getProjectRootFilename('InvalidFileName.json'),
120
            '--project-root' => '/tmp/foo/bar',
121
        ];
122
123
        $this->runCommand(
124
            CreateBaseLineCommand::COMMAND_NAME,
125
            $arguments,
126
            15,
127
            self::COMMIT_1_RESULTS,
128
        );
129
130
        // Only delete test directory if tests passed. Keep to investigate test failures
131
        $this->removeTestDirectory();
132
    }
133
134
    public function testInvalidBaselineFileNameSupplied(): void
135
    {
136
        $this->createTestDirectory();
137
        $arguments = [
138
            'baseline-file' => $this->getProjectRootFilename('InvalidFileName.json'),
139
        ];
140
141
        $this->runCommand(
142
            RemoveBaseLineFromResultsCommand::COMMAND_NAME,
143
            $arguments,
144
            14,
145
            self::COMMIT_1_RESULTS);
146
147
        // Only delete test directory if tests passed. Keep to investigate test failures
148
        $this->removeTestDirectory();
149
    }
150
151
    public function testInvalidBaselineContents(): void
152
    {
153
        $this->createTestDirectory();
154
        $this->gitWrapper->init($this->projectRoot);
155
        $this->commit(self::COMMIT_1_DIRECTORY);
156
        $arguments = [
157
            'baseline-file' => $this->getProjectRootFilename('src/Person.php'),
158
        ];
159
160
        $this->runCommand(
161
            RemoveBaseLineFromResultsCommand::COMMAND_NAME,
162
            $arguments,
163
            12,
164
            self::COMMIT_1_RESULTS);
165
166
        // Only delete test directory if tests passed. Keep to investigate test failures
167
        $this->removeTestDirectory();
168
    }
169
170
    public function testHappyPath(): void
171
    {
172
        $this->createTestDirectory();
173
        $this->gitWrapper->init($this->projectRoot);
174
175
        $this->commit(self::COMMIT_1_DIRECTORY);
176
        $this->runCreateBaseLineCommand();
177
178
        // Now create commit 2. THis introduces some new errors
179
        $this->commit(self::COMMIT_2_DIRECTORY);
180
        $this->runStripBaseLineFromResultsCommand(
181
            self::COMMIT_2_RESULTS,
182
            1,
183
            $this->getStaticAnalysisResultsAsString(self::COMMIT_2_BASELINE_REMOVED_EXPECTED_RESULTS),
184
        );
185
186
        // Check remove warnings works as expected
187
        $this->runStripBaseLineFromResultsCommand(
188
            self::COMMIT_2_RESULTS,
189
            1,
190
            $this->getStaticAnalysisResultsAsString(self::COMMIT_2_BASELINE_REMOVED_NO_WARNINGS_EXPECTED_RESULTS),
191
            null,
192
            true,
193
        );
194
195
        // Now create commit 3. This has errors that were only in the baseline.
196
        $this->commit(self::COMMIT_3_DIRECTORY);
197
        $this->runStripBaseLineFromResultsCommand(
198
            self::COMMIT_3_RESULTS,
199
            0,
200
            '',
201
        );
202
203
        // Only delete test directory if tests passed. Keep to investigate test failures
204
        $this->removeTestDirectory();
205
    }
206
207
    public function testRelativePathFlagHasNoAffectWhenUsingAbsolutePaths(): void
208
    {
209
        $this->createTestDirectory();
210
        $this->gitWrapper->init($this->projectRoot);
211
212
        $this->commit(self::COMMIT_1_DIRECTORY);
213
        $this->runCreateBaseLineCommand('code');
214
215
        // Now create commit 2. THis introduces some new errors
216
        $this->commit(self::COMMIT_2_DIRECTORY);
217
        $this->runStripBaseLineFromResultsCommand(
218
            self::COMMIT_2_RESULTS,
219
            1,
220
            $this->getStaticAnalysisResultsAsString(self::COMMIT_2_BASELINE_REMOVED_EXPECTED_RESULTS),
221
            'code',
222
        );
223
224
        // Now create commit 3. This has errors that were only in the baseline.
225
        $this->commit(self::COMMIT_3_DIRECTORY);
226
        $this->runStripBaseLineFromResultsCommand(
227
            self::COMMIT_3_RESULTS,
228
            0,
229
            '',
230
        );
231
232
        // Only delete test directory if tests passed. Keep to investigate test failures
233
        $this->removeTestDirectory();
234
    }
235
236
    public function testAttemptToCreateBaselineWithNonCleanGitStatus(): void
237
    {
238
        $this->createTestDirectory();
239
        $this->gitWrapper->init($this->projectRoot);
240
        $this->commit(self::COMMIT_1_DIRECTORY);
241
        $this->addNonCheckedInFile();
242
243
        $arguments = [
244
            'baseline-file' => $this->getProjectRootFilename('baseline.json'),
245
            '--project-root' => (string) $this->projectRoot,
246
        ];
247
248
        $this->runCommand(
249
            CreateBaseLineCommand::COMMAND_NAME,
250
            $arguments,
251
            15,
252
            self::COMMIT_1_RESULTS,
253
        );
254
255
        $this->removeTestDirectory();
256
    }
257
258
    public function testForceCreateBaselineWithNonCleanGitStatus(): void
259
    {
260
        $this->createTestDirectory();
261
        $this->gitWrapper->init($this->projectRoot);
262
        $this->commit(self::COMMIT_1_DIRECTORY);
263
        $this->addNonCheckedInFile();
264
265
        $arguments = [
266
            'baseline-file' => $this->getProjectRootFilename('baseline.json'),
267
            '--project-root' => (string) $this->projectRoot,
268
            '--force' => null,
269
        ];
270
271
        $this->runCommand(
272
            CreateBaseLineCommand::COMMAND_NAME,
273
            $arguments,
274
            0,
275
            self::COMMIT_1_RESULTS,
276
        );
277
278
        // Only delete test directory if tests passed. Keep to investigate test failures
279
        $this->removeTestDirectory();
280
    }
281
282
    /**
283
     * This is just a smoke test.
284
     */
285
    public function testListSupportedStaticAnalysisTools(): void
286
    {
287
        $this->runCommand(ListResultsParsesCommand::COMMAND_NAME, [], 0, null);
288
    }
289
290
    /**
291
     * This is just a smoke test.
292
     */
293
    public function testListSupportedHistoryAnalysers(): void
294
    {
295
        $this->runCommand(ListHistoryAnalysersCommand::COMMAND_NAME, [], 0, null);
296
    }
297
298
    private function commit(string $directory): void
299
    {
300
        $source = $this->getPath($directory);
301
        $this->fileSystem->mirror($source, (string) $this->projectRoot, null, ['override' => true]);
302
        $this->updatePathsInJsonFiles($this->projectRoot);
303
        $this->gitWrapper->addAndCommit("Updating code to $directory", $this->projectRoot);
304
    }
305
306
    private function runCreateBaseLineCommand(?string $relativePathToCode = null): void
307
    {
308
        $arguments = [
309
            'baseline-file' => $this->getBaselineFilePath(),
310
            '--project-root' => (string) $this->projectRoot,
311
        ];
312
313
        if (null !== $relativePathToCode) {
314
            $arguments['--relative-path-to-code'] = $relativePathToCode;
315
        }
316
317
        $this->runCommand(
318
            CreateBaseLineCommand::COMMAND_NAME,
319
            $arguments,
320
            0,
321
            self::COMMIT_1_RESULTS,
322
        );
323
    }
324
325
    private function runStripBaseLineFromResultsCommand(
326
        string $psalmResults,
327
        int $expectedExitCode,
328
        string $expectedResultsJson,
329
        ?string $relativePathToCode = null,
330
        bool $ignoreWarnings = false,
331
    ): void {
332
        $arguments = [
333
            'baseline-file' => $this->getBaselineFilePath(),
334
            '--output-format' => 'json',
335
            '--project-root' => (string) $this->projectRoot,
336
        ];
337
338
        if (null !== $relativePathToCode) {
339
            $arguments['--relative-path-to-code'] = $relativePathToCode;
340
        }
341
342
        if ($ignoreWarnings) {
343
            $arguments['--ignore-warnings'] = null;
344
        }
345
346
        $output = $this->runCommand(
347
            RemoveBaseLineFromResultsCommand::COMMAND_NAME,
348
            $arguments,
349
            $expectedExitCode,
350
            $psalmResults,
351
        );
352
353
        $output = str_replace('\/', '/', $output);
354
355
        $this->assertStringContainsString($expectedResultsJson, $output);
356
    }
357
358
    /**
359
     * @param array<string, string|null> $arguments
360
     */
361
    private function runCommand(
362
        string $commandName,
363
        array $arguments,
364
        int $expectedExitCode,
365
        ?string $resourceContainStdinContents,
366
    ): string {
367
        $command = $this->application->find($commandName);
368
        $commandTester = new CommandTester($command);
369
        $arguments['command'] = $command->getName();
370
371
        if (null !== $resourceContainStdinContents) {
372
            $stdin = $this->getStaticAnalysisResultsAsString($resourceContainStdinContents);
373
            $commandTester->setInputs([$stdin]);
374
        }
375
376
        $actualExitCode = $commandTester->execute($arguments);
377
        $output = $commandTester->getDisplay();
378
        $this->assertEquals($expectedExitCode, $actualExitCode, $output);
379
380
        return $output;
381
    }
382
383
    private function getBaselineFilePath(): string
384
    {
385
        return "{$this->projectRoot}/baseline.json";
386
    }
387
388
    private function getStaticAnalysisResultsAsString(string $resourceName): string
389
    {
390
        $fileName = __DIR__.'/../resources/integration/staticAnalysisOutput/'.$resourceName;
391
        $rawResults = file_get_contents($fileName);
392
        $this->assertNotFalse($rawResults);
393
        $projectRootDirectory = (string) $this->projectRoot;
394
        $resultsWithPathsCorrected = str_replace('__SCRATCH_PAD_PATH__', $projectRootDirectory, $rawResults);
395
396
        return $resultsWithPathsCorrected;
397
    }
398
399
    private function getProjectRootFilename(string $resourceName): string
400
    {
401
        return $this->projectRoot->getAbsoluteFileName(new RelativeFileName($resourceName))->getFileName();
402
    }
403
404
    private function updatePathsInJsonFiles(ProjectRoot $projectRoot): void
405
    {
406
        $directory = $projectRoot->getProjectRootDirectory();
407
408
        $files = scandir($directory);
409
        $this->assertNotFalse($files);
410
        foreach ($files as $file) {
411
            if (StringUtils::endsWith('.json', $file)) {
412
                $fullPath = Path::makeAbsolute($file, $directory);
413
                $contents = file_get_contents($fullPath);
414
                $this->assertNotFalse($contents);
415
                $newContents = str_replace('__SCRATCH_PAD_PATH__', $directory, $contents);
416
                file_put_contents($fullPath, $newContents);
417
            }
418
        }
419
    }
420
421
    private function addNonCheckedInFile(): void
422
    {
423
        // Add a new file that is not checked in
424
        $newFile = new RelativeFileName('new.php');
425
        $this->fileSystem->dumpFile($this->projectRoot->getAbsoluteFileName($newFile)->getFileName(), 'new');
426
    }
427
}
428