Skip to content

Commit

Permalink
Parse and apply baseline from configuration
Browse files Browse the repository at this point in the history
This integrates the changes filter into the backward compatibility check
command, parsing the configuration file prior to any process to ease the
integration of new properties.

Signed-off-by: Luís Cobucci <[email protected]>
  • Loading branch information
lcobucci committed Feb 21, 2023
1 parent 6fca151 commit 0680f22
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 3 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,19 @@ vendor/bin/roave-backward-compatibility-check --help

## Configuration

There are currently no configuration options available.
The file `.roave-backward-compatibility-check.json` is read from the current working directory (when it exists) and sets configuration for the command.

It's expected to be a JSON encoded file that, optionally, contains the following properties:

* `baseline`: list of regexes used to filter detected changes; useful to avoid detection of known BC-breaks

**Example:**

```json
{
"baseline": [
"#\\[BC\\] CHANGED: The parameter \\$a of TestArtifact\\\\TheClass\\#method()#",
"#\\[BC\\] CHANGED: The parameter \\$b of TestArtifact\\\\TheClass\\#method2()#"
]
}
```
31 changes: 29 additions & 2 deletions src/Command/AssertBackwardsCompatible.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Psl;
use Psl\Env;
use Psl\File;
use Psl\Iter;
use Psl\Str;
use Psl\Type;
Expand Down Expand Up @@ -34,6 +35,8 @@

final class AssertBackwardsCompatible extends Command
{
private const CONFIGURATION_FILENAME = '.roave-backward-compatibility-check.json';

/** @throws LogicException */
public function __construct(
private PerformCheckoutOfRevision $git,
Expand Down Expand Up @@ -113,7 +116,9 @@ public function execute(InputInterface $input, OutputInterface $output): int
$stdErr = $output->getErrorOutput();

// @todo fix flaky assumption about the path of the source repo...
$sourceRepo = CheckedOutRepository::fromPath(Env\current_dir());
$currentDirectory = Env\current_dir();

$sourceRepo = CheckedOutRepository::fromPath($currentDirectory);

$fromRevision = $input->getOption('from') !== null
? $this->parseRevisionFromInput($input, $sourceRepo)
Expand All @@ -125,6 +130,8 @@ public function execute(InputInterface $input, OutputInterface $output): int

$toRevision = $this->parseRevision->fromStringForRepository($to, $sourceRepo);

$configuration = $this->determineConfiguration($currentDirectory, $stdErr);

$stdErr->writeln(Str\format(
'Comparing from %s to %s...',
Type\string()->coerce($fromRevision),
Expand All @@ -148,7 +155,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
$toPath->__toString(),
($this->locateDependencies)($toPath->__toString(), $includeDevelopmentDependencies),
),
);
)->applyBaseline($configuration->baseline);

$formatters = [
'console' => new SymfonyConsoleTextFormatter($stdErr),
Expand Down Expand Up @@ -213,4 +220,24 @@ private function determineFromRevisionFromRepository(
$repository,
);
}

private function determineConfiguration(
string $currentDirectory,
OutputInterface $stdErr,
): Configuration {
$fileName = $currentDirectory . '/' . self::CONFIGURATION_FILENAME;

try {
$configContents = File\read($fileName);
} catch (File\Exception\InvalidArgumentException) {
return Configuration::default();
}

$stdErr->writeln(Str\format(
'Using "%s" as configuration file',
Type\string()->coerce($fileName),
));

return Configuration::fromJson($configContents);
}
}
43 changes: 43 additions & 0 deletions src/Command/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Roave\BackwardCompatibility\Command;

use Psl\Json;
use Psl\Type;
use Roave\BackwardCompatibility\Baseline;
use RuntimeException;

final class Configuration
{
private function __construct(public readonly Baseline $baseline)
{
}

public static function default(): self
{
return new self(Baseline::empty());
}

public static function fromJson(string $jsonContents): self
{
try {
$configuration = Json\typed(
$jsonContents,
Type\shape(
['baseline' => Type\optional(Type\vec(Type\string()))],
),
);
} catch (Json\Exception\DecodeException $exception) {
throw new RuntimeException(
'It was not possible to parse the configuration',
previous: $exception,
);
}

$baseline = $configuration['baseline'] ?? [];

return new self(Baseline::fromList(...$baseline));
}
}
22 changes: 22 additions & 0 deletions test/e2e/Command/AssertBackwardsCompatibleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ final class AssertBackwardsCompatibleTest extends TestCase
]
}

