Completed
Push — master ( aa7bb6...b565b9 )
by Marco
13:23 queued 04:48
created

validVersionCollections()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 15
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace RoaveTest\BackwardCompatibility\Command;
6
7
use Assert\AssertionFailedException;
8
use PHPUnit\Framework\MockObject\MockObject;
9
use PHPUnit\Framework\TestCase;
10
use Roave\BackwardCompatibility\Change;
11
use Roave\BackwardCompatibility\Changes;
12
use Roave\BackwardCompatibility\Command\AssertBackwardsCompatible;
13
use Roave\BackwardCompatibility\CompareApi;
14
use Roave\BackwardCompatibility\Factory\ComposerInstallationReflectorFactory;
15
use Roave\BackwardCompatibility\Git\CheckedOutRepository;
16
use Roave\BackwardCompatibility\Git\GetVersionCollection;
17
use Roave\BackwardCompatibility\Git\ParseRevision;
18
use Roave\BackwardCompatibility\Git\PerformCheckoutOfRevision;
19
use Roave\BackwardCompatibility\Git\PickVersionFromVersionCollection;
20
use Roave\BackwardCompatibility\Git\Revision;
21
use Roave\BackwardCompatibility\LocateDependencies\LocateDependencies;
22
use Roave\BackwardCompatibility\LocateSources\LocateSourcesViaComposerJson;
23
use Roave\BetterReflection\BetterReflection;
24
use Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Output\ConsoleOutputInterface;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Version\Version;
29
use Version\VersionCollection;
30
use function Safe\chdir;
31
use function Safe\realpath;
32
use function sha1;
33
use function uniqid;
34
35
/**
36
 * @covers \Roave\BackwardCompatibility\Command\AssertBackwardsCompatible
37
 */
38
final class AssertBackwardsCompatibleTest extends TestCase
39
{
40
    /** @var CheckedOutRepository */
41
    private $sourceRepository;
42
43
    /** @var InputInterface&MockObject */
44
    private $input;
45
46
    /** @var ConsoleOutputInterface&MockObject */
47
    private $output;
48
49
    /** @var OutputInterface&MockObject */
50
    private $stdErr;
51
52
    /** @var PerformCheckoutOfRevision&MockObject */
53
    private $performCheckout;
54
55
    /** @var ParseRevision&MockObject */
56
    private $parseRevision;
57
58
    /** @var GetVersionCollection&MockObject */
59
    private $getVersions;
60
61
    /** @var PickVersionFromVersionCollection&MockObject */
62
    private $pickVersion;
63
64
    /** @var LocateDependencies&MockObject */
65
    private $locateDependencies;
66
67
    /** @var CompareApi&MockObject */
68
    private $compareApi;
69
70
    /** @var AggregateSourceLocator */
71
    private $dependencies;
72
73
    /** @var AssertBackwardsCompatible */
74
    private $compare;
75
76
    public function setUp() : void
77
    {
78
        $repositoryPath = realpath(__DIR__ . '/../../../');
79
80
        $this->sourceRepository = CheckedOutRepository::fromPath($repositoryPath);
81
82
        chdir($this->sourceRepository->__toString());
83
84
        $this->input              = $this->createMock(InputInterface::class);
85
        $this->output             = $this->createMock(ConsoleOutputInterface::class);
86
        $this->stdErr             = $this->createMock(OutputInterface::class);
87
        $this->performCheckout    = $this->createMock(PerformCheckoutOfRevision::class);
88
        $this->parseRevision      = $this->createMock(ParseRevision::class);
89
        $this->getVersions        = $this->createMock(GetVersionCollection::class);
90
        $this->pickVersion        = $this->createMock(PickVersionFromVersionCollection::class);
91
        $this->locateDependencies = $this->createMock(LocateDependencies::class);
92
        $this->dependencies       = new AggregateSourceLocator();
93
        $this->compareApi         = $this->createMock(CompareApi::class);
94
        $this->compare            = new AssertBackwardsCompatible(
95
            $this->performCheckout,
96
            new ComposerInstallationReflectorFactory(new LocateSourcesViaComposerJson((new BetterReflection())->astLocator())),
97
            $this->parseRevision,
98
            $this->getVersions,
99
            $this->pickVersion,
100
            $this->locateDependencies,
101
            $this->compareApi
102
        );
103
104
        $this
105
            ->output
106
            ->expects(self::any())
107
            ->method('getErrorOutput')
108
            ->willReturn($this->stdErr);
109
    }
110
111
    public function testDefinition() : void
112
    {
113
        self::assertSame(
114
            'roave-backwards-compatibility-check:assert-backwards-compatible',
115
            $this->compare->getName()
116
        );
117
118
        $usages = $this->compare->getUsages();
119
120
        self::assertCount(1, $usages);
121
        self::assertStringStartsWith(
122
            'roave-backwards-compatibility-check:assert-backwards-compatible',
123
            $usages[0]
124
        );
125
        self::assertStringContainsString('Without arguments, this command will attempt to detect', $usages[0]);
126
        self::assertStringStartsWith('Verifies that the revision being', $this->compare->getDescription());
127
128
        self::assertSame(
129
            '[--from [FROM]] [--to TO] [--format [FORMAT]]',
130
            $this->compare
131
                ->getDefinition()
132
                ->getSynopsis()
133
        );
134
    }
135
136
    public function testExecuteWhenRevisionsAreProvidedAsOptions() : void
137
    {
138
        $fromSha = sha1('fromRevision', false);
139
        $toSha   = sha1('toRevision', false);
140
141
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
142
            ['from', $fromSha],
143
            ['to', $toSha],
144
        ]);
