Покрытие PHPUnit: допустимый объем памяти 536870912 байт исчерпан

Я пытаюсь сгенерировать покрытие тестового кода для моего проекта PHP с помощью PHPUnit и phpdbg, используя следующую команду:

phpdbg -dmemory_limit=512M -qrr ./bin/phpunit -c .phpunit.cover.xml

Это прекрасно работает:

PHPUnit 6.2.4 by Sebastian Bergmann and contributors.

........                                                            8 / 8 (100%)

Time: 114 ms, Memory: 14.00MB

OK (8 tests, 13 assertions)

Generating code coverage report in HTML format ... done

Однако, когда я использую ту же самую команду в контейнере Docker:

docker run -it --name YM4UPltmiPMjObaVULwsIPIkPL2bGL0T -e USER=sasan -v "/home/sasan/Project/phpredmin:/phpredmin" -w "/phpredmin" --user "1000:www-data" php:7.0-apache phpdbg -dmemory_limit=512M -qrr ./bin/phpunit -c .phpunit.cover.xml

Я получаю следующую ошибку:

PHPUnit 6.2.4 by Sebastian Bergmann and contributors.

[PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted (tried to allocate 561514763337856 bytes) in /phpredmin/vendor/phpunit/phpunit/src/Util/GlobalState.php on line 166]

Я не понимаю, почему PHPUnit должен выделять 561514763337856 байт памяти. Я подозреваю, что это застревает в цикле, но почему это не происходит вне контейнера? Вот моя версия PHP на моей машине:

PHP 7.0.22-0ubuntu0.17.04.1 (cli) (built: Aug  8 2017 22:03:30) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.22-0ubuntu0.17.04.1, Copyright (c) 1999-2017, by Zend Technologies

А вот и файл.phpunit.cover.xml:

<phpunit
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
        backupGlobals="false"
        backupStaticAttributes="false"
        bootstrap="vendor/autoload.php"
        cacheTokens="false"
        colors="false"
        convertErrorsToExceptions="true"
        convertNoticesToExceptions="true"
        convertWarningsToExceptions="true"
        processIsolation="false"
        stopOnError="true"
        stopOnFailure="true"
        stopOnIncomplete="false"
        stopOnSkipped="false"
        stopOnRisky="false"
        timeoutForSmallTests="1"
        timeoutForMediumTests="10"
        timeoutForLargeTests="60"
        verbose="false">
    <testsuites>
            <testsuite name="PhpRedmin PHP source">
            <directory>src-test/</directory>
            </testsuite>
    </testsuites>
    <logging>
        <log type="coverage-html" target="cover/" lowUpperBound="35" 
highLowerBound="70"/>
    </logging>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">src-test/</directory>
            <directory suffix=".php">src/</directory>
        </whitelist>
    </filter>
</phpunit>

- Edit1 -

Я обнаружил, что это как-то связано с @runInSeparateProcess. Когда я удаляю тест, который имеет @runInSeparateProcess, он начинает работать. Но все же я не знаю, в чем проблема

- Edit2 -

Также я обнаружил, что если я не смонтирую директорию с кодом в контейнере Docker, все работает нормально

3 ответа

Добавьте '-d memory_limit=-1' перед тестом

если вы используете композитор, используйте приведенный ниже код

./vendor/bin/simple-phpunit -d memory_limit=-1 tests/

если вы используете phpdbg, используйте приведенный ниже код

phpdbg -d memory_limit=-1 -qrr vendor/bin/phpunit --coverage-text

Когда мы используем @runInSeparateProcess, PHPUnit будет пытаться сериализовать включенные файлы, настройки ini, глобальные переменные и константы, чтобы передать их новому процессу. В этом случае, похоже, что PHPUnit столкнулся с рекурсивным сценарием при сериализации одного из этих элементов, который исчерпал память, доступную для процесса PHP. Нам нужно определить, что изменилось между вашей локальной средой и контейнером Docker.

Во-первых, мы можем попытаться отключить это поведение сериализации, чтобы убедиться, что мы должны идти по этому пути. Добавьте следующее @preserveGlobalState аннотация к методу теста, который не проходит:

/**
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */
public function testInSeparateProcess()
{
    // ...
}

Если это решит проблему или мы получим новую ошибку, мы можем начать искать различия в контейнере Docker, которые могут вызвать проблему. Без большей наглядности в код и среду трудно предложить, с чего начать, но вот несколько идей:

  • Сравните вывод php -i из каждой среды. Ищите расширения PHP, которые существуют в одном, но не в другом.
  • использование phpdbg установить точку останова и пройти через код. Мы уже используем его для создания покрытия, но это также полезный инструмент отладки. Мы ищем элемент, который вызывает бесконечную рекурсию. Обратите внимание, что нам нужно установить точку останова, прежде чем PHPUnit выполнит контрольный пример, такой как в файле начальной загрузки или в исходном коде PHPUnit (строка 810 из TestCase может работать).
  • При подключении тома, убедитесь, что www-data Пользователь в контейнере имеет тот же UID, что и пользователь, которому принадлежат файлы на хосте.
  • Попробуйте запустить тест без ограничения памяти. Очевидно, что мы не можем распределить столько, сколько предполагает ошибка, но ограничение памяти может скрыть еще одну проблему. Мы можем убить контейнер при необходимости.

Я не мог воспроизвести эту проблему, используя фиктивный тест в аналогичной среде (настолько близко, насколько я мог бы - тот же контейнер с объемом), поэтому тестируемый код может способствовать возникновению проблемы.

В вашей смонтированной папке, вероятно, есть все поставщики и файлы кэша. Попробуйте подключить только исходную папку.

Это ошибка phpdbg. Обратите внимание, сколько памяти он пытается выделить? Это безумие Я изо всех сил пытался выяснить, где именно ошибка: если полное имя тестового сценария (каталог + имя файла сценария) превышает определенный размер, то он переполняет стек (да, stackru;) и перезаписывает целое число, которое указывает, какой объем памяти должен быть выделен. Затем начинается нормальная обработка ошибок: недостаточно памяти для выделения такого безумного количества. Так что это для Кракджо, чтобы исправить. Или, может быть, я сделаю патч для него в свободное время. Если вам нужно иметь разрешение прямо сейчас: убедитесь, что путь к каталогу, в котором находится ваш скрипт, короткий.

Другие вопросы по тегам