[ROSAUTOTEST] Implement a process activity timeout of 2 minutes. If there is no log output within 2 minutes, the test process is killed, and we continue with the next test.

This is a rather graceful approach compared to sysreg2's 3 minute timeout before killing and restarting the entire VM.
Since we added autochk for FAT filesystems, the filesystem is often "fixed" after a reset with the consequence that ReactOS doesn't boot up anymore.
The sysreg2 restart code still remains for handling tests causing BSODs.
This commit is contained in:
Colin Finck 2019-04-23 09:17:05 +02:00 committed by Timo Kreuzer
parent 0ca4b50791
commit 7dd4d2256b
4 changed files with 131 additions and 19 deletions

View file

@ -3,10 +3,14 @@
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Class that manages an unidirectional anonymous byte stream pipe
* COPYRIGHT: Copyright 2015 Thomas Faber (thomas.faber@reactos.org)
* Copyright 2019 Colin Finck (colin@reactos.org)
*/
#include "precomp.h"
LONG CPipe::m_lPipeCount = 0;
/**
* Constructs a CPipe object and initializes read and write handles.
*/
@ -18,8 +22,50 @@ CPipe::CPipe()
SecurityAttributes.bInheritHandle = TRUE;
SecurityAttributes.lpSecurityDescriptor = NULL;
if(!CreatePipe(&m_hReadPipe, &m_hWritePipe, &SecurityAttributes, 0))
FATAL("CreatePipe failed\n");
// Construct a unique pipe name.
WCHAR wszPipeName[MAX_PATH];
InterlockedIncrement(&m_lPipeCount);
swprintf(wszPipeName, L"\\\\.\\pipe\\TestmanPipe%ld", m_lPipeCount);
// Create a named pipe with the default settings, but overlapped (asynchronous) operations.
// Latter feature is why we can't simply use CreatePipe.
const DWORD dwDefaultBufferSize = 4096;
const DWORD dwDefaultTimeoutMilliseconds = 120000;
m_hReadPipe = CreateNamedPipeW(wszPipeName,
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
dwDefaultBufferSize,
dwDefaultBufferSize,
dwDefaultTimeoutMilliseconds,
&SecurityAttributes);
if (m_hReadPipe == INVALID_HANDLE_VALUE)
{
FATAL("CreateNamedPipe failed\n");
}
// Use CreateFileW to get the write handle to the pipe.
// Writing is done synchronously, so no FILE_FLAG_OVERLAPPED here!
m_hWritePipe = CreateFileW(wszPipeName,
GENERIC_WRITE,
0,
&SecurityAttributes,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (m_hWritePipe == INVALID_HANDLE_VALUE)
{
FATAL("CreateFileW failed\n");
}
// Prepare the OVERLAPPED structure for reading.
ZeroMemory(&m_ReadOverlapped, sizeof(m_ReadOverlapped));
m_ReadOverlapped.hEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
if (!m_ReadOverlapped.hEvent)
{
FATAL("CreateEvent failed\n");
}
}
/**
@ -103,17 +149,60 @@ CPipe::Peek(PVOID Buffer, DWORD BufferSize, PDWORD BytesRead, PDWORD TotalBytesA
* On return, the number of bytes actually read from the pipe into Buffer.
*
* @return
* True on success, false on failure; call GetLastError for error information.
* Returns a Win32 error code. Expected error codes include:
* - ERROR_SUCCESS: The read has completed successfully.
* - WAIT_TIMEOUT: The given timeout has elapsed before any data was read.
* - ERROR_BROKEN_PIPE: The other end of the pipe has been closed.
*
* @see ReadFile
*/
bool
CPipe::Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead)
DWORD
CPipe::Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead, DWORD TimeoutMilliseconds)
{
if (!m_hReadPipe)
{
FATAL("Trying to read from a closed read pipe");
}
return ReadFile(m_hReadPipe, Buffer, NumberOfBytesToRead, NumberOfBytesRead, NULL);
if (ReadFile(m_hReadPipe, Buffer, NumberOfBytesToRead, NumberOfBytesRead, &m_ReadOverlapped))
{
// The asynchronous read request could be satisfied immediately.
return ERROR_SUCCESS;
}
else if (GetLastError() == ERROR_IO_PENDING)
{
// The asynchronous read request could not be satisfied immediately, so wait for it with the given timeout.
DWORD dwWaitResult = WaitForSingleObject(m_ReadOverlapped.hEvent, TimeoutMilliseconds);
if (dwWaitResult == WAIT_OBJECT_0)
{
// Fill NumberOfBytesRead.
if (GetOverlappedResult(m_hReadPipe, &m_ReadOverlapped, NumberOfBytesRead, FALSE))
{
// We successfully read NumberOfBytesRead bytes.
return ERROR_SUCCESS;
}
else if (GetLastError() == ERROR_BROKEN_PIPE)
{
// The other end of the pipe has been closed.
return ERROR_BROKEN_PIPE;
}
else
{
// An unexpected error.
FATAL("GetOverlappedResult failed\n");
}
}
else
{
// This may be WAIT_TIMEOUT or an unexpected error.
return dwWaitResult;
}
}
else
{
// This may be ERROR_BROKEN_PIPE or an unexpected error.
return GetLastError();
}
}
/**

View file

@ -3,11 +3,15 @@
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Class that manages an unidirectional anonymous byte stream pipe
* COPYRIGHT: Copyright 2015 Thomas Faber (thomas.faber@reactos.org)
* Copyright 2019 Colin Finck (colin@reactos.org)
*/
class CPipe
{
private:
static LONG m_lPipeCount;
OVERLAPPED m_ReadOverlapped;
HANDLE m_hReadPipe;
HANDLE m_hWritePipe;
@ -19,7 +23,7 @@ public:
void CloseWritePipe();
bool Peek(PVOID Buffer, DWORD BufferSize, PDWORD BytesRead, PDWORD TotalBytesAvailable);
bool Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead);
DWORD Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead, DWORD TimeoutMilliseconds);
bool Write(LPCVOID Buffer, DWORD NumberOfBytesToWrite, PDWORD NumberOfBytesWritten);
friend class CPipedProcess;

