Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/rector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Rector

on:
push:
paths:
- 'core/**'
- 'plugin/**'
- 'bridge/**'
- 'rector.php'
- 'composer.json'
- 'composer.lock'
- '.github/workflows/rector.yml'
pull_request:
paths:
- 'core/**'
- 'plugin/**'
- 'bridge/**'
- 'rector.php'
- 'composer.json'
- 'composer.lock'
- '.github/workflows/rector.yml'

jobs:
rector:
runs-on: ubuntu-latest
name: Rector

steps:

- name: Checkout
uses: actions/checkout@v7

# The split sub-packages pin testo/testo; in CI the root is a detached
# commit (dev-<sha>), so its version must be declared explicitly.
- name: Resolve root package version
run: echo "COMPOSER_ROOT_VERSION=$(jq -r '.["."]' resources/version.json)" >> "$GITHUB_ENV"

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
coverage: none

- name: Install Composer dependencies
uses: ramsey/composer-install@v3
with:
dependency-versions: highest

- name: Run Rector
run: composer rector:ci
1 change: 1 addition & 0 deletions .github/workflows/split-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ on: # yamllint disable-line rule:truthy
- 'convention-[0-9]*'
- 'data-[0-9]*'
- 'facade-[0-9]*'
- 'error-handler-[0-9]*'
- 'filter-[0-9]*'
- 'inline-[0-9]*'
- 'lifecycle-[0-9]*'
Expand Down
12 changes: 6 additions & 6 deletions bridge/infection/src/TestoAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,32 @@
*
* @internal
*/
final class TestoAdapter implements TestFrameworkAdapter
final readonly class TestoAdapter implements TestFrameworkAdapter
{
/** @var non-empty-string Path to the Testo PHP entry script. */
private readonly string $testFrameworkExecutable;
private string $testFrameworkExecutable;

public function __construct(
string $testFrameworkExecutable,
/** @var non-empty-string Absolute path to the project directory. */
private readonly string $projectDir,
private string $projectDir,
/** @var non-empty-string Infection's tmp directory; safe to drop per-mutant bootstrap files in. */
private readonly string $tmpDir,
private string $tmpDir,
/**
* @var non-empty-string Path where Infection expects the JUnit XML
* report. We pass it back to Testo via `--log-junit=<path>` and
* probe its existence in {@see hasJUnitReport()} to decide
* whether to use JUnit-driven test mapping or fall back to
* reflection-based resolution.
*/
private readonly string $jUnitFilePath,
private string $jUnitFilePath,
/**
* @var non-empty-string Directory where Infection expects the PHPUnit-style coverage XML
* (it reads `<dir>/index.xml`). We pass it back to Testo via `--coverage-xml=<dir>`,
* which activates the default (shadow) `CodecovPlugin` — so the coverage report is
* produced even when the user's `testo.php` declares no coverage plugin.
*/
private readonly string $coverageXmlPath = '',
private string $coverageXmlPath = '',
) {
# On Windows, Infection's TestFrameworkFinder may hand us `bin/testo.bat`.
# We can't `php testo.bat` — strip the `.bat` and run the sibling PHP script directly.
Expand Down
2 changes: 1 addition & 1 deletion bridge/symfony-console/src/Command/Init.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@

try {
$basePath = Path::create((string) $input->getOption('path'));
self::ensureDirectory($basePath, $io);

Check warning on line 58 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ try { $basePath = Path::create((string) $input->getOption('path')); - self::ensureDirectory($basePath, $io); + $srcPath = self::discoverSourceDirectory($input, $basePath, $io);

$srcPath = self::discoverSourceDirectory($input, $basePath, $io);

$testsPath = $basePath->join('tests');
self::ensureDirectory($testsPath, $io);

Check warning on line 63 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ $srcPath = self::discoverSourceDirectory($input, $basePath, $io); $testsPath = $basePath->join('tests'); - self::ensureDirectory($testsPath, $io); + $suites = self::discoverSuites($testsPath, $io); $composerKeys = self::updateComposerScripts($suites, $basePath, $io);

$suites = self::discoverSuites($testsPath, $io);
$composerKeys = self::updateComposerScripts($suites, $basePath, $io);
Expand All @@ -69,8 +69,8 @@

# Non-interactive + existing config: bail out before touching the file.
if ($configPath->isFile() && !$input->isInteractive()) {
$io->warning(\sprintf('%s already exists. Skipping (non-interactive mode).', $configPath));

Check warning on line 72 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ # Non-interactive + existing config: bail out before touching the file. if ($configPath->isFile() && !$input->isInteractive()) { - $io->warning(\sprintf('%s already exists. Skipping (non-interactive mode).', $configPath)); + return Command::SUCCESS; }
return Command::SUCCESS;

Check warning on line 73 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ # Non-interactive + existing config: bail out before touching the file. if ($configPath->isFile() && !$input->isInteractive()) { $io->warning(\sprintf('%s already exists. Skipping (non-interactive mode).', $configPath)); - return Command::SUCCESS; + } if (self::writeConfig($configPath, $srcPath, $suites, $io)) {
}

if (self::writeConfig($configPath, $srcPath, $suites, $io)) {
Expand All @@ -79,7 +79,7 @@
$io->note(\sprintf('Left existing %s untouched.', $configPath));
}
} catch (\Throwable $exception) {
$output->writeln('');

Check warning on line 82 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ $io->note(\sprintf('Left existing %s untouched.', $configPath)); } } catch (\Throwable $exception) { - $output->writeln(''); + $output->writeln(\sprintf('<fg=red>%s</>', $exception->getMessage())); return Command::FAILURE; }
$output->writeln(\sprintf('<fg=red>%s</>', $exception->getMessage()));
return Command::FAILURE;
}
Expand All @@ -90,11 +90,11 @@
private static function ensureDirectory(Path $path, SymfonyStyle $io): void
{
if ($path->isDir()) {
return;

Check warning on line 93 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ private static function ensureDirectory(Path $path, SymfonyStyle $io): void { if ($path->isDir()) { - return; + } \mkdir((string) $path, 0755, true);
}

\mkdir((string) $path, 0755, true);

Check warning on line 96 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "IncrementInteger": @@ @@ return; } - \mkdir((string) $path, 0755, true); + \mkdir((string) $path, 494, true); $io->success(\sprintf('Created %s/', $path)); }

Check warning on line 96 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "DecrementInteger": @@ @@ return; } - \mkdir((string) $path, 0755, true); + \mkdir((string) $path, 492, true); $io->success(\sprintf('Created %s/', $path)); }
$io->success(\sprintf('Created %s/', $path));

