Phpunit: Serialization of 'Closure' is not allowed when using @runInSeparateProcess

Created on 9 Jul 2020  路  7Comments  路  Source: sebastianbergmann/phpunit

| Q | A
| --------------------| ---------------
| PHPUnit version | 9.2.5
| PHP version | 7.4.7
| Installation Method | Composer

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 https://github.com/sebastianbergmann/phpunit/issues/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.)

typbug

Most helpful comment

Do not call assertions in closures then, sorry.

All 7 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'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.)

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:

PHP Fatal error:  Uncaught Exception: Serialization of 'Closure' is not allowed in Standard input code:89
Stack trace:
#0 Standard input code(89): serialize(Array)
#1 Standard input code(122): __phpunit_run_isolated_test()
#2 {main}
  thrown in Standard input code on line 89
Fatal error: Uncaught Exception: Serialization of 'Closure' is not allowed in Standard input code:89
Stack trace:
#0 Standard input code(89): serialize(Array)
#1 Standard input code(122): __phpunit_run_isolated_test()
#2 {main}
  thrown in Standard input code on line 89

If you change the the argument value for $mock->format() to 'foo', then it passes normally. If you leave it as 'bar' but remove the annotations, you see:

Expectation failed for method name is "format" when invoked 1 time(s)
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'foo'
+'bar'

Hello,

i can confirm it has something to do with

This appears to happen when an assertion called within a Closure fails.

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.

Was this page helpful?
0 / 5 - 0 ratings