Skip to content

Commit

Permalink
Make configuration parsing more flexible
Browse files Browse the repository at this point in the history
This moves responsibilities around and introduces abstractions to
support different file formats with less effort.

Signed-off-by: Luís Cobucci <lcobucci@gmail.com>
  • Loading branch information
lcobucci committed Feb 23, 2023
1 parent 3cc1fbe commit c251f5a
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 132 deletions.
25 changes: 1 addition & 24 deletions src/Command/AssertBackwardsCompatible.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

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

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 @@ -130,7 +127,7 @@ public function execute(InputInterface $input, OutputInterface $output): int

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

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

$stdErr->writeln(Str\format(
'Comparing from %s to %s...',
Expand Down Expand Up @@ -220,24 +217,4 @@ 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);
}
}
44 changes: 0 additions & 44 deletions src/Command/Configuration.php

This file was deleted.

36 changes: 36 additions & 0 deletions src/Command/DetermineConfigurationFromFilesystem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Roave\BackwardCompatibility\Command;

use Psl\Str;
use Psl\Type;
use Roave\BackwardCompatibility\Configuration\Configuration;
use Roave\BackwardCompatibility\Configuration\ParseConfigurationFile;
use Roave\BackwardCompatibility\Configuration\ParseJsonConfigurationFile;
use Symfony\Component\Console\Output\OutputInterface;

final class DetermineConfigurationFromFilesystem
{
public function __construct(
private readonly ParseConfigurationFile $parser = new ParseJsonConfigurationFile(),
) {
}

public function __invoke(
string $currentDirectory,
OutputInterface $stdErr,
): Configuration {
$configuration = $this->parser->parse($currentDirectory);

if ($configuration->filename !== null) {
$stdErr->writeln(Str\format(
'Using "%s" as configuration file',
Type\string()->coerce($configuration->filename),
));
}

return $configuration;
}
}
27 changes: 27 additions & 0 deletions src/Configuration/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Roave\BackwardCompatibility\Configuration;

use Roave\BackwardCompatibility\Baseline;

/** @psalm-immutable */
final class Configuration
{
private function __construct(
public readonly Baseline $baseline,
public readonly string|null $filename,
) {
}

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

public static function fromFile(Baseline $baseline, string $filename): self
{
return new self($baseline, $filename);
}
}
9 changes: 9 additions & 0 deletions src/Configuration/ParseConfigurationFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);

namespace Roave\BackwardCompatibility\Configuration;

interface ParseConfigurationFile
{
public function parse(string $currentDirectory): Configuration;
}
48 changes: 48 additions & 0 deletions src/Configuration/ParseJsonConfigurationFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Roave\BackwardCompatibility\Configuration;

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

final class ParseJsonConfigurationFile implements ParseConfigurationFile
{
private const CONFIGURATION_FILENAME = '.roave-backward-compatibility-check.json';

public function parse(string $currentDirectory): Configuration
{
$filename = $currentDirectory . '/' . self::CONFIGURATION_FILENAME;

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

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 Configuration::fromFile(
Baseline::fromList(...$baseline),
$filename,
);
}
}
67 changes: 3 additions & 64 deletions test/unit/Command/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,77 +6,16 @@

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

/** @covers \Roave\BackwardCompatibility\Command\Configuration */
/** @covers \Roave\BackwardCompatibility\Configuration\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": [{}]}'];
self::assertNull($config->filename);
}
}

0 comments on commit c251f5a

Please sign in to comment.