145
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
146
            ['sources-path', 'src'],
147
        ]);
148
149
        $this->performCheckout->expects(self::at(0))
150
            ->method('checkout')
151
            ->with($this->sourceRepository, $fromSha)
152
            ->willReturn($this->sourceRepository);
153
        $this->performCheckout->expects(self::at(1))
154
            ->method('checkout')
155
            ->with($this->sourceRepository, $toSha)
156
            ->willReturn($this->sourceRepository);
157
        $this->performCheckout->expects(self::at(2))
158
            ->method('remove')
159
            ->with($this->sourceRepository);
160
        $this->performCheckout->expects(self::at(3))
161
            ->method('remove')
162
            ->with($this->sourceRepository);
163
164
        $this->parseRevision->expects(self::at(0))
165
            ->method('fromStringForRepository')
166
            ->with($fromSha)
167
            ->willReturn(Revision::fromSha1($fromSha));
168
        $this->parseRevision->expects(self::at(1))
169
            ->method('fromStringForRepository')
170
            ->with($toSha)
171
            ->willReturn(Revision::fromSha1($toSha));
172
173
        $this
174
            ->locateDependencies
175
            ->expects(self::any())
176
            ->method('__invoke')
177
            ->with((string) $this->sourceRepository)
178
            ->willReturn($this->dependencies);
179
180
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::empty());
181
182
        self::assertSame(0, $this->compare->execute($this->input, $this->output));
183
    }
184
185
    public function testExecuteReturnsNonZeroExitCodeWhenChangesAreDetected() : void
186
    {
187
        $fromSha = sha1('fromRevision', false);
188
        $toSha   = sha1('toRevision', false);
189
190
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
191
            ['from', $fromSha],
192
            ['to', $toSha],
193
        ]);
194
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
195
            ['sources-path', 'src'],
196
        ]);
197
198
        $this->performCheckout->expects(self::at(0))
199
            ->method('checkout')
200
            ->with($this->sourceRepository, $fromSha)
201
            ->willReturn($this->sourceRepository);
202
        $this->performCheckout->expects(self::at(1))
203
            ->method('checkout')
204
            ->with($this->sourceRepository, $toSha)
205
            ->willReturn($this->sourceRepository);
