2016-01-23 4 views
2

Я пишу тестовый драйвер интеграции для исполняемого файла в командной строке. Я управляю как драйвером, так и исполняемым файлом, поэтому я могу гарантировать гарантии их поведения - например, исполняемый файл никогда не читает из stdin, он просто принимает аргументы командной строки, делает свою работу, а затем записывает вывод в файл и stdout.Чтение из трубы случайно не работает

Я хочу зафиксировать как код выхода, так и стандартный вывод процесса для проверки.

Вот код, который я использую:

#include <Windows.h> 

class Pipe { 
    HANDLE ReadHandle; 
    HANDLE writehandle; 
public: 
    Pipe() { 
     SECURITY_ATTRIBUTES saAttr; 
     saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
     saAttr.bInheritHandle = TRUE; 
     saAttr.lpSecurityDescriptor = NULL; 
     CreatePipe(&ReadHandle, &writehandle, &saAttr, 0); 
    } 
    HANDLE WriteHandle() { 
     return writehandle; 
    } 
    std::string Contents() { 
     CloseHandle(writehandle); 
     DWORD dwRead; 
     CHAR chBuf[1024]; 
     BOOL bSuccess = FALSE; 

     std::string result; 
     for (;;) 
     { 
      bSuccess = ReadFile(ReadHandle, chBuf, 1024, &dwRead, NULL); 
      if (!bSuccess) break; 
      result += std::string(chBuf, chBuf + dwRead); 
      if (dwRead < 1024) 
       break; 
     } 
     return result; 
    } 
    ~Pipe() { 
     CloseHandle(ReadHandle); 
    } 
}; 
Wide::Driver::ProcessResult Wide::Driver::StartAndWaitForProcess(std::string name, std::vector<std::string> args, Util::optional<unsigned> timeout) 
{ 
    ProcessResult result; 
    Pipe stdoutpipe; 
    PROCESS_INFORMATION info = { 0 }; 
    STARTUPINFO startinfo = { sizeof(STARTUPINFO) }; 
    std::string final_args = name; 
    for (auto arg : args) 
     final_args += " " + arg; 
    startinfo.hStdOutput = stdoutpipe.WriteHandle(); 
    startinfo.hStdError = INVALID_HANDLE_VALUE; 
    startinfo.hStdInput = INVALID_HANDLE_VALUE; 
    startinfo.dwFlags |= STARTF_USESTDHANDLES; 
    auto proc = CreateProcess(
     name.c_str(), 
     &final_args[0], 
     nullptr, 
     nullptr, 
     TRUE, 
     NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, 
     nullptr, 
     nullptr, 
     &startinfo, 
     &info 
     ); 
    if (!proc) { 
     DWORD dw = GetLastError(); 
     const char* message; 
     FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
      nullptr, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&message, 0, nullptr); 
     std::string err = message; 
     LocalFree((void*)message); 
     throw std::runtime_error(err);   
    } 
    if (timeout == 0) 
     timeout = INFINITE; 

    result.std_out = stdoutpipe.Contents(); 
    if (WaitForSingleObject(info.hProcess, timeout ? *timeout : INFINITE) == WAIT_TIMEOUT) 
     TerminateProcess(info.hProcess, 1); 

    DWORD exit_code; 
    GetExitCodeProcess(info.hProcess, &exit_code); 
    CloseHandle(info.hProcess); 
    CloseHandle(info.hThread); 
    result.exitcode = exit_code; 
    if (exit_code != 0) 
     return result; 
    return result; 
} 

У меня есть 259 интеграционные тесты, которые я бегу на этом пути. Некоторые занимают больше времени, чем другие. Когда я запускаю пакет, примерно 1-3 будет терпеть неудачу - каждый раз каждый раз. Я посмотрел на результат в отладчике, и stdout отключен на полпути. Если я не пытаюсь захватить stdout, все тесты будут успешными каждый раз, поэтому я знаю, что он основан на захвате stdout.

Тайм-аут указан, но это очень щедрый 60 секунд - намного дольше, чем обычно проводятся тесты. Я создаю новый процесс для каждого теста.

Как я могу захватить stdout более надежным способом, не получая случайных сбоев?

Как последнее замечание, для запуска сбоя в отладчике требуется много времени, чтобы потребовать некоторое время для обслуживания любых запросов для получения дополнительной информации.

ответ

1

У меня есть теория об этом, но я не совсем уверен. Ключ находится в состоянии цикла для чтения эталона процесса.

std::string result; 
for (;;) 
{ 
    bSuccess = ReadFile(ReadHandle, chBuf, 1024, &dwRead, NULL); 
    if (!bSuccess) break; 
    result += std::string(chBuf, chBuf + dwRead); 
    if (dwRead < 1024) 
     break; 
} 
return result; 

Здесь есть неявное предположение. ReadFile - это блокирующий API, поэтому мы предполагаем, что он продолжает блокировать до тех пор, пока он не получит данные, которые мы попросили, или вход закончился. Но я выдвигаю гипотезу, что на самом деле ReadFile может вернуться до того, как у него будет такой большой размер, как мы просили, , даже если труба не закончилась. Это приведет к завершению цикла ввода данных.

Поскольку родитель больше не читает stdout, ребенок, пытающийся записать stdout, может блокировать ожидание кого-то, чтобы очистить буфер - фактически тупик, поскольку никто не будет. Следовательно, тайм-аут инициирует и завершает процесс, записывая сбой.

MSDN документы говорят это:

The ReadFile function returns when one of the following conditions occur: 

    The number of bytes requested is read. 
    A write operation completes on the write end of the pipe. 
    An asynchronous handle is being used and the read is occurring asynchronously. 
    An error occurs. 

Это делает не сказать, что он вернется, когда операция записи завершается и количество байт, запрошенных доступно. Фактически, он не комментирует операцию записи, делая столько байтов, сколько вы запросили. Так эффективно, он ведет себя полуасинхронно, даже если он называется синхронно.

Я переписал петлю следующим образом:

std::string result; 
for (;;) 
{ 
    bSuccess = ReadFile(ReadHandle, chBuf, 1024, &dwRead, NULL); 
    if (!bSuccess || dwRead == 0) break; 
    result += std::string(chBuf, chBuf + dwRead); 
} 
return result; 

До сих пор я не смогли воспроизвести неудачи с этим циклом (и испытанием комплектным заметно быстрее).

+0

Да, ReadFile() на трубе всегда будет возвращаться, когда он достигнет конца соответствующего WriteFile().Если длина строк записи не превышает размер буфера, это означает, что он вернет меньше запрошенного количества байтов, хотя канал еще не закрыт, и я * думаю *, даже если все еще ожидающие данные из более позднего написать. –

Смежные вопросы