Passed
Pull Request — master (#103)
by Marco
05:16 queued 02:38
created

validVersionsCollections()   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
nc 1
nop 0
dl 0
loc 15
rs 9.9666
c 0
b 0
f 0
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\VersionsCollection;
30
use function chdir;
31
use function 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
        self::assertInternalType('string', $repositoryPath);
81
82
        $this->sourceRepository = CheckedOutRepository::fromPath($repositoryPath);
83
84
        chdir($this->sourceRepository->__toString());
85
86
        $this->input              = $this->createMock(InputInterface::class);
87
        $this->output             = $this->createMock(ConsoleOutputInterface::class);
88
        $this->stdErr             = $this->createMock(OutputInterface::class);
89
        $this->performCheckout    = $this->createMock(PerformCheckoutOfRevision::class);
90
        $this->parseRevision      = $this->createMock(ParseRevision::class);
91
        $this->getVersions        = $this->createMock(GetVersionCollection::class);
92
        $this->pickVersion        = $this->createMock(PickVersionFromVersionCollection::class);
93
        $this->locateDependencies = $this->createMock(LocateDependencies::class);
94
        $this->dependencies       = new AggregateSourceLocator();
95
        $this->compareApi         = $this->createMock(CompareApi::class);
96
        $this->compare            = new AssertBackwardsCompatible(
97
            $this->performCheckout,
98
            new ComposerInstallationReflectorFactory(new LocateSourcesViaComposerJson((new BetterReflection())->astLocator())),
99
            $this->parseRevision,
100
            $this->getVersions,
101
            $this->pickVersion,
102
            $this->locateDependencies,
103
            $this->compareApi
104
        );
105
106
        $this
107
            ->output
108
            ->expects(self::any())
109
            ->method('getErrorOutput')
110
            ->willReturn($this->stdErr);
111
    }
112
113
    public function testExecuteWhenRevisionsAreProvidedAsOptions() : void
114
    {
115
        $fromSha = sha1('fromRevision', false);
116
        $toSha   = sha1('toRevision', false);
117
118
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Symfony\Component\Console\Input\InputInterface. ( Ignorable by Annotation )

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

118
        $this->input->/** @scrutinizer ignore-call */ 
119
                      expects(self::any())->method('getOption')->willReturnMap([

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...
119
            ['from', $fromSha],
120
            ['to', $toSha],
121
        ]);
122
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
123
            ['sources-path', 'src'],
124
        ]);
125
126
        $this->performCheckout->expects(self::at(0))
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Roave\BackwardCompatibil...rformCheckoutOfRevision. ( Ignorable by Annotation )

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

126
        $this->performCheckout->/** @scrutinizer ignore-call */ 
127
                                expects(self::at(0))

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...
127
            ->method('checkout')
128
            ->with($this->sourceRepository, $fromSha)
129
            ->willReturn($this->sourceRepository);
130
        $this->performCheckout->expects(self::at(1))
131
            ->method('checkout')
132
            ->with($this->sourceRepository, $toSha)
133
            ->willReturn($this->sourceRepository);
134
        $this->performCheckout->expects(self::at(2))
135
            ->method('remove')
136
            ->with($this->sourceRepository);
137
        $this->performCheckout->expects(self::at(3))
138
            ->method('remove')
139
            ->with($this->sourceRepository);
140
141
        $this->parseRevision->expects(self::at(0))
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Roave\BackwardCompatibility\Git\ParseRevision. ( Ignorable by Annotation )

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

141
        $this->parseRevision->/** @scrutinizer ignore-call */ 
142
                              expects(self::at(0))

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...
142
            ->method('fromStringForRepository')
143
            ->with($fromSha)
144
            ->willReturn(Revision::fromSha1($fromSha));
145
        $this->parseRevision->expects(self::at(1))
146
            ->method('fromStringForRepository')
147
            ->with($toSha)
148
            ->willReturn(Revision::fromSha1($toSha));
149
150
        $this
151
            ->locateDependencies
152
            ->expects(self::any())
0 ignored issues
show
Bug introduced by
The method expects() 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

152
            ->/** @scrutinizer ignore-call */ 
153
              expects(self::any())

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...
153
            ->method('__invoke')
154
            ->with((string) $this->sourceRepository)
155
            ->willReturn($this->dependencies);
156
157
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::empty());
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Roave\BackwardCompatibility\CompareApi. ( Ignorable by Annotation )

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

157
        $this->compareApi->/** @scrutinizer ignore-call */ 
158
                           expects(self::once())->method('__invoke')->willReturn(Changes::empty());

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...
158
159
        self::assertSame(0, $this->compare->execute($this->input, $this->output));
160
    }