206
        $this->performCheckout->expects(self::at(2))
207
            ->method('remove')
208
            ->with($this->sourceRepository);
209
        $this->performCheckout->expects(self::at(3))
210
            ->method('remove')
211
            ->with($this->sourceRepository);
212
213
        $this->parseRevision->expects(self::at(0))
214
            ->method('fromStringForRepository')
215
            ->with($fromSha)
216
            ->willReturn(Revision::fromSha1($fromSha));
217
        $this->parseRevision->expects(self::at(1))
218
            ->method('fromStringForRepository')
219
            ->with($toSha)
220
            ->willReturn(Revision::fromSha1($toSha));
221
222
        $this
223
            ->locateDependencies
224
            ->expects(self::any())
225
            ->method('__invoke')
226
            ->with((string) $this->sourceRepository)
227
            ->willReturn($this->dependencies);
228
229
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::fromList(
230
            Change::added(uniqid('added', true), true)
231
        ));
232
233
        $this
234
            ->stdErr
235
            ->expects(self::exactly(3))
236
            ->method('writeln')
237
            ->with(self::logicalOr(
238
                self::matches('Comparing from %a to %a...'),
239
                self::matches('[BC] ADDED: added%a'),
240
                self::matches('<error>1 backwards-incompatible changes detected</error>')
241
            ));
242
243
        self::assertSame(3, $this->compare->execute($this->input, $this->output));
244
    }
245
246
    public function testProvidingMarkdownOptionWritesMarkdownOutput() : void
247
    {
248
        $fromSha = sha1('fromRevision', false);
249
        $toSha   = sha1('toRevision', false);
250
251
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
252
            ['from', $fromSha],
253
            ['to', $toSha],
254
            ['format', ['markdown']],
255
        ]);
256
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
257
            ['sources-path', 'src'],
258
        ]);
259
260
        $this->performCheckout->expects(self::at(0))
261
            ->method('checkout')
262
            ->with($this->sourceRepository, $fromSha)
263
            ->willReturn($this->sourceRepository);
264
        $this->performCheckout->expects(self::at(1))
265
            ->method('checkout')
266
            ->with($this->sourceRepository, $toSha)
267
            ->willReturn($this->sourceRepository);
268
        $this->performCheckout->expects(self::at(2))
269
            ->method('remove')
270
            ->with($this->sourceRepository);
271
        $this->performCheckout->expects(self::at(3))
272
            ->method('remove')
273
            ->with($this->sourceRepository);
274
275
        $this->parseRevision->expects(self::at(0))
276
            ->method('fromStringForRepository')
277
            ->with($fromSha)
278
            ->willReturn(Revision::fromSha1($fromSha));
279
        $this->parseRevision->expects(self::at(1))
280
            ->method('fromStringForRepository')
281
            ->with($toSha)
282
            ->willReturn(Revision::fromSha1($toSha));
283
284
        $this
285
            ->locateDependencies
286
            ->method('__invoke')
0 ignored issues
show
Bug introduced by
The method method() does not exist on Roave\BackwardCompatibil...cies\LocateDependencies. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

286
            ->/** @scrutinizer ignore-call */ 
287
              method('__invoke')

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
287
            ->with((string) $this->sourceRepository)
288
            ->willReturn($this->dependencies);
289
290
        $changeToExpect = uniqid('changeToExpect', true);
291
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::fromList(
292
            Change::removed($changeToExpect, true)
293
        ));
294
295
        $this->output
296
            ->expects(self::once())
297
            ->method('writeln')
298
            ->willReturnCallback(static function (string $output) use ($changeToExpect) : void {
299
                self::assertStringContainsString(' [BC] ' . $changeToExpect, $output);
300
            });
301
302
        $this->compare->execute($this->input, $this->output);
303
    }
304
305
    public function testExecuteWithDefaultRevisionsNotProvidedAndNoDetectedTags() : void
306
    {
307
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
308
            ['from', null],
309
            ['to', 'HEAD'],
310
        ]);