Check warning on line 97 in bridge/symfony-console/src/Command/Init.php

View workflow job for this annotation

GitHub Actions / Infection PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ } \mkdir((string) $path, 0755, true); - $io->success(\sprintf('Created %s/', $path)); + } /**
}

/**
Expand Down Expand Up @@ -250,7 +250,7 @@
$runHints = $composerKeys === []
? [' <comment>$ vendor/bin/testo</comment>']
: \array_map(
static fn(string $key) => \sprintf(' <comment>$ composer %s</comment>', $key),
static fn(string $key): string => \sprintf(' <comment>$ composer %s</comment>', $key),
$composerKeys,
);

Expand Down
10 changes: 10 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,13 @@ ignore:
- "**/tests/**"
- "resources/**"
- "skills/**"
# Bench warmup loop (warmup=0 in all tests) and bench renderer are unreachable under TESTO_CI=1
# because bench tests fail due to Xdebug stack depth before the renderer is ever called.
- "plugin/bench/src/Internal/BenchHandler.php"
- "plugin/bench/src/Internal/Renderer.php"
# SuiteFactory runs during test discovery (before per-test coverage windows open),
# so its lines never appear as covered in the clover report.
- "core/Application/Internal/SuiteFactory.php"
# TestingSuite is @psalm-internal Testo and is only instantiated via attribute reflection
# inside InjectPlugin tests, which are excluded from TESTO_CI=1 runs.
- "core/Testing/Attribute/TestingSuite.php"
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"testo/codecov": "^0.1.11",
"testo/convention": "^0.1.4",
"testo/data": "^0.1.6",
"testo/error-handler": "^0.1",
"testo/filter": "^0.1.5",
"testo/inline": "^0.1.6",
"testo/lifecycle": "^0.1.5",
Expand Down Expand Up @@ -92,6 +93,7 @@
"Tests\\Convention\\": "plugin/convention/tests/",
"Tests\\Data\\": "plugin/data/tests/",
"Tests\\Facade\\": "plugin/facade/tests/",
"Tests\\ErrorHandler\\": "plugin/error-handler/tests/",
"Tests\\Filter\\": "plugin/filter/tests/",
"Tests\\Lifecycle\\": "plugin/lifecycle/tests/",
"Tests\\Repeat\\": "plugin/repeat/tests/",
Expand All @@ -115,6 +117,7 @@
"testo/convention": "0.1.x-dev",
"testo/data": "0.1.x-dev",
"testo/facade": "0.1.x-dev",
"testo/error-handler": "0.1.x-dev",
"testo/filter": "0.1.x-dev",
"testo/inline": "0.1.x-dev",
"testo/lifecycle": "0.1.x-dev",
Expand Down Expand Up @@ -163,6 +166,8 @@
"post-update-cmd": "dload get --no-interaction -v || \"echo can't dload binaries\"",
"cs:diff": "php-cs-fixer fix --dry-run -v --diff",
"cs:fix": "php-cs-fixer fix -v",
"rector": "rector",
"rector:ci": "rector --dry-run --clear-cache",
"infect": [
"@putenv TESTO_CI=1",
"@putenv XDEBUG_MODE=coverage",
Expand Down
2 changes: 1 addition & 1 deletion core/Application/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
array $environment = [],
): self {
$container = new ObjectContainer();
$args = [

Check warning on line 64 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ ): self { $container = new ObjectContainer(); $args = [ - 'env' => $environment, 'inputArguments' => $inputArguments, 'inputOptions' => $inputOptions, ];
'env' => $environment,
'inputArguments' => $inputArguments,
'inputOptions' => $inputOptions,
Expand All @@ -77,14 +77,14 @@
'Configuration file %s must return an instance of %s, %s returned.',
$configFile,
ApplicationConfig::class,
\is_object($cfg) ? \get_class($cfg) : \gettype($cfg),
\get_debug_type($cfg),
),
);
return $cfg;
});

# Register Config inflector
$container->addInflector($container->make(ConfigInflector::class, $args));

Check warning on line 87 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ return $cfg; }); - # Register Config inflector - $container->addInflector($container->make(ConfigInflector::class, $args)); + return new self($container); }

return new self($container);
}
Expand All @@ -93,12 +93,12 @@
{
return $this->container->scope(static function (Container $container): RunResult {
$appConfig = $container->get(ApplicationConfig::class);
self::applyPlugins($container, self::resolvePlugins($appConfig->plugins, ApplicationPlugins::class));

Check warning on line 96 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ { return $this->container->scope(static function (Container $container): RunResult { $appConfig = $container->get(ApplicationConfig::class); - self::applyPlugins($container, self::resolvePlugins($appConfig->plugins, ApplicationPlugins::class)); + $filter = $container->get(Filter::class); $dispatcher = $container->get(EventDispatcherInterface::class);
$filter = $container->get(Filter::class);

$dispatcher = $container->get(EventDispatcherInterface::class);
$dispatcher->dispatch(new SessionStarting());

Check warning on line 100 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ $filter = $container->get(Filter::class); $dispatcher = $container->get(EventDispatcherInterface::class); - $dispatcher->dispatch(new SessionStarting()); + $dispatcher->dispatch(new WorkerStarting()); $suiteProvider = $container->get(SuiteProvider::class);
$dispatcher->dispatch(new WorkerStarting());

Check warning on line 101 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ $dispatcher = $container->get(EventDispatcherInterface::class); $dispatcher->dispatch(new SessionStarting()); - $dispatcher->dispatch(new WorkerStarting()); + $suiteProvider = $container->get(SuiteProvider::class); $status = Status::Passed;

$suiteProvider = $container->get(SuiteProvider::class);
$status = Status::Passed;
Expand All @@ -106,13 +106,13 @@

# Iterate and run Test Suites
$suiteResults = [];
foreach ($appConfig->suites as $config) {

Check warning on line 109 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "Foreach_": @@ @@ # Iterate and run Test Suites $suiteResults = []; - foreach ($appConfig->suites as $config) { + foreach ([] as $config) { # Resolve a Test Suite from config and # run a separate container scope to isolate services and plugins. $suiteResult = $container->scope( @@ @@ if ($suiteResult === null) { continue; } - $suiteResults[] = $suiteResult; $suiteResult->status->isFailure() and $status = Status::Failed; }
# Resolve a Test Suite from config and
# run a separate container scope to isolate services and plugins.
$suiteResult = $container->scope(
static function (Container $container) use ($filter, $config, $suiteProvider): ?SuiteResult {
# Apply plugins first to have all interceptors before suite files scanning
self::applyPlugins($container, self::resolvePlugins($config->plugins, SuitePlugins::class));

Check warning on line 115 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ # run a separate container scope to isolate services and plugins. $suiteResult = $container->scope( static function (Container $container) use ($filter, $config, $suiteProvider): ?SuiteResult { - # Apply plugins first to have all interceptors before suite files scanning - self::applyPlugins($container, self::resolvePlugins($config->plugins, SuitePlugins::class)); + if (null === $suite = $suiteProvider->findSuite($config)) { return null;

if (null === $suite = $suiteProvider->findSuite($config)) {
return null;
Expand All @@ -123,15 +123,15 @@
},
);
if ($suiteResult === null) {
continue;

Check warning on line 126 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "Continue_": @@ @@ }, ); if ($suiteResult === null) { - continue; + break; } $suiteResults[] = $suiteResult;
}

$suiteResults[] = $suiteResult;
$suiteResult->status->isFailure() and $status = Status::Failed;
}

$duration = \microtime(true) - $duration;

Check warning on line 133 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "Minus": @@ @@ $suiteResult->status->isFailure() and $status = Status::Failed; } - $duration = \microtime(true) - $duration; + $duration = \microtime(true) + $duration; $summary = Summary::combine(\array_map( static fn(SuiteResult $r): Summary => $r->summary, $suiteResults,
$summary = Summary::combine(\array_map(

Check warning on line 134 in core/Application/Application.php

View workflow job for this annotation

GitHub Actions / Infection (PHPUnit mirror) PHP8.4

Escaped Mutant for Mutator "UnwrapArrayMap": @@ @@ } $duration = \microtime(true) - $duration; - $summary = Summary::combine(\array_map( - static fn(SuiteResult $r): Summary => $r->summary, - $suiteResults, - )); + $summary = Summary::combine($suiteResults); # An empty run — no tests found, or every test filtered out — verified nothing, so it is # not a success. Flag it as Risky (unless something already failed) so every reporter and
static fn(SuiteResult $r): Summary => $r->summary,
$suiteResults,
));
Expand Down
2 changes: 1 addition & 1 deletion core/Application/Config/ApplicationConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function __construct(

# Validate suite configs
$suites === [] and throw new \InvalidArgumentException('At least one test suite must be defined.');
\array_walk($suites, static fn(mixed $suite) => $suite instanceof SuiteConfig
\array_walk($suites, static fn(mixed $suite): bool => $suite instanceof SuiteConfig
or throw new \InvalidArgumentException(
'Each suite must be an instance of SuiteConfig.',
));
Expand Down
1 change: 0 additions & 1 deletion core/Application/Config/Internal/ConfigInflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ private function injectValue(
// Cast value to the property type
$type = $property->getType();

/** @var mixed $result */
$result = match (true) {
!$type instanceof \ReflectionNamedType => $value,
$type->allowsNull() && $value === '' => null,
Expand Down
4 changes: 2 additions & 2 deletions core/Application/Internal/Messenger/State.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private function absorbEvents(array $events): void
if ($this->holdEvents) {
$this->heldEvents = \array_merge($this->heldEvents, $events);
# Keep held events in time order so they are released chronologically on commit.
\usort($this->heldEvents, static fn(Message $a, Message $b) => $a->time <=> $b->time);
\usort($this->heldEvents, static fn(Message $a, Message $b): int => $a->time <=> $b->time);
return;
}

Expand Down Expand Up @@ -169,7 +169,7 @@ private function merge(self $state): void

# Out-of-order (clock skew / interleaving): combine and stable-sort by time.
$merged = \array_merge($this->messages, $state->messages);
\usort($merged, static fn(Message $a, Message $b) => $a->time <=> $b->time);
\usort($merged, static fn(Message $a, Message $b): int => $a->time <=> $b->time);
$this->messages = $merged;
}
}
4 changes: 2 additions & 2 deletions core/Application/Internal/SuiteFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function __construct(
public function create(SuiteConfig $config, Filter $filter): SuiteInfo
{
$files = $this->getFilesIterator($config, $filter);
$definitions = $this->getCaseDefinitions($config, $files, $filter);
$definitions = $this->getCaseDefinitions($files, $filter);

$cases = [];
foreach ($definitions as $definition) {
Expand Down Expand Up @@ -89,7 +89,7 @@ private function getFilesIterator(SuiteConfig $config, Filter $filter): iterable
* @param iterable<TokenizedFile> $files
* @return list<CaseDefinition>
*/
private function getCaseDefinitions(SuiteConfig $config, iterable $files, Filter $filter): array
private function getCaseDefinitions(iterable $files, Filter $filter): array
{
$cases = [];
# Prepare interceptors pipeline
Expand Down
1 change: 0 additions & 1 deletion core/Common/Info.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public static function version(): string
return $cache = self::VERSION;
}

/** @var mixed $version */
$version = \json_decode($fileContent, true)['.'] ?? null;

return $cache = \is_string($version) && $version !== ''
Expand Down
6 changes: 1 addition & 5 deletions core/Output/Json/JsonPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ final class JsonPlugin implements PluginConfigurator
*/
private readonly ?Path $path;

/** @var resource|null Stream used in stdout mode; resolved to {@see \STDOUT} on write. */
private $stream;

private readonly JsonReport $report;

/**
Expand All @@ -54,10 +51,9 @@ final class JsonPlugin implements PluginConfigurator
* @param resource|null $stream Stream for stdout mode; defaults to {@see \STDOUT}. Ignored
* when a file path is set.
*/
public function __construct(?string $outputPath = null, $stream = null)
public function __construct(?string $outputPath = null, private $stream = null)
{
$this->path = $outputPath !== null && $outputPath !== '' ? Path::create($outputPath) : null;
$this->stream = $stream;
$this->report = new JsonReport();
}

Expand Down
12 changes: 6 additions & 6 deletions core/Output/Rendering/ChannelRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ private static function header(string $channel, float $time): string

/**
* Formats a {@see \microtime()} timestamp as `HH:MM:SS.mmm` wall-clock time.
*
* @return non-empty-string
*/
private static function formatTime(float $time): string
{
$seconds = (int) $time;
$millis = \min(999, (int) \round(($time - (float) $seconds) * 1000.0));
$totalSeconds = (int) $time;
$millis = \min(999, (int) \round(($time - (float) $totalSeconds) * 1000.0));
$s = $totalSeconds % 60;
$m = (int) ($totalSeconds / 60) % 60;
$h = (int) ($totalSeconds / 3600) % 24;

/** @var non-empty-string */
return \date('H:i:s', $seconds) . \sprintf('.%03d', $millis);
return \sprintf('%02d:%02d:%02d.%03d', $h, $m, $s, $millis);
}
Comment thread
rossaddison marked this conversation as resolved.
}
4 changes: 2 additions & 2 deletions core/Output/Rendering/Diff/PatienceDiffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
*
* @internal
*/
final class PatienceDiffer implements Differ
final readonly class PatienceDiffer implements Differ
{
public function __construct(
private readonly Differ $fallback = new MyersDiffer(),
private Differ $fallback = new MyersDiffer(),
) {}

#[\Override]
Expand Down
4 changes: 2 additions & 2 deletions core/Output/Rendering/Diff/PrefixSuffixDiffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
*
* @internal
*/
final class PrefixSuffixDiffer implements Differ
final readonly class PrefixSuffixDiffer implements Differ
{
public function __construct(
private readonly Differ $inner = new MyersDiffer(),
private Differ $inner = new MyersDiffer(),
) {}

#[\Override]
Expand Down
4 changes: 2 additions & 2 deletions core/Output/Rendering/Diff/RatcliffObershelpDiffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
*
* @internal
*/
final class RatcliffObershelpDiffer implements Differ
final readonly class RatcliffObershelpDiffer implements Differ
{
public function __construct(
private readonly bool $autoJunk = true,
private bool $autoJunk = true,
) {}

#[\Override]
Expand Down
2 changes: 0 additions & 2 deletions core/Output/Teamcity/Teamcity/TeamcityLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ public function logEmptyRun(): void
*/
public function handleSingleTestResult(TestResult $result, ?int $duration = null, ?string $overrideName = null): void
{
$name = $overrideName ?? $result->info->name;

match ($result->status) {
Status::Passed, Status::Flaky => $this->handlePassedTest($result, $duration, $overrideName),
Status::Failed, Status::Error => $this->handleFailedTest($result, $duration, $overrideName),
Expand Down
14 changes: 7 additions & 7 deletions core/Output/Terminal/Renderer/FormattedItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@
*
* @internal
*/
final class FormattedItem
final readonly class FormattedItem
{
public function __construct(
/**
* @var non-empty-string
*/
public readonly string $name,
public readonly Status $status,
public string $name,
public Status $status,
/**
* @var int<0, max>|null Duration in milliseconds
*/
public readonly ?int $duration = null,
public ?int $duration = null,
/**
* @var int<0, max> Indentation level (0 = no indent)
*/
public readonly int $indentLevel = 0,
public int $indentLevel = 0,
/**
* @var int<1, max>|null Index in collection (for numbered items)
*/
public readonly ?int $index = null,
public ?int $index = null,
/**
* @var non-empty-string|null Additional description (e.g., data provider key)
*/
public readonly string $description = '',
public string $description = '',
) {}
}
10 changes: 3 additions & 7 deletions core/Output/Terminal/Renderer/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,8 @@ public static function summary(
$result = "\n\n " . Style::bold('Summary') . "\n\n";
$result .= self::statRow('Time', Style::dim("{$testsTime} tests · {$overheadTime} overhead"));
$result .= self::statRow('Total', "{$total} tests · {$assertions} assertions");
$result .= self::statRow('', $breakdown);

return $result;
return $result . self::statRow('', $breakdown);
}

/**
Expand Down Expand Up @@ -390,17 +389,16 @@ private static function formatCompactRun(FormattedItem $item, OutputFormat $form
: '';

$result = "{$indent}{$symbol} {$item->name}{$durationStr}\n";
$result .= self::description($item->description, $item->indentLevel, $format);

return $result;
return $result . self::description($item->description, $item->indentLevel, $format);
}

/**
* Formats a test run in dots mode.
*/
private static function formatDotRun(FormattedItem $item): string
{
$symbol = match ($item->status) {
return match ($item->status) {
Status::Passed => DotSymbol::Passed->value,
Status::Failed => Style::error(DotSymbol::Failed->value),
Status::Skipped => Style::warning(DotSymbol::Skipped->value),
Expand All @@ -410,8 +408,6 @@ private static function formatDotRun(FormattedItem $item): string
Status::Flaky => Style::info(DotSymbol::Passed->value),
Status::Cancelled => Style::dim(DotSymbol::Skipped->value),
};

return $symbol;
}

/**
Expand Down
Loading
Loading