161
162
    public function testExecuteReturnsNonZeroExitCodeWhenChangesAreDetected() : void
163
    {
164
        $fromSha = sha1('fromRevision', false);
165
        $toSha   = sha1('toRevision', false);
166
167
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
168
            ['from', $fromSha],
169
            ['to', $toSha],
170
        ]);
171
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
172
            ['sources-path', 'src'],
173
        ]);
174
175
        $this->performCheckout->expects(self::at(0))
176
            ->method('checkout')
177
            ->with($this->sourceRepository, $fromSha)
178
            ->willReturn($this->sourceRepository);
179
        $this->performCheckout->expects(self::at(1))
180
            ->method('checkout')
181
            ->with($this->sourceRepository, $toSha)
182
            ->willReturn($this->sourceRepository);
183
        $this->performCheckout->expects(self::at(2))
184
            ->method('remove')
185
            ->with($this->sourceRepository);
186
        $this->performCheckout->expects(self::at(3))
187
            ->method('remove')
188
            ->with($this->sourceRepository);
189
190
        $this->parseRevision->expects(self::at(0))
191
            ->method('fromStringForRepository')
192
            ->with($fromSha)
193
            ->willReturn(Revision::fromSha1($fromSha));
194
        $this->parseRevision->expects(self::at(1))
195
            ->method('fromStringForRepository')
196
            ->with($toSha)
197
            ->willReturn(Revision::fromSha1($toSha));
198
199
        $this
200
            ->locateDependencies
201
            ->expects(self::any())
202
            ->method('__invoke')
203
            ->with((string) $this->sourceRepository)
204
            ->willReturn($this->dependencies);
205
206
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::fromList(
207
            Change::added(uniqid('added', true), true)
208
        ));
209
210
        $this
211
            ->stdErr
212
            ->expects(self::exactly(3))
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Symfony\Component\Console\Output\OutputInterface. ( Ignorable by Annotation )

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

212
            ->/** @scrutinizer ignore-call */ 
213
              expects(self::exactly(3))

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...
213
            ->method('writeln')
214
            ->with(self::logicalOr(
215
                self::matches('Comparing from %a to %a...'),
216
                self::matches('[BC] ADDED: added%a'),
217
                self::matches('<error>1 backwards-incompatible changes detected</error>')
218
            ));
219
220
        self::assertSame(3, $this->compare->execute($this->input, $this->output));
221
    }
222
223
    public function testProvidingMarkdownOptionWritesMarkdownOutput() : void
224
    {
225
        $fromSha = sha1('fromRevision', false);
226
        $toSha   = sha1('toRevision', false);
227
228
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
229
            ['from', $fromSha],
230
            ['to', $toSha],
231
            ['format', ['markdown']],
232
        ]);
233
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
234
            ['sources-path', 'src'],
235
        ]);
236
237
        $this->performCheckout->expects(self::at(0))
238
            ->method('checkout')
239
            ->with($this->sourceRepository, $fromSha)
240
            ->willReturn($this->sourceRepository);
241
        $this->performCheckout->expects(self::at(1))
242
            ->method('checkout')
243
            ->with($this->sourceRepository, $toSha)
244
            ->willReturn($this->sourceRepository);
245
        $this->performCheckout->expects(self::at(2))
246
            ->method('remove')
247
            ->with($this->sourceRepository);
248
        $this->performCheckout->expects(self::at(3))
249
            ->method('remove')
250
            ->with($this->sourceRepository);
251
252
        $this->parseRevision->expects(self::at(0))
253
            ->method('fromStringForRepository')
254
            ->with($fromSha)
255
            ->willReturn(Revision::fromSha1($fromSha));
256
        $this->parseRevision->expects(self::at(1))
257
            ->method('fromStringForRepository')
258
            ->with($toSha)
259
            ->willReturn(Revision::fromSha1($toSha));
260
261
        $this
262
            ->locateDependencies
263
            ->expects(self::any())
264
            ->method('__invoke')
265
            ->with((string) $this->sourceRepository)
266
            ->willReturn($this->dependencies);
267
268
        $changeToExpect = uniqid('changeToExpect', true);
269
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::fromList(
270
            Change::removed($changeToExpect, true)
271
        ));
272
273
        $this->compare->execute($this->input, $this->output);
274
275
        $this->output->expects(self::any())
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Symfony\Component\Consol...\ConsoleOutputInterface. ( Ignorable by Annotation )

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

275
        $this->output->/** @scrutinizer ignore-call */ 
276
                       expects(self::any())

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...
276
            ->method('writeln')
277
            ->willReturnCallback(function (string $output) use ($changeToExpect) : void {
278
                self::assertContains($changeToExpect, $output);
279
            });
280
    }
