我正在为命令行可执行文件编写集成测试驱动程序。我控制驱动程序和可执行文件,因此我可以保证它们的行为 - 例如,可执行文件永远不会从标准输入读取,它只接受命令行参数,执行其操作,然后将输出写入文件和标准输出。
我希望捕获进程的退出代码和标准输出以进行验证。
这是我正在使用的代码:
#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 个会失败 - 每次都有不同的失败。我查看了调试器中的结果,标准输出在中途被切断。如果我不 try catch 标准输出,则所有测试每次都会成功,因此我知道它是基于标准输出捕获的。
指定了超时,但它是一个非常慷慨的 60 秒 - 比正常运行测试所需的时间长得多。我为每个测试生成一个新进程。
如何以更可靠的方式捕获标准输出,而不会出现随机故障?
最后一点,运行套件需要很长时间才能捕获调试器中的故障,因此可能需要一段时间才能处理任何进一步信息的请求。
最佳答案
我对此有一个理论,但我并不完全确定。关键在于读取进程的标准输出的循环条件。
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
可能会在它具有我们要求的大小的 block 之前返回,即使管道尚未终止。这将导致输入读取循环终止。
由于父进程不再读取标准输出,尝试写入标准输出的子进程可能会阻塞等待某人清除缓冲区——实际上是一个死锁,因为没有人会这样做。因此,超时会触发并终止进程,记录失败。
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;
到目前为止,我无法通过此循环重现故障(并且测试完成速度明显更快)。
关于c++ - 从管道读取随机失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34966180/