我正在为命令行可执行文件编写集成测试驱动程序。我控制驱动程序和可执行文件,所以我可以保证他们的行为 - 例如,可执行文件永远不会从stdin读取,它只需要命令行参数,执行它的操作,然后将输出写入文件和标准输出。
我希望捕获退出代码和验证过程的标准输出。
以下是我使用的代码:
#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捕获。
指定了超时,但它是一个非常慷慨的60秒 - 比通常运行的测试要长得多。我为每个测试产生了一个新的过程。
如何以更可靠的方式捕获stdout,而不会出现随机故障?
作为最后一点,运行套件以在调试器中捕获故障需要很长时间,因此可能需要一段时间来处理任何请求以获取更多信息。
答案 0 :(得分: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;
到目前为止,我一直无法使用此循环重现失败(并且测试完成得非常快)。