diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a574d4..1092307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 5.9.0 - 2024-11-29 + +### Added + +- By default the printer groups the proofs output in a GitHub Action +- `Innmind\BlackBox\Runner\Printer\Standard::disableGitHubOutput()` + ## 5.8.0 - 2024-11-09 ### Added diff --git a/documentation/config.md b/documentation/config.md index 9dc6132..42b867d 100644 --- a/documentation/config.md +++ b/documentation/config.md @@ -92,3 +92,22 @@ Application::new([]) ->tryToProve(Load::everythingIn('proofs/')) ->exit(); ``` + +## Disable GitHub Action output + +When it detects it's run inside a GitHub Action the framework groups each proof output to make the output more compact for large suites. It also adds annotations to quickly jump to each failing proof. + +You can disable such behaviour like this: + +```php hl_lines="4 8" +use Innmind\BlackBox\{ + Application, + Runner\Load, + Runner\Printer\Standard, +}; + +Application::new([]) + ->usePrinter(Standard::new()->disableGitHubOutput()) + ->tryToProve(Load::everythingIn('proofs/')) + ->exit(); +``` diff --git a/documentation/index.md b/documentation/index.md index a36b556..4e36d0b 100644 --- a/documentation/index.md +++ b/documentation/index.md @@ -8,7 +8,7 @@ hide: BlackBox is a [Property Based Testing](https://en.wikipedia.org/wiki/Software_testing#Property_testing) framework. -It leverages randomness ti prove the correctness of your code. +It leverages randomness to prove the correctness of your code. It's the main testing framework for the [Innmind ecosystem](https://innmind.github.io/documentation/). diff --git a/proofs/runner/printer.php b/proofs/runner/printer.php index fa61cd5..8d12fcd 100644 --- a/proofs/runner/printer.php +++ b/proofs/runner/printer.php @@ -136,7 +136,7 @@ static function($assert, $proofs, $scenarii, $assertions, $failures) { Set\Sequence::of(Set\Elements::of(...Tag::cases())), ), static function($assert, $name, $tags) { - $printer = Standard::new(); + $printer = Standard::new()->disableGitHubOutput(); $io = Collect::new(); $printer->proof($io, $io, Name::of($name), $tags); @@ -154,8 +154,9 @@ static function($assert, $name, $tags) { ->contains($name); }, )->tag(Tag::ci, Tag::local); + yield proof( - 'Printer->proof()->emptySet()', + 'Printer->proof() in GitHub Action', given( Set\Strings::any(), Set\Sequence::of(Set\Elements::of(...Tag::cases())), @@ -164,6 +165,33 @@ static function($assert, $name, $tags) { $printer = Standard::new(); $io = Collect::new(); + $printer->proof($io, $io, Name::of($name), $tags); + + $written = $io->toString(); + + foreach ($tags as $tag) { + $assert + ->string($written) + ->contains($tag->name); + } + + $assert + ->string($written) + ->startsWith('::group::') + ->contains($name); + }, + )->tag(Tag::ci); + + yield proof( + 'Printer->proof()->emptySet()', + given( + Set\Strings::any(), + Set\Sequence::of(Set\Elements::of(...Tag::cases())), + ), + static function($assert, $name, $tags) { + $printer = Standard::new()->disableGitHubOutput(); + $io = Collect::new(); + $printer ->proof($io, $io, Name::of($name), $tags) ->emptySet($io, $io); @@ -175,6 +203,29 @@ static function($assert, $name, $tags) { ->same(\end($written)); }, )->tag(Tag::ci, Tag::local); + + yield proof( + 'Printer->proof()->emptySet() in GitHub Action', + given( + Set\Strings::any(), + Set\Sequence::of(Set\Elements::of(...Tag::cases())), + ), + static function($assert, $name, $tags) { + $printer = Standard::new(); + $io = Collect::new(); + + $printer + ->proof($io, $io, Name::of($name), $tags) + ->emptySet($io, $io); + + $written = \implode('', $io->written()); + + $assert + ->string($written) + ->endsWith("No scenario found\n::endgroup::\n"); + }, + )->tag(Tag::ci); + yield proof( 'Printer->proof()->success()', given( @@ -225,7 +276,7 @@ static function($assert, $name, $tags) { Set\Strings::any(), ), static function($assert, $name, $val, $truth) { - $printer = Standard::new(); + $printer = Standard::new()->disableGitHubOutput(); $io = Collect::new(); $printer @@ -248,6 +299,40 @@ static function($assert, $name, $val, $truth) { ->contains($truth); }, )->tag(Tag::ci, Tag::local); + + yield proof( + 'Printer->proof()->failure() for Failure\Truth in GitHub Action', + given( + Set\Strings::any(), + Set\Strings::madeOf(Set\Chars::alphanumerical()), + Set\Strings::any(), + ), + static function($assert, $name, $val, $truth) { + $printer = Standard::new(); + $io = Collect::new(); + + $printer + ->proof($io, $io, Name::of($name), []) + ->failed($io, $io, Failure::of( + Assert\Failure::of(Truth::of($truth)), + Value::immutable(Scenario\Inline::of( + [$val], + static fn($assert, $foo) => null, + )), + )); + + $written = $io->toString(); + + $assert + ->string($written) + ->contains("F\n\n") + ->contains('$foo = ') + ->contains($val) + ->contains('::error ::') + ->contains($truth); + }, + )->tag(Tag::ci); + yield proof( 'Printer->proof()->failure() for Failure\Property', given( @@ -257,7 +342,7 @@ static function($assert, $name, $val, $truth) { Set\Strings::any(), ), static function($assert, $name, $property, $val, $message) { - $printer = Standard::new(); + $printer = Standard::new()->disableGitHubOutput(); $io = Collect::new(); $printer @@ -285,6 +370,46 @@ static function($assert, $name, $property, $val, $message) { ->contains($message); }, )->tag(Tag::ci, Tag::local); + + yield proof( + 'Printer->proof()->failure() for Failure\Property in GitHub Action', + given( + Set\Strings::any(), + Set\Strings::madeOf(Set\Chars::alphanumerical()), + Set\Strings::madeOf(Set\Chars::alphanumerical()), + Set\Strings::any(), + ), + static function($assert, $name, $property, $val, $message) { + $printer = Standard::new(); + $io = Collect::new(); + + $printer + ->proof($io, $io, Name::of($name), []) + ->failed($io, $io, Failure::of( + Assert\Failure::of(Property::of( + $property, + $message, + )), + Value::immutable(Scenario\Inline::of( + [$val], + static fn($assert, $foo) => null, + )), + )); + + $written = $io->toString(); + + $assert + ->string($written) + ->contains("F\n\n") + ->contains('$variable = ') + ->contains($property) + ->contains('$foo = ') + ->contains($val) + ->contains('::error ::') + ->contains($message); + }, + )->tag(Tag::ci); + yield proof( 'Printer->proof()->failure() for Failure\Comparison', given( @@ -295,7 +420,7 @@ static function($assert, $name, $property, $val, $message) { Set\Strings::any(), ), static function($assert, $name, $expected, $actual, $val, $message) { - $printer = Standard::new(); + $printer = Standard::new()->disableGitHubOutput(); $io = Collect::new(); $printer @@ -326,6 +451,50 @@ static function($assert, $name, $expected, $actual, $val, $message) { ->contains($message); }, )->tag(Tag::ci, Tag::local); + + yield proof( + 'Printer->proof()->failure() for Failure\Comparison in GitHub Action', + given( + Set\Strings::any(), + Set\Strings::madeOf(Set\Chars::alphanumerical()), + Set\Strings::madeOf(Set\Chars::alphanumerical()), + Set\Strings::madeOf(Set\Chars::alphanumerical()), + Set\Strings::any(), + ), + static function($assert, $name, $expected, $actual, $val, $message) { + $printer = Standard::new(); + $io = Collect::new(); + + $printer + ->proof($io, $io, Name::of($name), []) + ->failed($io, $io, Failure::of( + Assert\Failure::of(Comparison::of( + $expected, + $actual, + $message, + )), + Value::immutable(Scenario\Inline::of( + [$val], + static fn($assert, $foo) => null, + )), + )); + + $written = $io->toString(); + + $assert + ->string($written) + ->contains("F\n\n") + ->contains('$expected = ') + ->contains($expected) + ->contains('$actual = ') + ->contains($actual) + ->contains('$foo = ') + ->contains($val) + ->contains('::error ::') + ->contains($message); + }, + )->tag(Tag::ci); + yield proof( 'Printer->proof()->failure() for Scenario\Property', given( @@ -397,7 +566,7 @@ static function($assert, $name, $message) { Set\Sequence::of(Set\Elements::of(...Tag::cases())), ), static function($assert, $name, $tags) { - $printer = Standard::new(); + $printer = Standard::new()->disableGitHubOutput(); $io = Collect::new(); $printer @@ -411,4 +580,26 @@ static function($assert, $name, $tags) { ->same(\end($written)); }, )->tag(Tag::ci, Tag::local); + + yield proof( + 'Printer->proof()->end() in GitHub Action', + given( + Set\Strings::any(), + Set\Sequence::of(Set\Elements::of(...Tag::cases())), + ), + static function($assert, $name, $tags) { + $printer = Standard::new(); + $io = Collect::new(); + + $printer + ->proof($io, $io, Name::of($name), $tags) + ->end($io, $io); + + $written = \implode('', $io->written()); + + $assert + ->string($written) + ->endsWith("\n\n::endgroup::\n"); + }, + )->tag(Tag::ci); }; diff --git a/src/Runner/Printer/Proof/Standard.php b/src/Runner/Printer/Proof/Standard.php index ce1cee1..89e4067 100644 --- a/src/Runner/Printer/Proof/Standard.php +++ b/src/Runner/Printer/Proof/Standard.php @@ -25,25 +25,37 @@ final class Standard implements Proof private CliDumper $dumper; private VarCloner $cloner; private bool $addMarks; + private bool $addGroups; private int $scenarii = 0; - private function __construct(bool $withColors, bool $addMarks) - { + private function __construct( + bool $withColors, + bool $addMarks, + bool $addGroups, + ) { $this->dumper = new CliDumper; $this->cloner = new VarCloner; $this->addMarks = $addMarks; + $this->addGroups = $addGroups; $this->dumper->setColors($withColors); $this->cloner->setMinDepth(100); } - public static function new(bool $withColors, bool $addMarks): self - { - return new self($withColors, $addMarks); + public static function new( + bool $withColors, + bool $addMarks, + bool $addGroups, + ): self { + return new self($withColors, $addMarks, $addGroups); } public function emptySet(IO $output, IO $error): void { $error("No scenario found\n"); + + if ($this->addGroups) { + $output("::endgroup::\n"); + } } public function success(IO $output, IO $error): void @@ -69,7 +81,11 @@ public function failed(IO $output, IO $error, Failure $failure): void $this->renderFailure($output, $failure->assertion()->kind()); $output(\sprintf( - "\n%s\n", + "\n%s%s\n", + match ($this->addGroups) { + true => '::error ::', + false => '', + }, $failure->assertion()->kind()->message(), )); @@ -126,6 +142,10 @@ public function failed(IO $output, IO $error, Failure $failure): void public function end(IO $output, IO $error): void { $output("\n\n"); + + if ($this->addGroups) { + $output("::endgroup::\n"); + } } private function newLine(IO $output): void diff --git a/src/Runner/Printer/Standard.php b/src/Runner/Printer/Standard.php index 591ccf7..5a0ed8f 100644 --- a/src/Runner/Printer/Standard.php +++ b/src/Runner/Printer/Standard.php @@ -19,12 +19,18 @@ final class Standard implements Printer private Timer $timer; private bool $withColors; private bool $addMarks; + private bool $addGroups; - private function __construct(bool $withColors) - { - $this->timer = new Timer; + private function __construct( + bool $withColors, + ?Timer $timer = null, + ?bool $addMarks = null, + ?bool $addGroups = null, + ) { + $this->timer = $timer ?? new Timer; $this->withColors = $withColors; - $this->addMarks = \getenv('LC_TERMINAL') === 'iTerm2'; + $this->addMarks = $addMarks ?? \getenv('LC_TERMINAL') === 'iTerm2'; + $this->addGroups = $addGroups ?? \getenv('GITHUB_ACTIONS') === 'true'; } /** @@ -40,6 +46,16 @@ public static function withoutColors(): self return new self(false); } + public function disableGitHubOutput(): self + { + return new self( + $this->withColors, + $this->timer, + $this->addMarks, + false, + ); + } + public function start(IO $output, IO $error): void { $this->timer->start(); @@ -53,17 +69,29 @@ public function proof( Proof\Name $proof, array $tags, ): Printer\Proof { + $header = ''; + foreach ($tags as $tag) { - $output("[{$tag->name}]"); + $header .= "[{$tag->name}]"; } if (\count($tags) > 0) { - $output(' '); + $header .= ' '; } - $output($proof->toString().":\n"); + $header .= $proof->toString().":\n"; + + if ($this->addGroups) { + $header = '::group::'.$header; + } - return Printer\Proof\Standard::new($this->withColors, $this->addMarks); + $output($header); + + return Printer\Proof\Standard::new( + $this->withColors, + $this->addMarks, + $this->addGroups, + ); } public function end(IO $output, IO $error, Stats $stats): void