View file

@ -2,7 +2,7 @@
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Class able to create a new process and closing its handles on destruction (exception-safe)
* COPYRIGHT: Copyright 2009 Colin Finck (colin@reactos.org)
* COPYRIGHT: Copyright 2009-2019 Colin Finck (colin@reactos.org)
*/
#include "precomp.h"
@ -27,10 +27,11 @@ CProcess::CProcess(const wstring& CommandLine, LPSTARTUPINFOW StartupInfo)
}
/**
* Destructs a CProcess object and closes all handles belonging to the process.
* Destructs a CProcess object, terminates the process if running, and closes all handles belonging to the process.
*/
CProcess::~CProcess()
{
TerminateProcess(m_ProcessInfo.hProcess, 255);
CloseHandle(m_ProcessInfo.hThread);
CloseHandle(m_ProcessInfo.hProcess);
}

View file

@ -2,12 +2,13 @@
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Class implementing functions for handling Wine tests
* COPYRIGHT: Copyright 2009-2015 Colin Finck (colin@reactos.org)
* COPYRIGHT: Copyright 2009-2019 Colin Finck (colin@reactos.org)
*/
#include "precomp.h"
static const DWORD ListTimeout = 10000;
static const DWORD ProcessActivityTimeout = 120000;
/**
* Constructs a CWineTest object.
@ -140,7 +141,7 @@ CWineTest::DoListCommand()
/* Read the data */
m_ListBuffer = new char[BytesAvailable];
if(!Pipe.Read(m_ListBuffer, BytesAvailable, &Temp))
if(Pipe.Read(m_ListBuffer, BytesAvailable, &Temp, INFINITE) != ERROR_SUCCESS)
TESTEXCEPTION("CPipe::Read failed\n");
return BytesAvailable;
@ -291,17 +292,34 @@ CWineTest::RunTest(CTestInfo* TestInfo)
CPipedProcess Process(TestInfo->CommandLine, Pipe);
/* Receive all the data from the pipe */
while(Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable) && BytesAvailable)
for (;;)
{
/* Output text through StringOut, even while the test is still running */
Buffer[BytesAvailable] = 0;
tailString = StringOut(tailString.append(string(Buffer)), false);
DWORD dwReadResult = Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable, ProcessActivityTimeout);
if (dwReadResult == ERROR_SUCCESS)
{
/* Output text through StringOut, even while the test is still running */
Buffer[BytesAvailable] = 0;
tailString = StringOut(tailString.append(string(Buffer)), false);
if(Configuration.DoSubmit())
TestInfo->Log += Buffer;
if (Configuration.DoSubmit())
TestInfo->Log += Buffer;
}
else if (dwReadResult == ERROR_BROKEN_PIPE)
{
// The process finished and has been terminated.
break;
}
else if (dwReadResult == WAIT_TIMEOUT)
{
// The process activity timeout above has elapsed without any new data.
TESTEXCEPTION("Timeout while waiting for the test process\n");
}
else
{
// An unexpected error.
TESTEXCEPTION("CPipe::Read failed for the test run\n");
}
}
if(GetLastError() != ERROR_BROKEN_PIPE)
TESTEXCEPTION("CPipe::Read failed for the test run\n");
}
catch(CTestException& e)
{