Почему юнит-тестирование не работает в этой D-программе?

Почему модульное тестирование работает для программы 1, а не для программы 2 ниже?

Программа 1

import std.stdio;

unittest
{
    assert(false);
}

void main()
{
    writeln("Hello D-World!");
}

Программа 2

module winmain;

import core.sys.windows.windows;

unittest {
    assert(false);
}

extern (Windows)
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
{
    return 0;
}

Обе программы были скомпилированы с -unittest вариант (запустив dmd -unittest <program.d>). При запуске программа 1 отображает ошибку модульного теста, а программа 2 - нет. Что мне не хватает?

Обновление: переформулированный вопрос и добавленный рабочий пример.

Обновление 2: также скомпилировано с dmd -debug -unittest <program.d>с похожими результатами.

1 ответ

Решение

Ответ довольно прост: в первой программе юнит-тесты фактически выполняются, а во время выполнения во второй программе - не потому, что функция юнит-теста никогда не вызывается, поскольку объявляется ваш собственный WinMain (или даже extern(C) main) обходит инициализацию и настройку во время выполнения, которые обычно выполняются автоматически перед вызовом вашего основного кода D, выполненного в C main.

Откройте ваш dmd zip и перейдите к файлу dmd2/src/druntime/src/rt/dmain2.d. Найдите функцию _d_run_main().

Когда запускается D-программа с обычной D-главной, компилятор вставляет C-главную, которая вызывает _d_run_main(). Эта функция, как вы можете видеть, просматривая источник, делает кучу вещей:

  • он инициализирует аппаратное обеспечение с плавающей запятой в ожидаемом режиме D
  • он форматирует аргументы командной строки в D-строки
  • он инициализирует среду выполнения
  • он запускает юнит-тесты << --- очень важно для вас!
  • он запускает D main, завернутый в блок try/catch для обработки исключений по умолчанию
  • это завершает время выполнения
  • он сбрасывает вывод и возвращает

Да, в строке 399 (у меня есть версия, которая может немного отличаться в вашей версии источника druntime), вы увидите эти строки:

    if (rt_init() && runModuleUnitTests()) 
        tryExec({ result = mainFunc(args); });

Да, модульные тесты запускаются отдельно от rt_init (также известного как Runtime.initialize). Переключатель -unittest компилятора работает так, что он просто не компилирует функции unittest, поэтому runModuleUnitTests видит кучу пустых тестов, которые он пропускает. Таким образом, вы можете вызывать функцию в своей пользовательской главной, не беспокоясь о переключателе компилятора.

Так как у вас есть пользовательский основной и не звонил runModuleUnitTests (определено в core.runtime кстати), модульных тестов никогда не бывает. Они вызываются перед D main, но все еще внутри c main или Win main.

Я рекомендую избегать использования WinMain в D, вместо того, чтобы предпочитать писать обычные D сети. Вы можете получить аргументы, переданные WinMain с функциями API, такими как GetCommandLineW, а также GetModuleHandle, (nCmdShow в любом случае редко используется, и я думаю, hPrevInstance это наследие, оставшееся от 16-ти битных дней, поэтому я сомневаюсь, что вы все равно о них позаботитесь!)

Наличие WinMain также сообщает компоновщику, что вы пишете программу с графическим интерфейсом и, следовательно, должны использовать подсистему Windows - у вас нет консоли. Вы также можете сделать это явно, передав -L/SUBSYSTEM:WINDOWS:5.0 в dmd при компиляции в Windows 32 бит. (Аргумент /SUBSYSTEM является одним из переключателей optlink.) В Windows 64 я не уверен, но он, вероятно, похож, если не идентичен - проверьте документы компоновщика Microsoft для выбора подсистемы, я уверен, что она есть.

Между переключателем компоновщика и двумя вызовами API для извлечения аргументов вам больше не нужен WinMain, поэтому он избавляет вас от необходимости переопределять то, что во время выполнения _d_run_main Функция выполняет сама.

Если вы все равно хотите его использовать, есть два варианта: просто позвоните _d_run_main - посмотрите на исходный код для ожидаемой подписи. Требуется указатель на основную функцию, чтобы вы могли использовать все это. Или вы можете import core.runtime; и позвонить Runtime.initialize(); runModuleUnitTests(); your main here... Runtime.terminate(); сам. Не забудьте проверить возвращаемые значения и обработать исключения! Вы должны сделать это в правильном порядке и правильно обрабатывать ошибки, иначе вы увидите сбои.

То же самое относится и к тому, что вы пишете самостоятельно. extern(C) main а также ваши собственные WinMain,

Опять же, вам, вероятно, лучше избегать этого и просто писать обычную D-главную функцию с переключателем компоновщика, чтобы отключить консоль в вашем графическом приложении.

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