Skip to content

Commit dbb66df

Browse files
Closes #1151
1 parent d10d9a5 commit dbb66df

9 files changed

Lines changed: 81 additions & 49 deletions

File tree

ChangeLog-14.1.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
44

5+
## [14.1.3] - 2026-MM-DD
6+
7+
### Fixed
8+
9+
* [#1151](https://github.com/sebastianbergmann/php-code-coverage/issues/1151): Version check in `Unserializer::unserialize()` is too restrictive
10+
511
## [14.1.2] - 2026-04-15
612

713
### Fixed
@@ -28,6 +34,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
2834

2935
* [#1147](https://github.com/sebastianbergmann/php-code-coverage/pull/1147): `CoversClass` does not transitively target traits used by enumerations
3036

37+
[14.1.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/14.1.2...14.1
3138
[14.1.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/14.1.1...14.1.2
3239
[14.1.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/14.1.0...14.1.1
3340
[14.1.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/14.0.0...14.1.0

src/Exception/VersionMismatchException.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
*/
1919
final class VersionMismatchException extends RuntimeException implements Exception
2020
{
21-
public function __construct(string $storedVersion, string $currentVersion)
21+
public function __construct(int $storedFormat, int $supportedFormat)
2222
{
2323
parent::__construct(
2424
sprintf(
25-
'Coverage data was written by version %s and cannot be read by version %s',
26-
$storedVersion,
27-
$currentVersion,
25+
'Coverage data was written using serialization format %d and cannot be read by code that supports serialization format %d',
26+
$storedFormat,
27+
$supportedFormat,
2828
),
2929
);
3030
}

src/Serialization/Merger.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ public function merge(array $paths, bool $requireMatchingGitInformation = true,
103103
'timestamp' => (new DateTimeImmutable)->format('D M j G:i:s T Y'),
104104
'runtime' => $refRuntime,
105105
'phpCodeCoverage' => [
106-
'version' => Version::id(),
107-
'driverInformation' => $refDriver,
106+
'version' => Version::id(),
107+
'serializationFormat' => Serializer::SERIALIZATION_FORMAT,
108+
'driverInformation' => $refDriver,
108109
],
109110
];
110111

src/Serialization/Serializer.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* },
3434
* phpCodeCoverage: array{
3535
* version: string,
36+
* serializationFormat: int,
3637
* driverInformation: array{
3738
* name: non-empty-string,
3839
* version: non-empty-string,
@@ -55,6 +56,8 @@
5556
*/
5657
final readonly class Serializer
5758
{
59+
public const int SERIALIZATION_FORMAT = 1;
60+
5861
/**
5962
* @param non-empty-string $target
6063
*
@@ -72,8 +75,9 @@ public function serialize(string $target, CodeCoverage $codeCoverage, bool $incl
7275
'vendorUrl' => $runtime->getVendorUrl(),
7376
],
7477
'phpCodeCoverage' => [
75-
'version' => Version::id(),
76-
'driverInformation' => $codeCoverage->driverInformation(),
78+
'version' => Version::id(),
79+
'serializationFormat' => self::SERIALIZATION_FORMAT,
80+
'driverInformation' => $codeCoverage->driverInformation(),
7781
],
7882
];
7983

@@ -111,7 +115,7 @@ public function serialize(string $target, CodeCoverage $codeCoverage, bool $incl
111115

112116
Filesystem::write(
113117
$target,
114-
'<?php // phpunit/php-code-coverage version ' . Version::id() . PHP_EOL .
118+
'<?php // phpunit/php-code-coverage serialization format ' . self::SERIALIZATION_FORMAT . PHP_EOL .
115119
"return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'" . PHP_EOL .
116120
$serializedData . PHP_EOL .
117121
'END_OF_COVERAGE_SERIALIZATION' . PHP_EOL .

src/Serialization/Unserializer.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
use function fopen;
1616
use function is_array;
1717
use function is_bool;
18+
use function is_int;
1819
use function is_string;
1920
use function preg_match;
2021
use function restore_error_handler;
2122
use function set_error_handler;
2223
use function trim;
2324
use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData;
24-
use SebastianBergmann\CodeCoverage\Version;
2525

2626
/**
2727
* @phpstan-import-type SerializedCoverage from Serializer
@@ -55,14 +55,14 @@ public function unserialize(string $path): array
5555
throw new FileCouldNotBeReadException('Cannot read from file: ' . $path);
5656
}
5757

58-
if (preg_match('/^<\?php \/\/ phpunit\/php-code-coverage version (.+)$/', trim($firstLine), $matches) !== 1) {
59-
throw new FileCouldNotBeReadException('File does not contain phpunit/php-code-coverage version information: ' . $path);
58+
if (preg_match('/^<\?php \/\/ phpunit\/php-code-coverage serialization format (\d+)$/', trim($firstLine), $matches) !== 1) {
59+
throw new FileCouldNotBeReadException('File does not contain phpunit/php-code-coverage serialization format information: ' . $path);
6060
}
6161

62-
$storedVersion = $matches[1];
62+
$storedFormat = (int) $matches[1];
6363

64-
if ($storedVersion !== Version::id()) {
65-
throw new VersionMismatchException($storedVersion, Version::id());
64+
if ($storedFormat !== Serializer::SERIALIZATION_FORMAT) {
65+
throw new VersionMismatchException($storedFormat, Serializer::SERIALIZATION_FORMAT);
6666
}
6767

6868
set_error_handler(
@@ -126,6 +126,10 @@ private function validate(mixed $data): void
126126
throw new InvalidCoverageDataException('Coverage data is missing valid \'buildInformation.phpCodeCoverage.version\' key');
127127
}
128128

129+
if (!array_key_exists('serializationFormat', $phpCodeCoverage) || !is_int($phpCodeCoverage['serializationFormat'])) {
130+
throw new InvalidCoverageDataException('Coverage data is missing valid \'buildInformation.phpCodeCoverage.serializationFormat\' key');
131+
}
132+
129133
if (!array_key_exists('driverInformation', $phpCodeCoverage) || !is_array($phpCodeCoverage['driverInformation'])) {
130134
throw new InvalidCoverageDataException('Coverage data is missing valid \'buildInformation.phpCodeCoverage.driverInformation\' key');
131135
}

tests/tests/Exception/VersionMismatchExceptionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ final class VersionMismatchExceptionTest extends TestCase
1919
{
2020
public function testHasMessage(): void
2121
{
22-
$e = new VersionMismatchException('1.0', '2.0');
22+
$e = new VersionMismatchException(1, 2);
2323

2424
$this->assertSame(
25-
'Coverage data was written by version 1.0 and cannot be read by version 2.0',
25+
'Coverage data was written using serialization format 1 and cannot be read by code that supports serialization format 2',
2626
$e->getMessage(),
2727
);
2828
}

tests/tests/Serialization/MergerTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,9 @@ private function makeItem(array $overrides = [], ?ProcessedCodeCoverageData $cov
301301
'vendorUrl' => 'https://php.net',
302302
],
303303
'phpCodeCoverage' => [
304-
'version' => Version::id(),
305-
'driverInformation' => [
304+
'version' => Version::id(),
305+
'serializationFormat' => Serializer::SERIALIZATION_FORMAT,
306+
'driverInformation' => [
306307
'name' => 'Xdebug',
307308
'version' => '3.3.0',
308309
],
@@ -342,7 +343,7 @@ private function writeFile(string $filename, array $data): string
342343
{
343344
$path = TEST_FILES_PATH . 'tmp/' . $filename;
344345

345-
$content = '<?php // phpunit/php-code-coverage version ' . Version::id() . PHP_EOL .
346+
$content = '<?php // phpunit/php-code-coverage serialization format ' . Serializer::SERIALIZATION_FORMAT . PHP_EOL .
346347
"return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'" . PHP_EOL .
347348
serialize($data) . PHP_EOL .
348349
'END_OF_COVERAGE_SERIALIZATION' . PHP_EOL .

tests/tests/Serialization/SerializerTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected function tearDown(): void
3232
$this->removeTemporaryFiles();
3333
}
3434

35-
public function testSerializedFileHasVersionHeaderOnFirstLine(): void
35+
public function testSerializedFileHasSerializationFormatHeaderOnFirstLine(): void
3636
{
3737
$coverage = $this->getLineCoverageForBankAccount();
3838
$target = TEST_FILES_PATH . 'tmp/serialized.php';
@@ -43,8 +43,8 @@ public function testSerializedFileHasVersionHeaderOnFirstLine(): void
4343
$firstLine = fgets($file);
4444
fclose($file);
4545

46-
$this->assertMatchesRegularExpression(
47-
'/^<\?php \/\/ phpunit\/php-code-coverage version .+$/',
46+
$this->assertSame(
47+
'<?php // phpunit/php-code-coverage serialization format ' . Serializer::SERIALIZATION_FORMAT,
4848
trim($firstLine),
4949
);
5050
}
@@ -125,6 +125,7 @@ public function testSerializedDataPreservesBuildInformation(): void
125125
$this->assertArrayHasKey('timestamp', $data['buildInformation']);
126126
$this->assertArrayHasKey('runtime', $data['buildInformation']);
127127
$this->assertArrayHasKey('phpCodeCoverage', $data['buildInformation']);
128+
$this->assertSame(Serializer::SERIALIZATION_FORMAT, $data['buildInformation']['phpCodeCoverage']['serializationFormat']);
128129
$this->assertEquals($coverage->driverInformation(), $data['buildInformation']['phpCodeCoverage']['driverInformation']);
129130
}
130131

0 commit comments

Comments
 (0)