Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support indexed arrays in ArraySubset #3161

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
93 changes: 85 additions & 8 deletions src/Framework/Constraint/ArraySubset.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,21 @@ public function evaluate($other, $description = '', $returnResult = false)
$other = $this->toArray($other);
$this->subset = $this->toArray($this->subset);

$patched = \array_replace_recursive($other, $this->subset);
$intersect = $this->arrayIntersectRecursive($other, $this->subset);
$this->deepSort($intersect);
$this->deepSort($this->subset);

if ($this->strict) {
$result = $other === $patched;
} else {
$result = $other == $patched;
}
$result = $this->compare($intersect, $this->subset);

if ($returnResult) {
return $result;
}

if (!$result) {
$f = new ComparisonFailure(
$patched,
$this->subset,
$other,
\print_r($patched, true),
\print_r($this->subset, true),
\print_r($other, true)
);

Expand Down Expand Up @@ -130,4 +128,83 @@ private function toArray(iterable $other): array
// Keep BC even if we know that array would not be the expected one
return (array) $other;
}

private function isAssociative(array $array): bool
{
return \array_reduce(\array_keys($array), function (bool $carry, $key): bool {
return $carry || \is_string($key);
}, false);
}

private function compare($first, $second): bool
{
return $this->strict ? $first === $second : $first == $second;
}

private function deepSort(array &$array): void
{
foreach ($array as &$value) {
if (\is_array($value)) {
$this->deepSort($value);
}
}

if ($this->isAssociative($array)) {
\ksort($array);
Copy link

@chx chx Jun 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird. Are you sure this shouldn't be asort? The sort just below sorts by value and this sorts by key. asort would be more analogous.

Copy link
Contributor Author

@pfrenssen pfrenssen Jun 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to be able to compare the two arrays, and so both ksort() and asort() do the trick for this use case. You do have a point that it would be more consistent using asort().

Copy link

@chx chx Jun 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, same arrays sort to the same regardless you sort them by keys or values but for numeric arrays you wanted [1,2] and [2,1] to be the same.

} else {
\sort($array);
}
}

private function arrayIntersectRecursive(array $array, array $subset): array
{
$intersect = [];

if ($this->isAssociative($subset)) {
// If the subset is an associative array, get the intersection while
// preserving the keys.
foreach ($subset as $key => $subset_value) {
if (\array_key_exists($key, $array)) {
$array_value = $array[$key];

if (\is_array($subset_value) && \is_array($array_value)) {
$intersect[$key] = $this->arrayIntersectRecursive($array_value, $subset_value);
} elseif ($this->compare($subset_value, $array_value)) {
$intersect[$key] = $array_value;
}
}
}
} else {
// If the subset is an indexed array, loop over all entries in the
// haystack and check if they match the ones in the subset.
foreach ($array as $array_value) {
if (\is_array($array_value)) {
foreach ($subset as $key => $subset_value) {
if (\is_array($subset_value)) {
$recursed = $this->arrayIntersectRecursive($array_value, $subset_value);

if (!empty($recursed)) {
$intersect[$key] = $recursed;

break;
}
}
}
} else {
foreach ($subset as $key => $subset_value) {
if (!\is_array($subset_value) && $this->compare(
$subset_value,
$array_value
)) {
$intersect[$key] = $array_value;

break;
}
}
}
}
}

return $intersect;
}
}
37 changes: 37 additions & 0 deletions tests/Framework/AssertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,17 @@ public function testAssertArraySubset(): void
'd' => ['a2' => ['a3' => 'item a3', 'b3' => 'item b3']]
];

$this->assertArraySubset(['a' => 'item a'], $array);
$this->assertArraySubset(['a' => 'item a', 'c' => ['a2' => 'item a2']], $array);
$this->assertArraySubset(['a' => 'item a', 'd' => ['a2' => ['b3' => 'item b3']]], $array);
$this->assertArraySubset(['b' => 'item b', 'd' => ['a2' => ['b3' => 'item b3']]], $array);

$arrayAccessData = new \ArrayObject($array);

$this->assertArraySubset(['a' => 'item a'], $arrayAccessData);
$this->assertArraySubset(['a' => 'item a', 'c' => ['a2' => 'item a2']], $arrayAccessData);
$this->assertArraySubset(['a' => 'item a', 'd' => ['a2' => ['b3' => 'item b3']]], $arrayAccessData);
$this->assertArraySubset(['b' => 'item b', 'd' => ['a2' => ['b3' => 'item b3']]], $arrayAccessData);

try {
$this->assertArraySubset(['a' => 'bad value'], $array);
Expand All @@ -181,6 +185,39 @@ public function testAssertArraySubset(): void
$this->fail();
}