311
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
312
            ['sources-path', 'src'],
313
        ]);
314
315
        $this
316
            ->performCheckout
317
            ->expects(self::never())
318
            ->method('checkout');
319
        $this
320
            ->parseRevision
321
            ->expects(self::never())
322
            ->method('fromStringForRepository');
323
324
        $this
325
            ->getVersions
326
            ->expects(self::once())
327
            ->method('fromRepository')
328
            ->willReturn(new VersionCollection());
329
        $this
330
            ->pickVersion
331
            ->expects(self::never())
332
            ->method('forVersions');
333
        $this
334
            ->compareApi
335
            ->expects(self::never())
336
            ->method('__invoke');
337
338
        $this->expectException(AssertionFailedException::class);
339
340
        $this->compare->execute($this->input, $this->output);
341
    }
342
343
    /**
344
     * @dataProvider validVersionCollections
345
     */
346
    public function testExecuteWithDefaultRevisionsNotProvided(VersionCollection $versions) : void
347
    {
348
        $fromSha       = sha1('fromRevision', false);
349
        $toSha         = sha1('toRevision', false);
350
        $pickedVersion = Version::fromString('1.0.0');
351
352
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
353
            ['from', null],
354
            ['to', 'HEAD'],
355
        ]);
356
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
357
            ['sources-path', 'src'],
358
        ]);
359
360
        $this->performCheckout->expects(self::at(0))
361
            ->method('checkout')
362
            ->with($this->sourceRepository, $fromSha)
363
            ->willReturn($this->sourceRepository);
364
        $this->performCheckout->expects(self::at(1))
365
            ->method('checkout')
366
            ->with($this->sourceRepository, $toSha)
367
            ->willReturn($this->sourceRepository);
368
        $this->performCheckout->expects(self::at(2))
369
            ->method('remove')
370
            ->with($this->sourceRepository);
371
        $this->performCheckout->expects(self::at(3))
372
            ->method('remove')
373
            ->with($this->sourceRepository);
374
375
        $this->parseRevision->expects(self::at(0))
376
            ->method('fromStringForRepository')
377
            ->with((string) $pickedVersion)
378
            ->willReturn(Revision::fromSha1($fromSha));
379
        $this->parseRevision->expects(self::at(1))
380
            ->method('fromStringForRepository')
381
            ->with('HEAD')
382
            ->willReturn(Revision::fromSha1($toSha));
383
384
        $this->getVersions->expects(self::once())
385
            ->method('fromRepository')
386
            ->with(self::callback(function (CheckedOutRepository $checkedOutRepository) : bool {
387
                self::assertEquals($this->sourceRepository, $checkedOutRepository);
388
389
                return true;
390
            }))
391
            ->willReturn($versions);
392
        $this->pickVersion->expects(self::once())
393
            ->method('forVersions')
394
            ->with($versions)
395
            ->willReturn($pickedVersion);
396
397
        $this
398
            ->stdErr
399
            ->expects(self::exactly(3))
400
            ->method('writeln')
401
            ->with(self::logicalOr(
402
                'Detected last minor version: 1.0.0',
403
                self::matches('Comparing from %a to %a...'),
404
                self::matches('<info>No backwards-incompatible changes detected</info>')
405
            ));
406
407
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::empty());
408
409
        self::assertSame(0, $this->compare->execute($this->input, $this->output));
410
    }
411
412
    /** @return VersionCollection[][] */
413
    public function validVersionCollections() : array
414
    {
415
        return [
416
            [new VersionCollection(
417
                Version::fromString('1.0.0'),
418
                Version::fromString('1.0.1'),
419
                Version::fromString('1.0.2')
420
            ),
421
            ],
422
            [new VersionCollection(
423
                Version::fromString('1.0.0'),
424
                Version::fromString('1.0.1')
425
            ),
426
            ],
427
            [new VersionCollection(Version::fromString('1.0.0'))],
428
        ];
429
    }
430
}
431