mockery / mockery

Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL).
http://docs.mockery.io/en/stable/
BSD 3-Clause "New" or "Revised" License
10.63k stars 456 forks source link

IteratorAggregate regex doesn't match IteratorAggregate interface name #1134

Open Grendel7 opened 3 years ago

Grendel7 commented 3 years ago

Earlier today, I tried to write a test involving where I wanted to mock the MailboxInterface class from DDeBoer's IMAP library: https://github.com/ddeboer/imap/blob/master/src/MailboxInterface.php

I tried to mock this interface as you would mock anything else:

use Ddeboer\Imap\MailboxInterface;
\Mockery::mock(MailboxInterface::class)

However, this generated the following error message:

Declaration of Mockery_2_IteratorAggregate_Ddeboer_Imap_MailboxInterface::getIterator() must be compatible with Ddeboer\Imap\MailboxInterface::getIterator(): Ddeboer\Imap\MessageIteratorInterface                  

I did a lot of digging into the internals of Mockery to figure out what was happening, but I think I found the culprit.

The MailboxInterface class extends from both Countable and IteratorAggregate. But it extends the definition of IteratorAggregate::getIterator with a return type of MessageIteratorInterface. Mockery however seems to try to mock the method from IteratorAggregate, which of course doesn't match the one from MailboxInterface.

This is evidence by tracing the allMethods array in MockConfiguration, which as the very first entry contains the ReflectionMethod pointing to IteratorAggregate::getIterator. And this is (probably, I didn't check this) caused by IteratorAggregate being added to the targetInterfaces array.

This doesn't seem like the way it should work, and I think you'll agree with me, because this line would seem to be intended to filter out IteratorAggregate from the interface list:

https://github.com/mockery/mockery/blob/d1339f64479af1bee0e82a0413813fe5345a54ea/library/Mockery/Generator/MockConfiguration.php#L382

But this looks all OK, right? preg_match("/^\\?Iterator(|Aggregate)$/i", "IteratorAggregate") should match, right? Online regex testers like regex101.com all say this matches, so it does, doesn't it?

Except that it doesn't. That function returns 0. I tested various PHP versions (5.6, 7.0, 7.4, 8.0) and operating systems (Debian, Ubuntu, Alpine, CentOS) and they all return 0. Please do run php -r 'echo preg_match("/^\\?Iterator(|Aggregate)$/i", "IteratorAggregate");' on your platform of choice too to check it!

I'm utterly confused by what is happening here. Maybe I overlooked something important. But for some reason there is code in place to filter out IteratorAggregate and it doesn't work due to some very strange behavior in PCRE.

davedevelopment commented 3 years ago

Must be something to do with the slashes?

Maybe we could try

preg_match("/^Iterator(|Aggregate)$/i", ltrim($interface, "\\"))