Passed
Pull Request — master (#75)
by Dave
02:17
created

EndToEndTest::runCommand()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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