简体   繁体   English

@runInSeparateProcess 时反序列化错误

[英]Unserialize error when @runInSeparateProcess

I am implementing a module that will provide an API to work with and manage PHP sessions.我正在实现一个模块,它将提供一个 API 来使用和管理 PHP 会话。 I am testing the Session\\Manager implementation that will allow users to start sessions, set IDs, get IDs, destroy sessions, etc. I am testing the methods in this class in a separate process, by using PHPUnit's @runInSeparateProcess annotation.我正在测试Session\\Manager实现,它将允许用户启动会话、设置 ID、获取 ID、销毁会话等。我正在使用 PHPUnit 的@runInSeparateProcess注释在单独的进程中测试此类中的方法。 When I use this annotation I get an exception thrown by PHPUnit due to an unserialization error.当我使用此注释时,由于反序列化错误,我收到 PHPUnit 抛出的异常。 When I do not use the annotation the test runs as expected and fails on null not being equal to false.当我不使用注释时,测试按预期运行,并且在 null 不等于 false 时失败。

Here's the test causing the error.这是导致错误的测试。 So far no implementation details have been made, all methods for the interface exist but perform no operations.到目前为止还没有实现细节,接口的所有方法都存在但不执行任何操作。

class ManagerTest extends PHPUnitTestCase {

    /**
     * Ensures that if the session has not been started yet the sessionExists
     * method returns false.
     *
     * @runInSeparateProcess
     */
    public function testSessionExistsWhenSessionHasNotBeenStarted() {
        $Manager = new \Session\Manager();
        $this->assertFalse($Manager->sessionExists());
    }

}

I was able to trace the problem down to the following PHPUnit_Util_PHP::runJob() method.我能够将问题追溯到以下PHPUnit_Util_PHP::runJob()方法。 I am running PHPUnit 3.7.5 and the runJob method being invoked is:我正在运行 PHPUnit 3.7.5,正在调用的runJob方法是:

/**
 * Runs a single job (PHP code) using a separate PHP process.
 *
 * @param  string                       $job
 * @param  PHPUnit_Framework_TestCase   $test
 * @param  PHPUnit_Framework_TestResult $result
 * @return array|null
 * @throws PHPUnit_Framework_Exception
 */
public function runJob($job, PHPUnit_Framework_Test $test = NULL, PHPUnit_Framework_TestResult $result = NULL)
{
    $process = proc_open(
      $this->getPhpBinary(),
      array(
        0 => array('pipe', 'r'),
        1 => array('pipe', 'w'),
        2 => array('pipe', 'w')
      ),
      $pipes
    );

    if (!is_resource($process)) {
        throw new PHPUnit_Framework_Exception(
          'Unable to create process for process isolation.'
        );
    }

    if ($result !== NULL) {
        $result->startTest($test);
    }

    $this->process($pipes[0], $job);
    fclose($pipes[0]);

    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);

    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);

    proc_close($process);
    $this->cleanup();

    if ($result !== NULL) {
        $this->processChildResult($test, $result, $stdout, $stderr);
    } else {
        return array('stdout' => $stdout, 'stderr' => $stderr);
    }
}

The line $stdout = stream_get_contents($pipes[1]);$stdout = stream_get_contents($pipes[1]); results in $stdout being equal to a long string of ?导致$stdout等于一长串? . . In $this->processChildResult the value in $stdout is unserialized and the invalid value passed to this function triggers a warning, resulting in an exception being thrown.$this->processChildResult中, $stdout的值是未序列化的,传递给此函数的无效值会触发警告,导致抛出异常。 I've also been able to determine that the return value of $this->getPhpBinary() is /usr/bin/php .我还能够确定$this->getPhpBinary()的返回值是/usr/bin/php

Exception message thrown:抛出的异常消息:

PHPUnit_Framework_Exception: ???...???"*???...??


Caused by
ErrorException: unserialize(): Error at offset 0 of 10081 bytes

Thanks to hek2mgl the PHP code in $job can be reviewed at this gist holding the output of a var_dump on $job .感谢 hek2mgl, $job的 PHP 代码可以在这个 gist 中查看,其中包含 $job 上 var_dump 的输出 I created a link as it is a fair amount of code and this question is already quite long.我创建了一个链接,因为它是相当数量的代码,这个问题已经很长了。

I've reached the end of my knowledge for this particular domain and not sure how go about debugging this problem further.我对这个特定领域的知识已经到了尽头,不知道如何进一步调试这个问题。 I'm not sure why the @runInSeparateProcess is failing and why the $stdout from running a separate process results in a long string of ?我不确定为什么@runInSeparateProcess失败,以及为什么运行单独进程的$stdout导致一长串 ? marks.分数。 What might be causing this issue and how could I go about resolving it?什么可能导致此问题,我该如何解决? I'm at a standstill for this module as future tests will require running in a separate process to ensure sessions being started and destroyed don't impact tests.我对这个模块处于停滞状态,因为未来的测试将需要在单独的进程中运行,以确保启动和销毁的会话不会影响测试。

  • PHP: 5.3.15 PHP:5.3.15
  • PHPUnit: 3.7.5 PHP单位:3.7.5
  • Error caused in IDE and command line test runners IDE 和命令行测试运行程序中引起的错误