JSON;

private const BASELINE_CONFIGURATION = <<<'JSON'
{
"baseline": ["#\\[BC\\] CHANGED: The parameter \\$a of TestArtifact\\\\TheClass\\#method()#"]
}

JSON;

private const CLASS_VERSIONS = [
Expand Down Expand Up @@ -264,6 +271,21 @@ public function testWillPickLatestTaggedVersionOnNoGivenFrom(): void
}
}

public function testWillAllowSpecifyingBaselineConfiguration(): void
{
File\write($this->sourcesRepository . '/.roave-backward-compatibility-check.json', self::BASELINE_CONFIGURATION);

$output = Shell\execute(__DIR__ . '/../../../bin/roave-backward-compatibility-check', [
'--from=' . $this->versions[0],
'--to=' . $this->versions[1],
], $this->sourcesRepository, [], Shell\ErrorOutputBehavior::Append);

self::assertStringContainsString(
'.roave-backward-compatibility-check.json" as configuration file',
$output,
);
}

private function tagOnVersion(string $tagName, int $version): void
{
Shell\execute('git', ['checkout', $this->versions[$version]], $this->sourcesRepository);
Expand Down
82 changes: 82 additions & 0 deletions test/unit/Command/ConfigurationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace RoaveTest\BackwardCompatibility\Command;

use PHPUnit\Framework\TestCase;
use Roave\BackwardCompatibility\Baseline;
use Roave\BackwardCompatibility\Command\Configuration;
use RuntimeException;

/** @covers \Roave\BackwardCompatibility\Command\Configuration */
final class ConfigurationTest extends TestCase
{
public function testBaselineShouldBeEmptyForDefaultConfiguration(): void
{
$config = Configuration::default();

self::assertEquals(Baseline::empty(), $config->baseline);
}

/** @dataProvider validConfigurations */
public function testBaselineShouldBeReadFromJsonContents(
string $jsonContents,
Baseline $expectedBaseline,
): void {
$config = Configuration::fromJson($jsonContents);

self::assertEquals($expectedBaseline, $config->baseline);
}

/** @psalm-return iterable<string, array{string, Baseline}> */
public function validConfigurations(): iterable
{
yield 'empty object' => ['{}', Baseline::empty()];
yield 'empty array' => ['[]', Baseline::empty()];
yield 'empty baseline property' => ['{"baseline":[]}', Baseline::empty()];

yield 'baseline with strings' => [
<<<'JSON'
{"baseline": ["#\\[BC\\] CHANGED: The parameter \\$a#"]}
JSON,
Baseline::fromList('#\[BC\] CHANGED: The parameter \$a#'),
];

yield 'random properties are ignored' => [
<<<'JSON'
{
"baseline": ["#\\[BC\\] CHANGED: The parameter \\$a#"],
"random": false
}
JSON,
Baseline::fromList('#\[BC\] CHANGED: The parameter \$a#'),
];
}

/** @dataProvider invalidConfigurations */
public function testExceptionShouldBeTriggeredOnInvalidConfiguration(
string $jsonContents,
): void {
$this->expectException(RuntimeException::class);

Configuration::fromJson($jsonContents);
}

/** @psalm-return iterable<string, array{string}> */
public function invalidConfigurations(): iterable
{
yield 'empty content' => [''];
yield 'empty string' => ['""'];
yield 'int' => ['0'];
yield 'float' => ['0.1'];
yield 'boolean' => ['false'];
yield 'baseline with string' => ['{"baseline": "this should be a list"}'];
yield 'baseline with int' => ['{"baseline": 0}'];
yield 'baseline with float' => ['{"baseline": 0.0}'];
yield 'baseline with bool' => ['{"baseline": true}'];
yield 'baseline with array of float' => ['{"baseline": [0.0]}'];
yield 'baseline with array of bool' => ['{"baseline": [false]}'];
yield 'baseline with array of object' => ['{"baseline": [{}]}'];
}
}

0 comments on commit 0680f22

Please sign in to comment.