Author: tfaber Date: Mon Feb 16 13:17:04 2015 New Revision: 66316
URL: http://svn.reactos.org/svn/reactos?rev=66316&view=rev Log: [ROSAUTOTEST] - Abstract unidirectional anonymous pipes into a CPipe class - Abstract a process with redirected output into a CPipedProcess class - Use these abstractions to avoid polling for output from test processes. Instead, use blocking read operations to yield the CPU while waiting for data. ROSTESTS-144 #resolve
Added: trunk/rostests/rosautotest/CPipe.cpp (with props) trunk/rostests/rosautotest/CPipe.h (with props) trunk/rostests/rosautotest/CPipedProcess.cpp (with props) trunk/rostests/rosautotest/CPipedProcess.h (with props) Modified: trunk/rostests/rosautotest/CMakeLists.txt trunk/rostests/rosautotest/CWineTest.cpp trunk/rostests/rosautotest/CWineTest.h trunk/rostests/rosautotest/precomp.h
Modified: trunk/rostests/rosautotest/CMakeLists.txt URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CMakeLists.txt... ============================================================================== --- trunk/rostests/rosautotest/CMakeLists.txt [iso-8859-1] (original) +++ trunk/rostests/rosautotest/CMakeLists.txt [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -6,6 +6,8 @@ CFatalException.cpp CInvalidParameterException.cpp CJournaledTestList.cpp + CPipe.cpp + CPipedProcess.cpp CProcess.cpp CSimpleException.cpp CTest.cpp
Added: trunk/rostests/rosautotest/CPipe.cpp URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CPipe.cpp?rev=... ============================================================================== --- trunk/rostests/rosautotest/CPipe.cpp (added) +++ trunk/rostests/rosautotest/CPipe.cpp [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -0,0 +1,143 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class that managed an unidirectional anonymous byte stream pipe + * COPYRIGHT: Copyright 2015 Thomas Faber thomas.faber@reactos.org + */ + +#include "precomp.h" + +/** + * Constructs a CPipe object and initializes read and write handles. + */ +CPipe::CPipe() +{ + SECURITY_ATTRIBUTES SecurityAttributes; + + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.bInheritHandle = TRUE; + SecurityAttributes.lpSecurityDescriptor = NULL; + + if(!CreatePipe(&m_hReadPipe, &m_hWritePipe, &SecurityAttributes, 0)) + FATAL("CreatePipe failed\n"); +} + +/** + * Destructs a CPipe object and closes all open handles. + */ +CPipe::~CPipe() +{ + if (m_hReadPipe) + CloseHandle(m_hReadPipe); + if (m_hWritePipe) + CloseHandle(m_hWritePipe); +} + +/** + * Closes a CPipe's read pipe, leaving the write pipe unchanged. + */ +void +CPipe::CloseReadPipe() +{ + if (!m_hReadPipe) + FATAL("Trying to close already closed read pipe"); + CloseHandle(m_hReadPipe); +} + +/** + * Closes a CPipe's write pipe, leaving the read pipe unchanged. + */ +void +CPipe::CloseWritePipe() +{ + if (!m_hWritePipe) + FATAL("Trying to close already closed write pipe"); + CloseHandle(m_hWritePipe); +} + +/** + * Reads data from a pipe without advancing the read offset and/or retrieves information about available data. + * + * This function must not be called after CloseReadPipe. + * + * @param Buffer + * An optional buffer to read pipe data into. + * + * @param BufferSize + * The size of the buffer specified in Buffer, or 0 if no read should be performed. + * + * @param BytesRead + * On return, the number of bytes actually read from the pipe into Buffer. + * + * @param TotalBytesAvailable + * On return, the total number of bytes available to read from the pipe. + * + * @return + * True on success, false on failure; call GetLastError for error information. + * + * @see PeekNamedPipe + */ +bool +CPipe::Peek(PVOID Buffer, DWORD BufferSize, PDWORD BytesRead, PDWORD TotalBytesAvailable) +{ + if (!m_hReadPipe) + FATAL("Trying to peek from a closed read pipe"); + + return PeekNamedPipe(m_hReadPipe, Buffer, BufferSize, BytesRead, TotalBytesAvailable, NULL); +} + +/** + * Reads data from the read pipe, advancing the read offset accordingly. + * + * This function must not be called after CloseReadPipe. + * + * @param Buffer + * Buffer to read pipe data into. + * + * @param NumberOfBytesToRead + * The number of bytes to read into Buffer. + * + * @param NumberOfBytesRead + * 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. + * + * @see ReadFile + */ +bool +CPipe::Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead) +{ + if (!m_hReadPipe) + FATAL("Trying to read from a closed read pipe"); + + return ReadFile(m_hReadPipe, Buffer, NumberOfBytesToRead, NumberOfBytesRead, NULL); +} + +/** + * Writes data to the write pipe. + * + * This function must not be called after CloseWritePipe. + * + * @param Buffer + * Buffer containing the data to write. + * + * @param NumberOfBytesToWrite + * The number of bytes to write to the pipe from Buffer. + * + * @param NumberOfBytesWritten + * On return, the number of bytes actually written to the pipe. + * + * @return + * True on success, false on failure; call GetLastError for error information. + * + * @see WriteFile + */ +bool +CPipe::Write(LPCVOID Buffer, DWORD NumberOfBytesToWrite, PDWORD NumberOfBytesWritten) +{ + if (!m_hWritePipe) + FATAL("Trying to write to a closed write pipe"); + + return WriteFile(m_hWritePipe, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, NULL); +}
Propchange: trunk/rostests/rosautotest/CPipe.cpp ------------------------------------------------------------------------------ svn:eol-style = native
Added: trunk/rostests/rosautotest/CPipe.h URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CPipe.h?rev=66... ============================================================================== --- trunk/rostests/rosautotest/CPipe.h (added) +++ trunk/rostests/rosautotest/CPipe.h [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -0,0 +1,26 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class that managed an unidirectional anonymous byte stream pipe + * COPYRIGHT: Copyright 2015 Thomas Faber thomas.faber@reactos.org + */ + +class CPipe +{ +private: + HANDLE m_hReadPipe; + HANDLE m_hWritePipe; + +public: + CPipe(); + ~CPipe(); + + void CloseReadPipe(); + void CloseWritePipe(); + + bool Peek(PVOID Buffer, DWORD BufferSize, PDWORD BytesRead, PDWORD TotalBytesAvailable); + bool Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead); + bool Write(LPCVOID Buffer, DWORD NumberOfBytesToWrite, PDWORD NumberOfBytesWritten); + + friend class CPipedProcess; +};
Propchange: trunk/rostests/rosautotest/CPipe.h ------------------------------------------------------------------------------ svn:eol-style = native
Added: trunk/rostests/rosautotest/CPipedProcess.cpp URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CPipedProcess.... ============================================================================== --- trunk/rostests/rosautotest/CPipedProcess.cpp (added) +++ trunk/rostests/rosautotest/CPipedProcess.cpp [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -0,0 +1,42 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class that creates a process and redirects its output to a pipe + * COPYRIGHT: Copyright 2015 Thomas Faber thomas.faber@reactos.org + */ + +#include "precomp.h" + +/** + * Constructs a CPipedProcess object and starts the process with redirected output. + * + * @param CommandLine + * A std::wstring containing the command line to run. + * + * @param Pipe + * The CPipe instance to redirect the process's output to. + * Note that only the read pipe is usable after the pipe was passed to this object. + */ +CPipedProcess::CPipedProcess(const wstring& CommandLine, CPipe& Pipe) + : CProcess(CommandLine, InitStartupInfo(Pipe)) +{ + Pipe.CloseWritePipe(); +} + +/** + * Initializes the STARTUPINFO structure for use in CreateProcessW. + * + * @param Pipe + * The CPipe instance to redirect the process's output to. + */ +LPSTARTUPINFOW +CPipedProcess::InitStartupInfo(CPipe& Pipe) +{ + ZeroMemory(&m_StartupInfo, sizeof(m_StartupInfo)); + m_StartupInfo.cb = sizeof(m_StartupInfo); + m_StartupInfo.dwFlags = STARTF_USESTDHANDLES; + m_StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + m_StartupInfo.hStdOutput = Pipe.m_hWritePipe; + m_StartupInfo.hStdError = Pipe.m_hWritePipe; + return &m_StartupInfo; +}
Propchange: trunk/rostests/rosautotest/CPipedProcess.cpp ------------------------------------------------------------------------------ svn:eol-style = native
Added: trunk/rostests/rosautotest/CPipedProcess.h URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CPipedProcess.... ============================================================================== --- trunk/rostests/rosautotest/CPipedProcess.h (added) +++ trunk/rostests/rosautotest/CPipedProcess.h [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -0,0 +1,17 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class that creates a process and redirects its output to a pipe + * COPYRIGHT: Copyright 2015 Thomas Faber thomas.faber@reactos.org + */ + +class CPipedProcess : public CProcess +{ +private: + STARTUPINFOW m_StartupInfo; + + LPSTARTUPINFOW InitStartupInfo(CPipe& Pipe); + +public: + CPipedProcess(const wstring& CommandLine, CPipe& Pipe); +};
Propchange: trunk/rostests/rosautotest/CPipedProcess.h ------------------------------------------------------------------------------ svn:eol-style = native
Modified: trunk/rostests/rosautotest/CWineTest.cpp URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CWineTest.cpp?... ============================================================================== --- trunk/rostests/rosautotest/CWineTest.cpp [iso-8859-1] (original) +++ trunk/rostests/rosautotest/CWineTest.cpp [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -18,10 +18,7 @@
/* Zero-initialize variables */ m_hFind = NULL; - m_hReadPipe = NULL; - m_hWritePipe = NULL; m_ListBuffer = NULL; - memset(&m_StartupInfo, 0, sizeof(m_StartupInfo));
/* Set up m_TestPath */ if(!GetWindowsDirectoryW(WindowsDirectory, MAX_PATH)) @@ -38,12 +35,6 @@ { if(m_hFind) FindClose(m_hFind); - - if(m_hReadPipe) - CloseHandle(m_hReadPipe); - - if(m_hWritePipe) - CloseHandle(m_hWritePipe);
if(m_ListBuffer) delete m_ListBuffer; @@ -111,6 +102,7 @@ DWORD BytesAvailable; DWORD Temp; wstring CommandLine; + CPipe Pipe;
/* Build the command line */ CommandLine = m_TestPath; @@ -119,7 +111,7 @@
{ /* Start the process for getting all available tests */ - CProcess Process(CommandLine, &m_StartupInfo); + CPipedProcess Process(CommandLine, Pipe);
/* Wait till this process ended */ if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED) @@ -127,8 +119,8 @@ }
/* Read the output data into a buffer */ - if(!PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL)) - FATAL("PeekNamedPipe failed for the test list\n"); + if(!Pipe.Peek(NULL, 0, NULL, &BytesAvailable)) + FATAL("CPipe::Peek failed for the test list\n");
/* Check if we got any */ if(!BytesAvailable) @@ -142,8 +134,8 @@ /* Read the data */ m_ListBuffer = new char[BytesAvailable];
- if(!ReadFile(m_hReadPipe, m_ListBuffer, BytesAvailable, &Temp, NULL)) - FATAL("ReadPipe failed\n"); + if(!Pipe.Read(m_ListBuffer, BytesAvailable, &Temp)) + FATAL("CPipe::Read failed\n");
return BytesAvailable; } @@ -260,13 +252,13 @@ void CWineTest::RunTest(CTestInfo* TestInfo) { - bool BreakLoop = false; DWORD BytesAvailable; - DWORD Temp; stringstream ss, ssFinish; DWORD StartTime = GetTickCount(); float TotalTime; string tailString; + CPipe Pipe; + char Buffer[1024];
ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl; StringOut(ss.str()); @@ -275,40 +267,20 @@
{ /* Execute the test */ - CProcess Process(TestInfo->CommandLine, &m_StartupInfo); + CPipedProcess Process(TestInfo->CommandLine, Pipe);
/* Receive all the data from the pipe */ - do + while(Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable) && BytesAvailable) { - /* When the application finished, make sure that we peek the pipe one more time, so that we get all data. - If the following condition would be the while() condition, we might hit a race condition: - - We check for data with PeekNamedPipe -> no data available - - The application outputs its data and finishes - - WaitForSingleObject reports that the application has finished and we break the loop without receiving any data - */ - if(WaitForSingleObject(Process.GetProcessHandle(), 0) != WAIT_TIMEOUT) - BreakLoop = true; - - if(!PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL)) - FATAL("PeekNamedPipe failed for the test run\n"); - - if(BytesAvailable) - { - /* There is data, so get it and output it */ - auto_array_ptr<char> Buffer(new char[BytesAvailable + 1]); - - if(!ReadFile(m_hReadPipe, Buffer, BytesAvailable, &Temp, NULL)) - FATAL("ReadFile failed for the test run\n"); - - /* 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; - } + /* 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; } - while(!BreakLoop); + if(GetLastError() != ERROR_BROKEN_PIPE) + FATAL("CPipe::Read failed for the test run\n"); }
/* Print what's left */ @@ -330,21 +302,6 @@ auto_ptr<CTestList> TestList; auto_ptr<CWebService> WebService; CTestInfo* TestInfo; - SECURITY_ATTRIBUTES SecurityAttributes; - - /* Create a pipe for getting the output of the tests */ - SecurityAttributes.nLength = sizeof(SecurityAttributes); - SecurityAttributes.bInheritHandle = TRUE; - SecurityAttributes.lpSecurityDescriptor = NULL; - - if(!CreatePipe(&m_hReadPipe, &m_hWritePipe, &SecurityAttributes, 0)) - FATAL("CreatePipe failed\n"); - - m_StartupInfo.cb = sizeof(m_StartupInfo); - m_StartupInfo.dwFlags = STARTF_USESTDHANDLES; - m_StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - m_StartupInfo.hStdOutput = m_hWritePipe; - m_StartupInfo.hStdError = m_hWritePipe;
/* The virtual test list is of course faster, so it should be preferred over the journaled one.
Modified: trunk/rostests/rosautotest/CWineTest.h URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/CWineTest.h?re... ============================================================================== --- trunk/rostests/rosautotest/CWineTest.h [iso-8859-1] (original) +++ trunk/rostests/rosautotest/CWineTest.h [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -9,10 +9,7 @@ { private: HANDLE m_hFind; - HANDLE m_hReadPipe; - HANDLE m_hWritePipe; PCHAR m_ListBuffer; - STARTUPINFOW m_StartupInfo; string m_CurrentTest; wstring m_CurrentFile; wstring m_CurrentListCommand;
Modified: trunk/rostests/rosautotest/precomp.h URL: http://svn.reactos.org/svn/reactos/trunk/rostests/rosautotest/precomp.h?rev=... ============================================================================== --- trunk/rostests/rosautotest/precomp.h [iso-8859-1] (original) +++ trunk/rostests/rosautotest/precomp.h [iso-8859-1] Mon Feb 16 13:17:04 2015 @@ -30,7 +30,9 @@ #include "CConfiguration.h" #include "CFatalException.h" #include "CInvalidParameterException.h" +#include "CPipe.h" #include "CProcess.h" +#include "CPipedProcess.h" #include "CSimpleException.h" #include "CTestInfo.h" #include "CTest.h"