281
282
    public function testExecuteWithDefaultRevisionsNotProvidedAndNoDetectedTags() : void
283
    {
284
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
285
            ['from', null],
286
            ['to', 'HEAD'],
287
        ]);
288
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
289
            ['sources-path', 'src'],
290
        ]);
291
292
        $this
293
            ->performCheckout
294
            ->expects(self::never())
295
            ->method('checkout');
296
        $this
297
            ->parseRevision
298
            ->expects(self::never())
299
            ->method('fromStringForRepository');
300
301
        $this
302
            ->getVersions
303
            ->expects(self::once())
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Roave\BackwardCompatibil...it\GetVersionCollection. ( Ignorable by Annotation )

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

303
            ->/** @scrutinizer ignore-call */ 
304
              expects(self::once())

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...
304
            ->method('fromRepository')
305
            ->willReturn(new VersionsCollection());
306
        $this
307
            ->pickVersion
308
            ->expects(self::never())
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Roave\BackwardCompatibil...onFromVersionCollection. ( Ignorable by Annotation )

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

308
            ->/** @scrutinizer ignore-call */ 
309
              expects(self::never())

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...
309
            ->method('forVersions');
310
        $this
311
            ->compareApi
312
            ->expects(self::never())
313
            ->method('__invoke');
314
315
        $this->expectException(AssertionFailedException::class);
316
317
        $this->compare->execute($this->input, $this->output);
318
    }
319
320
    /** @dataProvider validVersionsCollections */
321
    public function testExecuteWithDefaultRevisionsNotProvided(VersionsCollection $versions) : void
322
    {
323
        $fromSha       = sha1('fromRevision', false);
324
        $toSha         = sha1('toRevision', false);
325
        $pickedVersion = Version::fromString('1.0.0');
326
327
        $this->input->expects(self::any())->method('getOption')->willReturnMap([
328
            ['from', null],
329
            ['to', 'HEAD'],
330
        ]);
331
        $this->input->expects(self::any())->method('getArgument')->willReturnMap([
332
            ['sources-path', 'src'],
333
        ]);
334
335
        $this->performCheckout->expects(self::at(0))
336
            ->method('checkout')
337
            ->with($this->sourceRepository, $fromSha)
338
            ->willReturn($this->sourceRepository);
339
        $this->performCheckout->expects(self::at(1))
340
            ->method('checkout')
341
            ->with($this->sourceRepository, $toSha)
342
            ->willReturn($this->sourceRepository);
343
        $this->performCheckout->expects(self::at(2))
344
            ->method('remove')
345
            ->with($this->sourceRepository);
346
        $this->performCheckout->expects(self::at(3))
347
            ->method('remove')
348
            ->with($this->sourceRepository);
349
350
        $this->parseRevision->expects(self::at(0))
351
            ->method('fromStringForRepository')
352
            ->with((string) $pickedVersion)
353
            ->willReturn(Revision::fromSha1($fromSha));
354
        $this->parseRevision->expects(self::at(1))
355
            ->method('fromStringForRepository')
356
            ->with('HEAD')
357
            ->willReturn(Revision::fromSha1($toSha));
358
359
        $this->getVersions->expects(self::once())
360
            ->method('fromRepository')
361
            ->with(self::callback(function (CheckedOutRepository $checkedOutRepository) : bool {
362
                self::assertEquals($this->sourceRepository, $checkedOutRepository);
363
                return true;
364
            }))
365
            ->willReturn($versions);
366
        $this->pickVersion->expects(self::once())
367
            ->method('forVersions')
368
            ->with($versions)
369
            ->willReturn($pickedVersion);
370
371
        $this
372
            ->stdErr
373
            ->expects(self::exactly(3))
374
            ->method('writeln')
375
            ->with(self::logicalOr(
376
                'Detected last minor version: 1.0.0',
377
                self::matches('Comparing from %a to %a...'),
378
                self::matches('<info>No backwards-incompatible changes detected</info>')
379
            ));
380
381
        $this->compareApi->expects(self::once())->method('__invoke')->willReturn(Changes::empty());
382
383
        self::assertSame(0, $this->compare->execute($this->input, $this->output));
384
    }
385
386
    /** @return VersionsCollection[][] */
387
    public function validVersionsCollections() : array
388
    {
389
        return [
390
            [new VersionsCollection(
391
                Version::fromString('1.0.0'),
392
                Version::fromString('1.0.1'),
393
                Version::fromString('1.0.2')
394
            ),
395
            ],
396
            [new VersionsCollection(
397
                Version::fromString('1.0.0'),
398
                Version::fromString('1.0.1')
399
            ),
400
            ],
401
            [new VersionsCollection(Version::fromString('1.0.0'))],
402
        ];
403
    }
404
}
405