Ok, I figured out what is going on here and boy is it a doozy.好吧,我想通了这里发生了什么,男孩是不是很糟糕。

During testing the framework attempts to preserve global state.在测试期间,框架尝试保留全局状态。 In the PHPUnit_Framework_TestCase::run() function the current globals are converted into a string of PHP code by PHPUnit_Util_GlobalState::getGlobalsAsString() .PHPUnit_Framework_TestCase::run()函数中,当前全局变量被PHPUnit_Util_GlobalState::getGlobalsAsString()转换为 PHP 代码字符串。 I am currently using an autoloading library to take care of requiring appropriate class files.我目前正在使用自动加载库来处理需要适当的类文件。 This class was declared in a variable that was in the global scope, causing a serialization of that object to be put into the $GLOBALS array created by getGlobalsAsString() .此类在全局范围内的变量中声明,导致该对象的序列化放入由getGlobalsAsString()创建的$GLOBALS数组中。 This serialization, for whatever reason, includes the null byte character \ .无论出于何种原因,此序列化包括空字节字符\ When you attempt to unserialize this you get an error and is ultimately causing the output to be corrupted as well.当您尝试对其进行反序列化时,您会收到错误并最终导致输出损坏。

I fixed this issue by create a function in my test bootstrap that creates the autoloading object outside of the global scope.我通过在我的测试引导程序中创建一个函数来解决这个问题,该函数在全局范围之外创建自动加载对象。 Now that the script is no longer attempting to unserialize a null byte the script produces no errors and the test case works as expected.现在脚本不再尝试反序列化空字节,脚本不会产生错误并且测试用例按预期工作。 This is a really funky issue, not sure if its an error in my code, PHPUnit's code or an internal error with the serialize function.这是一个非常时髦的问题,不确定是我的代码、PHPUnit 的代码中的错误还是serialize函数的内部错误。


Alternatively you can override the run() function in your test case and explicitly set the test case to not preserve global state.或者,您可以覆盖测试用例中的run()函数,并将测试用例显式设置为不保留全局状态。 This is probably a cleaner solution as you could easily run into other issues with requiring all included files again as is the default behavior.这可能是一个更简洁的解决方案,因为您很容易遇到其他问题,因为这是默认行为,再次需要所有包含的文件。

class ManagerTest extends PHPUnit_Framework_TestCase {

    public function run(PHPUnit_Framework_TestResult $result = null) {
        $this->setPreserveGlobalState(false);
        parent::run($result);
    }

}

Global state must be set before the PHPUnit_Framework_TestCase::run() function is invoked.全局状态必须在调用PHPUnit_Framework_TestCase::run()函数之前设置。 This is likely the only way to reliably set the global state preservation properly.这可能是正确设置全局状态保存的唯一方法。

The $job argument to PHPUnit_Util_PHP::runJob() is php code generated by PHPUnit that wraps the code of your test method in a way that it can be executed as a commandline script (clever trick!). PHPUnit_Util_PHP::runJob()的 $job 参数是 PHPUnit 生成的 php 代码,它以一种可以作为命令行脚本执行的方式包装测试方法的代码(聪明的技巧!)。 The isolated test will output serialized php data about test results back to phpunit.隔离测试会将有关测试结果的序列化 php 数据输出回 phpunit。

You can investigate this code if you would add the following line on top pf PHPUnit_Util_PHP::runJob()如果您要在PHPUnit_Util_PHP::runJob()顶部添加以下行,您可以调查此代码

file_put_contents('isolated.php', $job);

After executing the failed test again the file isolated.php should have been created in the current directory.再次执行失败的测试后,应该在当前目录中创建了文件isolated.php You can have a look at code can execute it (in the current folder) by typing您可以通过键入查看代码可以执行它(在当前文件夹中)

php isolated.php

Do you see any output that looks messy?你看到任何看起来很乱的输出吗?

Also you should debug the method PHPUnit_Util_PHP::processChildResult() .您还应该调试方法PHPUnit_Util_PHP::processChildResult() The method is responsible for deserializing and checking test results.该方法负责反序列化和检查测试结果。 Place some echos or var_dumps there or use a debugger.在那里放置一些 echo 或 var_dumps 或使用调试器。 When having a look at this method it is very likely that the problem is caused by illegal output from the test method.查看此方法时,问题很可能是由测试方法的非法输出引起的。

Please try set in php.ini:请尝试在 php.ini 中设置:

detect_unicode = Off detect_unicode = 关

Worked for me.为我工作。

也许当你的测试代码调用exit时会抛出同样的错误是不值得的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM