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
Serialization of 'Closure' is not allowed when using @runInSeparateProcess #4371
Comments
Thank you for your report. Please provide a minimal, self-contained, reproducing test case that shows the problem you are reporting. Without such a minimal, self-contained, reproducing test case I will not be able to investigate this issue. |
@sebastianbergmann Please read the last paragraph:
|
I need to check the data that are being serialized in order to provide a reproducer. Without knowing where the serialization happens I'm unable to do so. |
I have the same problem. And I also don't understand what causes it, what data are serialized and where the serialization happens. |
This appears to happen when an assertion called within a Closure fails. Here's an example that reproduces this: /**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testSomething(): void
{
$mock = $this->createMock(\DateTime::class);
$mock->expects($this->once())
->method('format')
->with($this->callback(function ($arg1): bool {
$this->assertSame('foo', $arg1);
return true;
}));
$mock->format('bar');
} When run as-is, this produces the following fatal error:
If you change the the argument value for
|
Hello, i can confirm it has something to do with
I would extend it to 'when assertion is called outside of the test function. For example: <?php declare(strict_types=1);
class ExampleTest extends TestCase
{
/**
* @runInSeparateProcess
*/
public function testLanguage(): void
{
$languageSwitch = Something::something();
$this->switchLocale($languageSwitch, "en");
// ... test something with english ...
$this->switchLocale($languageSwitch, "de");
// ... test something with german ...
}
private function switchLocale($languageSwitch, $language)
{
$languageSwitch->setLanguage($language);
// This next assert is throwing the error
// if you comment it, it works fine
// Edit: only if the assertion fails.
$this->assertSame(
$language,
$languageSwitch->getLanguage()
);
}
} |
Do not call assertions in closures then, sorry. |
This has been occasionally plaguing me for a few months now so I spent a few hours to dig deeper.
While I understand the sentiment, that scope for the bug is a bit too small, as illustrated by Zrniks's example and my own tests. In my investigation I was able to see a difference between I was able to figure out that the closure serialisation occurred in the By modifying That is an exception from the test case. Looking further I found that it's backtrace contained a reference to a different error handler that contained itself an error with a stacktrace of a function stack call that contained a closure. So the error occurs "when any part of your application that produces an error contains a function in the stacktrace that has a closure as one of its arguments". In my specific case the serialization error was caused in an error with a callstack that contained a call to Although not directly useful, to illustrate the difficulty of debugging this, the full path from TestResult to offending array entry was as follows: |
This commit changes some loops into built-in PHP functions to simplify the code and eliminate the need for closures. Additionally this switches over to an `assertEmpty` from `assertEquals`. This achieves the same test result and in the message still provides the errors. However it prevents test failures with weird serialization complaints that are caused by closures in function arguments of GraphQL Error stacktraces. See sebastianbergmann/phpunit#4371 (comment) The downside to this change is that we may lose some contextual information that the full error in the array comparison may provide. The upside is that we can actually see why tests fail since in a lot of cases this beneficial diff doesn't make it into the test output anyway. So all-in-all this is a step forward.
This commit changes some loops into built-in PHP functions to simplify the code and eliminate the need for closures. Additionally this switches over to an `assertEmpty` from `assertEquals`. This achieves the same test result and in the message still provides the errors. However it prevents test failures with weird serialization complaints that are caused by closures in function arguments of GraphQL Error stacktraces. See sebastianbergmann/phpunit#4371 (comment) The downside to this change is that we may lose some contextual information that the full error in the array comparison may provide. The upside is that we can actually see why tests fail since in a lot of cases this beneficial diff doesn't make it into the test output anyway. So all-in-all this is a step forward.
This commit changes some loops into built-in PHP functions to simplify the code and eliminate the need for closures. Additionally this switches over to an `assertEmpty` from `assertEquals`. This achieves the same test result and in the message still provides the errors. However it prevents test failures with weird serialization complaints that are caused by closures in function arguments of GraphQL Error stacktraces. See sebastianbergmann/phpunit#4371 (comment) The downside to this change is that we may lose some contextual information that the full error in the array comparison may provide. The upside is that we can actually see why tests fail since in a lot of cases this beneficial diff doesn't make it into the test output anyway. So all-in-all this is a step forward.
…ization (#1159) This commit changes some loops into built-in PHP functions to simplify the code and eliminate the need for closures. Additionally this switches over to an `assertEmpty` from `assertEquals`. This achieves the same test result and in the message still provides the errors. However it prevents test failures with weird serialization complaints that are caused by closures in function arguments of GraphQL Error stacktraces. See sebastianbergmann/phpunit#4371 (comment) The downside to this change is that we may lose some contextual information that the full error in the array comparison may provide. The upside is that we can actually see why tests fail since in a lot of cases this beneficial diff doesn't make it into the test output anyway. So all-in-all this is a step forward.
I'm going to add a little more context, as I've just run across the same issue, and have no idea how to change my test to work. The basic gist of it is: /**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider somethingProvidingAStringMessage
*/
public function testMockingWithConstraints(string $message): void
{
$mock = $this->createMock(AClassDefiningWriteLine::class);
$mock
->expects($this->once())
->method('writeLine')
->with($this->stringContains($message));
$mock->writeLine('<info>' . $message . '</info>');
} The problem with this statement, @sebastianbergmann :
is that the above example demonstrates a documented usage of mock objects, namely, using one of the various constraint methods to provide an assertion to the mock object. But it does not work in this scenario, due to the serialization issues. I think the issue is valid and should be re-opened. |
Another interesting one: if you use $mock
->expects($this->exactly(2))
->method('writeLine')
->withConsecutive(
['First message'],
['SECOND message'],
);
$mock->writeLine('First message');
$mock->writeLine('Second message'); // This line triggers the serialization error |
I wrote the patch: mpyw/phpunit-patch-serializable-comparison |
Can confirm, works! Very nice, thank you! |
Awesome that works for me ! Thanks ! |
The issue I had was also fixed by this patch. Although, i had different case. One more case and scenario that i want to share, happening on The test case itself didn't contain any closures. // \Monolog\Logger , v1.26.1 (2021-05-28)
$loggerMock = $this->getMockBuilder(Logger::class)
->disableOriginalConstructor()
->getMock();
$loggerMock->expects($this->once())
->method('error')
->with(
'Failed myControllerActionMethod: error or rejection',
[
'item_ids' => [ 111, 222 ], // <-- this was the difference. The "actual" value was 'item_ids' => []
'error_code' => 'get_payment_pay_process',
'customer_id' => '1',
]
); . $loggerMock = $this->getMockBuilder(Logger::class)
->disableOriginalConstructor()
->getMock();
$loggerMock->expects($this->once())
->method('error')
->with(
'Failed myControllerActionMethod: error or rejection',
$this->callback(function($args) {
if ($args['item_ids'] !== [ 111, 222 ]) {
return false;
}
if ($args['error_code'] !== 'get_payment_pay_process') {
return false;
}
if ($args['customer_id'] !== '1') {
return false;
}
return true;
]
); |
…ization (drupal-graphql#1159) This commit changes some loops into built-in PHP functions to simplify the code and eliminate the need for closures. Additionally this switches over to an `assertEmpty` from `assertEquals`. This achieves the same test result and in the message still provides the errors. However it prevents test failures with weird serialization complaints that are caused by closures in function arguments of GraphQL Error stacktraces. See sebastianbergmann/phpunit#4371 (comment) The downside to this change is that we may lose some contextual information that the full error in the array comparison may provide. The upside is that we can actually see why tests fail since in a lot of cases this beneficial diff doesn't make it into the test output anyway. So all-in-all this is a step forward.
Note that some examples do not explicitly use closures. I think those are connected to exception backtraces. See mockery/mockery#1038 (comment) I wonder if phpunit could handle this in default configuration file. <ini name="zend.exception_ignore_args" value="On"/> |
…ization (#1159) This commit changes some loops into built-in PHP functions to simplify the code and eliminate the need for closures. Additionally this switches over to an `assertEmpty` from `assertEquals`. This achieves the same test result and in the message still provides the errors. However it prevents test failures with weird serialization complaints that are caused by closures in function arguments of GraphQL Error stacktraces. See sebastianbergmann/phpunit#4371 (comment) The downside to this change is that we may lose some contextual information that the full error in the array comparison may provide. The upside is that we can actually see why tests fail since in a lot of cases this beneficial diff doesn't make it into the test output anyway. So all-in-all this is a step forward.
Summary
One of our tests started failing with
Serialization of 'Closure' is not allowed
. When I removed the@runInSeparateProcess
(which I have to keep as workaround for https://bugs.php.net/bug.php?id=79646) I got an entirely different error which helped me find the problem in the test.Apparently there is some issue with process isolation - the data that are serialized to be sent back to the original process contain a Closure and the serialization fails as a result.
Note: This has nothing to do with #2739 - that's about sending data into the test, this is about getting data back.
I'm not sure what data are serialized and where the serialization happens so I'm unable to provide more details and a reproducer for now. Can you point me where in PHPUnit's source code this serialization happens? (Using -v and --debug didn't get me a stack trace.)
The text was updated successfully, but these errors were encountered: