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

EndToEndTest::createTestDirectory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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