Skip to content

Commit

Permalink
assertArrayIs*ToArrayOnlyConsideringListOfKeys(): bug fix - better re…
Browse files Browse the repository at this point in the history
…spect PHP native array key handling

Related to sebastianbergmann/phpunit 5600

As things are, arrays in PHP can have either integer or string keys.
Depending on the key input, PHP does some type juggling magic though, like auto-converting purely integer string keys to integers and flooring floating point keys to integers.

While experienced devs will know this pitfall, less experienced devs (who also write tests) may not be as aware and may provide the keys in `$keysToBeConsidered` the same way as the original array was defined, not realizing that the type of some of the keys will have auto-magically been changed by PHP.

The code in the new `assertArrayIs*ToArrayOnlyConsideringListOfKeys()` assertions, with its use of strict `in_array()` [did not respect the key juggling PHP does](https://3v4l.org/FdReu), while [the code for the `assertArrayIs*ToArrayIgnoringListOfKeys` assertions did](https://3v4l.org/AfHoc) (as `unset()` - and `isset()` for that matter - will do the same type juggling for the array keys).

This commit adjusts the code for the `assertArrayIs*ToArrayOnlyConsideringListOfKeys()` assertions to handle arrays keys passed in `$keysToBeConsidered` consistently in the same way PHP itself would do.

Includes tests.
Includes tests for the same for the `assertArrayIs*ToArrayIgnoringListOfKeys` assertions which were not affected by this bug.
  • Loading branch information
jrfnl authored and sebastianbergmann committed Feb 29, 2024
1 parent 8bcc515 commit 7ec4599
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 16 deletions.
38 changes: 22 additions & 16 deletions src/Framework/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
*/
namespace PHPUnit\Framework;

use function array_keys;
use function class_exists;
use function count;
use function file_get_contents;
use function in_array;
use function interface_exists;
use function is_bool;
use ArrayAccess;
Expand Down Expand Up @@ -84,19 +82,23 @@ abstract class Assert
*/
final public static function assertArrayIsEqualToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void
{
foreach (array_keys($expected) as $key) {
if (!in_array($key, $keysToBeConsidered, true)) {
unset($expected[$key]);
$filteredExpected = [];

foreach ($keysToBeConsidered as $key) {
if (isset($expected[$key])) {
$filteredExpected[$key] = $expected[$key];
}
}

foreach (array_keys($actual) as $key) {
if (!in_array($key, $keysToBeConsidered, true)) {
unset($actual[$key]);
$filteredActual = [];

foreach ($keysToBeConsidered as $key) {
if (isset($actual[$key])) {
$filteredActual[$key] = $actual[$key];
}
}

static::assertEquals($expected, $actual, $message);
static::assertEquals($filteredExpected, $filteredActual, $message);
}

/**
Expand Down Expand Up @@ -126,19 +128,23 @@ final public static function assertArrayIsEqualToArrayIgnoringListOfKeys(array $
*/
final public static function assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void
{
foreach (array_keys($expected) as $key) {
if (!in_array($key, $keysToBeConsidered, true)) {
unset($expected[$key]);
$filteredExpected = [];

foreach ($keysToBeConsidered as $key) {
if (isset($expected[$key])) {
$filteredExpected[$key] = $expected[$key];
}
}

foreach (array_keys($actual) as $key) {
if (!in_array($key, $keysToBeConsidered, true)) {
unset($actual[$key]);
$filteredActual = [];

foreach ($keysToBeConsidered as $key) {
if (isset($actual[$key])) {
$filteredActual[$key] = $actual[$key];
}
}

static::assertSame($expected, $actual, $message);
static::assertSame($filteredExpected, $filteredActual, $message);
}

/**
Expand Down
52 changes: 52 additions & 0 deletions tests/unit/Framework/AssertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,58 @@ public function testAssertArrayIsIdenticalToArrayIgnoringListOfKeys(): void
$this->assertArrayIsIdenticalToArrayIgnoringListOfKeys($expected, $actual, ['b']);
}

public function testAssertArrayIsEqualToArrayOnlyConsideringListOfKeysInterpretsKeysSameAsPHP(): void
{
// Effective keys: int 0, int 1, int 2, string '3.0'.
$expected = [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4];
$actual = [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4];

$this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys($expected, $actual, [0, '1', '3.0']);

$this->expectException(AssertionFailedError::class);

$this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys($expected, $actual, ['1', 2.0, '3.0']);
}

public function testAssertArrayIsEqualToArrayIgnoringListOfKeysInterpretsKeysSameAsPHP(): void
{
// Effective keys: int 0, int 1, int 2, string '3.0'.
$expected = [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4];
$actual = [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4];

$this->assertArrayIsEqualToArrayIgnoringListOfKeys($expected, $actual, [2.0]);

$this->expectException(AssertionFailedError::class);

$this->assertArrayIsEqualToArrayIgnoringListOfKeys($expected, $actual, ['1']);
}

public function testAssertArrayIsIdenticalToArrayOnlyConsideringListOfKeysInterpretsKeysSameAsPHP(): void
{
// Effective keys: int 0, int 1, int 2, string '3.0'.
$expected = [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4];
$actual = [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4];

$this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys($expected, $actual, [0, '1', '3.0']);

$this->expectException(AssertionFailedError::class);

$this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys($expected, $actual, ['1', 2.0, '3.0']);
}

public function testAssertArrayIsIdenticalToArrayIgnoringListOfKeysInterpretsKeysSameAsPHP(): void
{
// Effective keys: int 0, int 1, int 2, string '3.0'.
$expected = [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4];
$actual = [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4];

$this->assertArrayIsIdenticalToArrayIgnoringListOfKeys($expected, $actual, [2.0]);

$this->expectException(AssertionFailedError::class);

$this->assertArrayIsIdenticalToArrayIgnoringListOfKeys($expected, $actual, ['1']);
}

public function testAssertArrayHasIntegerKey(): void
{
$this->assertArrayHasKey(0, ['foo']);
Expand Down

0 comments on commit 7ec4599

Please sign in to comment.