Обход подводных камней при наличии тестов, требующих php-cgi
PHPT (известный как PHP Testing Framework) — лёгкий фреймворк для тестирования, используемый для тестирования самого PHP, а также расширений PECL и PEAR (это de facto стандарт для тестирования PECL и PEAR). Одно из самых больших его достоинств — простота написания тестов (пример истории успеха).
Иногда в проекте используется несколько фреймворков для тестирования (например, PHPUnit для модульного тестирования и PHPT для (быстрого) тестирования регрессий); в этом случае бывает удобным настроить запуск всех тестов из одного места, а ещё лучше — полную интеграцию тестов.
В PHPUnit есть два полезных класса: PHPUnit_Extensions_PhptTestSuite
и PHPUnit_Extensions_PhptTestCase
. Первый предназначен для создания набора тестов (test suite) из файлов *.phpt
заданного каталога. Второй класс является обёрткой над тестом PHPT.
В большинстве случаев интеграция будет заключаться в написании класса, подобного данному:
{
public static function suite()
{
return new PHPUnit_Extensions_PhptTestSuite('/path/to/tests');
}
}
и заданию набора тестов в файле конфигурации:
<file>path/to/PHPTTestSuite.php</file>
</testsuite>
Или вообще такой конструкцией (не требует дополнительного класса):
<directory suffix=".phpt">path/to/tests</directory>
</testsuite>
И в большинстве случаев это будет работать.
Тем не менее, есть один большой подводный камень — если тестам PHPT требуется CGI, то без танцев с бубном не обойтись.
Приведём пример:
PHPUnit + CGI: FAIL
--GET--
a=duniyā
--FILE--
<?php echo "Hailō ", $_GET['a'], PHP_EOL; ?>
--EXPECT--
Hailō duniyā
<testsuites>
<testsuite name="PHPT">
<directory suffix=".phpt">./</directory>
</testsuite>
</testsuites>
</phpunit>
Запускаем: phpunit -c phpunit.xml
Получаем:
Configuration read from /home/vladimir/cgitest/phpunit.xml
F
Time: 1 second, Memory: 3.75Mb
There was 1 failure:
1) /home/vladimir/cgitest.phpt
--- Expected
+++ Actual
@@ @@
-Hailō duniyā
+
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
Изучение внутренностей PHPUnit показало, что проблема (скорее всего) лежит где-то внутри PEAR_RunTest
:
ob_start();
system($cmd, $return_value);
$out = ob_get_contents();
ob_end_clean();
$section_text['RETURNS'] = (int) trim($section_text['RETURNS']);
$returnfail = ($return_value != $section_text['RETURNS']);
} else {
echo $cmd, "\n";
$returnfail = false;
$stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null;
$out = $this->system_with_timeout($cmd, $env, $stdin);
print_r($out);
$return_value = $out[0];
$out = $out[1];
}
При запуске получим что-то такое:
php-cgi -d "include_path=.:/usr/share/php:/usr/share/pear" -d "coverage=" -d "output_handler=" -d "open_basedir=" -d "safe_mode=0" -d "disable_functions=" -d "output_buffering=Off" -d "display_errors=1" -d "log_errors=0" -d "html_errors=0" -d "track_errors=1" -d "report_memleaks=0" -d "report_zend_debug=0" -d "docref_root=" -d "docref_ext=.html" -d "error_prepend_string=" -d "error_append_string=" -d "auto_prepend_file=" -d "auto_append_file=" -d "magic_quotes_runtime=0" -d "xdebug.default_enable=0" -d "allow_url_fopen=1" -d "error_reporting=22527" -f '/home/vladimir/cgitest/test.php' 2>&1
Array
(
[0] => 127
[1] => sh: 1: php-cgi: not found
)
F
Видим, что php-cgi
где-то потерялся, хотя из командной строки его вызвать можно.
Насколько я могу судить, это происходит из-за того, что $_ENV['PATH']
не установлен, ибо $_ENV['PATH'] = $_SERVER['PATH'];
быстро всё решило.
UPDATE: проблема оказалась в настройках PHP: для того, чтобы всё работало, в variables_order
должно присутствовать E
(например, variables_order = EGPCS
) — причём для CLI, так как PHPUnit запускается из командной строки. Но указывать пользователю, что нужно изменить в php.ini — не comme il faut, по моему мнению.
Поэтому для исправления ситуации можно поступить так:
class PHPTTestSuite extends PHPUnit_Framework_TestCase
{
public static function suite()
{
if (empty($_ENV)) {
if (isset($_SERVER['PATH'])) {
$_ENV['PATH'] = $_SERVER['PATH'];
}
else {
$_ENV['PATH'] = getenv('PATH');
}
}
return new PHPUnit_Extensions_PhptTestSuite(__DIR__);
}
}
<testsuites>
<testsuite name="PHPT Test Suite">
<file>./PHPTTestSuite.php</file>
</testsuite>
</testsuites>
</phpunit>
После запуска получим
Configuration read from /home/vladimir/cgitest/phpunit.xml
.
Time: 0 seconds, Memory: 4.25Mb
OK (1 test, 1 assertion)
Но и это ещё не всё ©
Дело в том, что PEAR_RunTest
не полностью поддерживает спецификацию PHPT. В частности, php-cgi
будет использован только если в файле присутствует хотя бы одна из секций POST
, POST_RAW
, UPLOAD
, GET
, COOKIE
, EXPECTHEADERS
(UPLOAD
вообще нет в спецификации). Секции PUT
, GZIP_POST
, DEFLATE_POST
, CGI
не поддерживаются и тесты с такими секциями, скорее всего, работать не будут.
Простой пример:
Require CGI SAPI: FAIL
--CGI--
--FILE--
<?php echo PHP_SAPI, PHP_EOL; ?>
--EXPECTREGEX--
^cgi(.*+)$
Configuration read from /home/vladimir/cgitest/phpunit.xml
F.
Time: 1 second, Memory: 4.25Mb
There was 1 failure:
1) /home/vladimir/cgitest/cgi.phpt
--- Expected
+++ Actual
@@ @@
-^cgi(.*+)$
+cli
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
Поэтому в случае использования тестов с такими секциями, несовместимые тесты придётся пропустить:
{
public static function suite()
{
if (empty($_ENV)) {
if (isset($_SERVER['PATH'])) {
$_ENV['PATH'] = $_SERVER['PATH'];
}
else {
$_ENV['PATH'] = getenv('PATH');
}
}
$facade = new File_Iterator_Facade;
$files = $facade->getFilesAsArray(__DIR__, '.phpt');
$suite = new PHPUnit_Framework_TestSuite();
foreach ($files as $file) {
$c = file_get_contents($file);
if (!preg_match('/^--(?:PUT|(?:GZIP|DEFLATE)_POST|CGI)--$/m', $c)) {
$suite->addTestFile($file);
}
}
return $suite;
}
}
В идеале эта функциональность должна быть в PEAR_RunTest
, но пока её там нет, приходится обходиться подручными средствами.
© 2013 सत्यं वद धर्मं चर. Все права защищены. Перепубликация материалов без разрешения автора запрещена.
При использовании материалов блога наличие активной не закрытой от индексирования ссылки на источник обязательно.