public function testAssertArraySubsetWithIndexedArrays(): void
{
$array = [
'item a',
'item b',
['a2' => 'item a2', 'b2' => 'item b2'],
['a2' => ['a3' => 'item a3', 'b3' => 'item b3']]
];

$this->assertArraySubset(['item a', ['a2' => 'item a2']], $array);
$this->assertArraySubset(['item a', ['a2' => ['b3' => 'item b3']]], $array);
$this->assertArraySubset(['item b', ['a2' => ['b3' => 'item b3']]], $array);

$arrayAccessData = new \ArrayObject($array);

$this->assertArraySubset(['item a', ['a2' => 'item a2']], $arrayAccessData);
$this->assertArraySubset(['item a', ['a2' => ['b3' => 'item b3']]], $arrayAccessData);
$this->assertArraySubset(['item b', ['a2' => ['b3' => 'item b3']]], $arrayAccessData);

try {
$this->assertArraySubset(['bad value'], $array);
} catch (AssertionFailedError $e) {
}

try {
$this->assertArraySubset([['a2' => ['bad index' => 'item b3']]], $array);
} catch (AssertionFailedError $e) {
return;
}

$this->fail();
}

public function testAssertArraySubsetWithDeepNestedArrays(): void
{
$array = [
Expand Down
124 changes: 120 additions & 4 deletions tests/Framework/Constraint/ArraySubsetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,146 @@ class ArraySubsetTest extends ConstraintTestCase
public static function evaluateDataProvider()
{
return [
'loose array subset and array other' => [
'loose associative array subset and array other' => [
'expected' => true,
'subset' => ['bar' => 0],
'other' => ['foo' => '', 'bar' => '0'],
'strict' => false
],
'strict array subset and array other' => [
'strict associative array subset and array other' => [
'expected' => false,
'subset' => ['bar' => 0],
'other' => ['foo' => '', 'bar' => '0'],
'strict' => true
],
'loose array subset and ArrayObject other' => [
'loose associative array subset and ArrayObject other' => [
'expected' => true,
'subset' => ['bar' => 0],
'other' => new \ArrayObject(['foo' => '', 'bar' => '0']),
'strict' => false
],
'strict ArrayObject subset and array other' => [
'strict associative ArrayObject subset and array other' => [
'expected' => true,
'subset' => new \ArrayObject(['bar' => 0]),
'other' => ['foo' => '', 'bar' => 0],
'strict' => true
],
'loose indexed array subset and array other' => [
'expected' => true,
'subset' => [0],
'other' => ['', '0'],
'strict' => false
],
'strict indexed array subset and array other' => [
'expected' => false,
'subset' => [0],
'other' => ['', '0'],
'strict' => true
],
'loose indexed array subset and ArrayObject other' => [
'expected' => true,
'subset' => [0],
'other' => new \ArrayObject(['', '0']),
'strict' => false
],
'strict indexed ArrayObject subset and array other' => [
'expected' => true,
'subset' => new \ArrayObject([0]),
'other' => ['', 0],
'strict' => true
],
'loose unordered indexed array subset and array other' => [
'expected' => true,
'subset' => [0, '1'],
'other' => ['1', '2', '0'],
'strict' => false
],
'strict unordered indexed array subset and array other' => [
'expected' => false,
'subset' => [0, '1'],
'other' => ['1', '2', '0'],
'strict' => true
],
'loose unordered indexed array subset and ArrayObject other' => [
'expected' => true,
'subset' => [0, '1'],
'other' => new \ArrayObject(['1', '2', '0']),
'strict' => false
],
'strict unordered indexed ArrayObject subset and array other' => [
'expected' => true,
'subset' => new \ArrayObject([0, '1']),
'other' => ['1', '2', 0],
'strict' => true
],
'loose unordered multidimensional indexed array subset and array other' => [
'expected' => true,
'subset' => [
[[3, 4], 2],
'10',
],
'other' => [
0 => '1',
'a' => [
'aa' => '2',
'ab' => [5, 4, 3],
'ac' => 10,
],
'b' => '10',
],
'strict' => false
],
'strict unordered multidimensional indexed array subset and array other' => [
'expected' => false,
'subset' => [
[[3, 4], 2],
'10',
],
'other' => [
0 => '1',
'a' => [
'aa' => '2',
'ab' => [5, 4, 3],
'ac' => 10,
],
'b' => '10',
],
'strict' => true
],
'loose unordered multidimensional indexed array subset and ArrayObject other' => [
'expected' => true,
'subset' => [
[[3, 4], 2],
'10',
],
'other' => new \ArrayObject([
0 => '1',
'a' => [
'aa' => '2',
'ab' => [5, 4, 3],
'ac' => 10,
],
'b' => '10',
]),
'strict' => false
],
'strict unordered multidimensional indexed ArrayObject subset and array other' => [
'expected' => true,
'subset' => new \ArrayObject([
[[3, 4], '2'],
'10',
]),
'other' => [
0 => '1',
'a' => [
'aa' => '2',
'ab' => [5, 4, 3],
'ac' => 10,
],
'b' => '10',
],
'strict' => true
],
];
}

Expand Down