问题描述
我正在实现一个模块,该模块将提供用于处理和管理PHP会话的API.我正在测试Session\Manager
实现,该实现将允许用户启动会话,设置ID,获取ID,销毁会话等.我正在使用PHPUnit的@runInSeparateProcess
批注在单独的过程中测试此类中的方法.使用此批注时,由于未序列化错误,PHPUnit引发了异常.当我不使用注释时,测试将按预期运行,并且在不等于false的null上失败.
I am implementing a module that will provide an API to work with and manage PHP sessions. 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. When I use this annotation I get an exception thrown by PHPUnit due to an unserialization error. When I do not use the annotation the test runs as expected and fails on null not being equal to 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());
}
}
我能够将问题追溯到以下 PHPUnit_Util_PHP::runJob()
方法.我正在运行PHPUnit 3.7.5,正在调用的runJob
方法是:
I was able to trace the problem down to the following PHPUnit_Util_PHP::runJob()
method. I am running PHPUnit 3.7.5 and the runJob
method being invoked is:
/**
* 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);
}
}
行$stdout = stream_get_contents($pipes[1]);
导致$stdout
等于?
的长字符串.在$this->processChildResult
中,$stdout
中的值未序列化,并且传递给该函数的无效值会触发警告,从而引发异常.我还能够确定$this->getPhpBinary()
的返回值是/usr/bin/php
.
The line $stdout = stream_get_contents($pipes[1]);
results in $stdout
being equal to a long string of ?
. 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. I've also been able to determine that the return value of $this->getPhpBinary()
is /usr/bin/php
.
抛出异常消息:
PHPUnit_Framework_Exception: ???...???"*???...??
Caused by
ErrorException: unserialize(): Error at offset 0 of 10081 bytes
由于hek2mgl,可以查看$job
中的PHP代码在此要点上,将$工作.我创建了一个链接,因为它包含大量代码,并且这个问题已经很长了.
Thanks to hek2mgl the PHP code in $job
can be reviewed at this gist holding the output of a var_dump on $job. I created a link as it is a fair amount of code and this question is already quite long.
对于这个特定领域,我的知识已经到了尽头,并且不确定如何进一步调试此问题.我不确定为什么@runInSeparateProcess
失败以及为什么$stdout
从运行单独的进程中导致的字符串很长?分数.是什么可能导致此问题,我该如何解决呢?我对这个模块感到停滞不前,因为将来的测试将需要在单独的过程中运行,以确保开始和销毁会话不会影响测试.
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 ? 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
- PHPUnit:3.7.5
- 在IDE和命令行测试运行程序中引起的错误
推荐答案
好吧,我知道这里发生了什么,而孩子是个傻瓜.
Ok, I figured out what is going on here and boy is it a doozy.
在测试框架期间,尝试保留全局状态.在PHPUnit_Framework_TestCase::run()
函数中,当前的全局变量由PHPUnit_Util_GlobalState::getGlobalsAsString()
转换为PHP代码字符串.我目前正在使用自动加载库来照顾需要适当的类文件.此类在全局范围内的变量中声明,导致该对象的序列化被放入由getGlobalsAsString()
创建的$GLOBALS
数组中.无论出于何种原因,此序列化都包含空字节字符\u0000
.当您尝试对此进行反序列化时,会出现错误,并最终导致输出也被破坏.
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()
. 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()
. This serialization, for whatever reason, includes the null byte character \u0000
. When you attempt to unserialize this you get an error and is ultimately causing the output to be corrupted as well.
我通过在测试引导程序中创建一个在全局范围之外创建自动加载对象的函数来解决此问题.现在,脚本不再尝试反序列化一个空字节,该脚本不会产生任何错误,并且测试用例可以按预期工作.这是一个非常时髦的问题,不确定是我的代码,PHPUnit的代码错误还是serialize
函数的内部错误.
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.
或者,您可以在测试用例中覆盖run()
函数,并显式将测试用例设置为不保留全局状态.这可能是一种更清洁的解决方案,因为您可以很容易地遇到其他问题,因为默认行为是再次要求所有包含的文件.
Alternatively you can override the run()
function in your test case and explicitly set the test case to not preserve global state. 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);
}
}
必须在调用PHPUnit_Framework_TestCase::run()
函数之前设置全局状态.这可能是可靠地正确设置全局状态保留的唯一方法.
Global state must be set before the PHPUnit_Framework_TestCase::run()
function is invoked. This is likely the only way to reliably set the global state preservation properly.
这篇关于@runInSeparateProcess时反序列化错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!