Starting compliance test framework. Mac only for now.

This commit is contained in:
Jonas Drewsen 2013-06-25 14:51:08 +02:00
Родитель 141675735c
Коммит 0e9c80498e
11 изменённых файлов: 946 добавлений и 3 удалений

Двоичные данные
Build/OSXi386/PerforcePlugin Executable file

Двоичный файл не отображается.

Просмотреть файл

@ -12,6 +12,9 @@ PLATFORM = OSXi386
COMMON_MODULES = $(COMMON_SRCS:.c=.o)
COMMON_MODULES := $(COMMON_MODULES:.cpp=.o)
TESTSERVER_MODULES = $(TESTSERVER_SRCS:.c=.o)
TESTSERVER_TARGET= Build/$(PLATFORM)/TestServer
P4PLUGIN_MODULES = $(P4PLUGIN_SRCS:.c=.o)
P4PLUGIN_MODULES := $(P4PLUGIN_MODULES:.cpp=.o)
P4PLUGIN_TARGET = PerforcePlugin
@ -22,14 +25,27 @@ SVNPLUGIN_TARGET = SubversionPlugin
default: all
all: P4Plugin SvnPlugin
all: P4Plugin SvnPlugin
test: all testserver testp4 testsvn
@echo "done testing"
testp4: $(TESTSCRIPTS_GENERAL) ${P4TESTSCRIPTS}
@echo "Running perforce tests"
@./Test/run_tests.sh ${TESTSERVER_TARGET} ./${P4PLUGIN_TARGET} nonverbose ${^}
testsvn:
testserver: $(TESTSERVER_TARGET)
@mkdir -p Build/$(PLATFORM)
P4Plugin: $(P4PLUGIN_TARGET)
mkdir -p Build/$(PLATFORM)
@mkdir -p Build/$(PLATFORM)
cp $(P4PLUGIN_TARGET) Build/$(PLATFORM)
SvnPlugin: $(SVNPLUGIN_TARGET)
mkdir -p Build/$(PLATFORM)
@mkdir -p Build/$(PLATFORM)
cp $(SVNPLUGIN_TARGET) Build/$(PLATFORM)
Common: $(COMMON_MODULES)
@ -37,12 +53,18 @@ Common: $(COMMON_MODULES)
Common/%.o : Common/%.cpp $(COMMON_INCLS)
$(CXX) $(CXXFLAGS) $(INCLUDE) -c $< -o $@
Test/Source/%.o : Test/Source/%.cpp $(TESTSERVER_INCLS)
$(CXX) $(CXXFLAGS) -c $< -o $@
P4Plugin/Source/%.o : P4Plugin/Source/%.cpp $(COMMON_INCLS) $(P4PLUGIN_INCLS)
$(CXX) $(CXXFLAGS) $(P4PLUGIN_INCLUDE) -c $< -o $@
SvnPlugin/Source/%.o : SvnPlugin/Source/%.cpp $(COMMON_INCLS) $(SVNPLUGIN_INCLS)
$(CXX) $(CXXFLAGS) $(SVNPLUGIN_INCLUDE) -c $< -o $@
$(TESTSERVER_TARGET): $(TESTSERVER_MODULES)
$(CXX) -arch i386 -o $@ $^
$(P4PLUGIN_TARGET): $(COMMON_MODULES) $(P4PLUGIN_MODULES)
$(CXX) -arch i386 -o $@ -framework Cocoa $^ ./P4Plugin/Source/r12.2/lib/osx32/libssl.a ./P4Plugin/Source/r12.2/lib/osx32/libcrypto.a -L./P4Plugin/Source/r12.2/lib/osx32 $(P4PLUGIN_LINK)

Просмотреть файл

@ -24,6 +24,11 @@ COMMON_INCLS = ./Common/Changes.h \
./Common/Log.h \
./Common/POpen.h
TESTSERVER_SRCS = ./Test/Source/ExternalProcess_Posix.cpp \
./Test/Source/TestServer.cpp
TESTSERVER_INCLS = ./Test/Source/ExternalProcess.h
TESTSCRIPTS_GENERAL = ./Test/ProtocolNegotiation.txt
P4PLUGIN_SRCS = ./P4Plugin/Source/P4Plugin_Posix.cpp \
./P4Plugin/Source/P4AddCommand.cpp \
./P4Plugin/Source/P4ChangeDescriptionCommand.cpp \
@ -67,6 +72,7 @@ P4PLUGIN_INCLS = ./P4Plugin/Source/P4Command.h \
P4PLUGIN_LINK = -lclient -lrpc -lsupp
P4PLUGIN_INCLUDE = -I./Common -I./P4Plugin/Source/r12.2/include/p4 -I./P4Plugin/Source
P4TESTSCRIPTS = ./Test/Perforce/Traits.txt
SVNPLUGIN_SRCS = ./SvnPlugin/Source/SvnPlugin_OSX.cpp \
./SvnPlugin/Source/SvnTask.cpp
@ -96,3 +102,5 @@ SVNPLUGIN_INCLS = ./SvnPlugin/Source/AllSvnCommands.h \
SVNPLUGIN_LINK =
SVNPLUGIN_INCLUDE = -I./Common -I./SvnPlugin/Source
SVNTESTSCRIPTS =

30
Test/Perforce/Traits.txt Normal file
Просмотреть файл

@ -0,0 +1,30 @@
c:pluginConfig pluginTraits
--
o1:4
o8:requiresNetwork
o8:enablesCheckout
o8:enablesLocking
o8:enablesRevertUnchanged
o1:4
o1:vcPerforceUsername
o8:Username
o8:The perforce user name
o1:
o1:1
o1:vcPerforcePassword
o8:Password
o8:The perforce password
o1:
o1:2
o1:vcPerforceWorkspace
o8:Workspace
o8:The perforce workspace/client
o1:
o1:1
o1:vcPerforceServer
o8:Server
o8:The perforce server using format: hostname:port. Port hostname defaults to 'perforce' and port defaults to 1666
o1:perforce
o1:0
r1:end of response
--

Просмотреть файл

@ -0,0 +1,5 @@
c:pluginConfig pluginVersions 1
--
o8:1
r1:end of response
--

Просмотреть файл

@ -0,0 +1,81 @@
#pragma once
#if _WIN32
#include "PlatformDependent/Win/WinUtils.h"
#endif
#include <string>
#include <vector>
enum ExternalProcessState {
EPSTATE_NotRunning,
EPSTATE_TimeoutReading,
EPSTATE_TimeoutWriting,
EPSTATE_BrokenPipe
};
class ExternalProcessException
{
public:
ExternalProcessException(ExternalProcessState st, const std::string& msg = "") : m_State(st), m_Msg(msg) {}
const std::string& Message() const throw() { return m_Msg; }
const char* what() const throw() { return m_Msg.c_str(); } // standard
private:
const ExternalProcessState m_State;
const std::string m_Msg;
};
class ExternalProcess
{
public:
ExternalProcess(const std::string& app, const std::vector<std::string>& arguments);
~ExternalProcess();
bool Launch();
bool IsRunning();
bool Write(const std::string& data);
std::string ReadLine();
std::string PeekLine();
void Shutdown();
void SetReadTimeout(double secs);
double GetReadTimeout();
void SetWriteTimeout(double secs);
double GetWriteTimeout();
private:
void Cleanup();
bool m_LineBufferValid;
std::string m_LineBuffer;
std::string m_ApplicationPath;
std::vector<std::string> m_Arguments;
double m_ReadTimeout;
double m_WriteTimeout;
#if UNITY_WIN
PROCESS_INFORMATION m_Task;
OVERLAPPED m_Overlapped;
HANDLE m_NamedPipe;
HANDLE m_Event; // Read or Write event
// Main task writes to m_Task_stdin_wr and child task reads from m_Task_stdin_rd
winutils::AutoHandle m_Task_stdin_rd;
winutils::AutoHandle m_Task_stdin_wr;
// Main task reads m_Task_stdout_rd and child task writes to m_Task_stdout_wr
winutils::AutoHandle m_Task_stdout_rd;
winutils::AutoHandle m_Task_stdout_wr;
std::string m_Buffer;
#else // posix
pid_t m_Pid;
int m_ExitCode;
FILE *m_Input,
*m_Output;
bool m_Exited;
std::string ReadLine (FILE *file, std::string &buffer, bool removeFromBuffer);
#endif
};

Просмотреть файл

@ -0,0 +1,221 @@
#include "ExternalProcess.h"
#include <cstdio>
#include <cerrno>
#include <fcntl.h>
#include <sys/wait.h>
#include <signal.h>
#include <iostream>
ExternalProcess::ExternalProcess(const std::string& app, const std::vector<std::string>& arguments) :
m_LineBuffer(""),
m_LineBufferValid (false),
m_Pid(0),
m_Input(NULL),
m_Output(NULL),
m_ExitCode(-1),
m_Exited(false),
m_ApplicationPath(app),
m_Arguments(arguments),
m_ReadTimeout (120.0)
{
}
ExternalProcess::~ExternalProcess()
{
Shutdown ();
fclose (m_Input);
fclose (m_Output);
}
bool ExternalProcess::Launch()
{
int readpipe[2]; // file descriptors for the parent to read and the child to write
int writepipe[2]; // file descriptors for the parent to write and the child to read
// Setup communication pipeline first
if (pipe(readpipe) || pipe(writepipe))
{
std::cerr << "Pipe error!" << std::endl;
return false;
}
// Attempt to fork and check for errors
m_Pid = fork ();
if (m_Pid == -1)
{
std::cerr << "fork error" << std::endl; // something went wrong
return false;
}
if (m_Pid != 0) {
// A positive (non-negative) PID indicates the parent process
m_Input = fdopen (readpipe[0], "r");
m_Output = fdopen (writepipe[1], "w");
close (readpipe[1]);
close (writepipe[0]);
if (!(m_Input && m_Output)) {
std::cerr << "error in fdopen()" << std::endl;
fclose (m_Input);
fclose (m_Output);
return false;
}
return true;
} else {
// A zero PID indicates that this is the child process
dup2 (writepipe[0], STDIN_FILENO); // Replace stdin with the IN side of the pipe
dup2 (readpipe[1], STDOUT_FILENO); // Replace stdout with the OUT side of the pipe
close (readpipe[0]);
close (writepipe[1]);
// Extract binary name from the path:
std::string bin = m_ApplicationPath;
int lastSlash = m_ApplicationPath.rfind ('/');
if (lastSlash != std::string::npos)
bin = m_ApplicationPath.substr (lastSlash + 1);
std::vector<char const*> args;
args.push_back (bin.c_str ());
for (std::vector<std::string>::const_iterator it = m_Arguments.begin (),
end = m_Arguments.end (); it != end; ++it)
args.push_back (it->c_str ());
args.push_back (NULL);
// Replace the child fork with a new process
int ret = execvp (m_ApplicationPath.c_str (), const_cast<char* const*> (&args[0]));
if( -1 == ret ) {
std::cerr << "Error: execvp(" << m_ApplicationPath << ") : " << strerror(errno) << std::endl;
exit (ret);
}
}
return true;
}
bool ExternalProcess::IsRunning()
{
if (m_Exited)
return false;
int status;
int retval = waitpid (m_Pid, &status, WNOHANG | WUNTRACED | WCONTINUED);
if (0 == retval) // State hasn't changed since last time
return true;
if (WIFEXITED (status)) {
m_Exited = true;
m_ExitCode = WEXITSTATUS (status);
} else if (WIFSIGNALED (status)) {
m_Exited = true;
m_ExitCode = WTERMSIG (status);
}
return !m_Exited;
}
bool ExternalProcess::Write(const std::string& data)
{
fprintf (m_Output, "%s", data.c_str ());
fflush (m_Output);
return true;
}
std::string ExternalProcess::ReadLine (FILE *file, std::string &buffer, bool removeFromBuffer)
{
int readyFDs,
fd = fileno (file);
fd_set fdReadSet;
std::string::size_type i = buffer.find('\n');
int retries = 3;
struct timeval timeout;
timeout.tv_sec = (time_t) m_ReadTimeout;
timeout.tv_usec = (suseconds_t) ((m_ReadTimeout - timeout.tv_sec) * 1000000.0);
while (retries > 0)
{
if (!IsRunning())
throw ExternalProcessException(EPSTATE_NotRunning, "Trying to read from process that is not running");
if (i == std::string::npos)
{
// No newline found - read more data
FD_ZERO(&fdReadSet);
FD_SET(fd, &fdReadSet);
readyFDs = select (fd + 1, &fdReadSet, NULL, NULL, &timeout);
if ( readyFDs == -1 ) {
if (errno == EAGAIN) {
--retries;
continue;
}
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error polling external process");
} else if (readyFDs == 0 || !FD_ISSET (fd, &fdReadSet)) {
throw ExternalProcessException(EPSTATE_BrokenPipe, "Got unknown write ready file description while write from external process");
}
char charBuffer[BUFSIZ];
ssize_t bytesread = read (fd, charBuffer, BUFSIZ-1);
if (0 >= bytesread)
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error reading external process - read 0 bytes");
charBuffer[bytesread] = '\0';
buffer += charBuffer;
retries = 3;
}
i = buffer.find ('\n');
if (std::string::npos != i) {
std::string line = buffer.substr (0, i);
if (removeFromBuffer)
buffer.erase (0, i+1);
return line;
}
}
throw ExternalProcessException(EPSTATE_BrokenPipe, "Failed waiting for read from external process");
}
std::string ExternalProcess::ReadLine()
{
return ReadLine (m_Input, m_LineBuffer, true);
}
std::string ExternalProcess::PeekLine()
{
return ReadLine (m_Input, m_LineBuffer, false);
}
void ExternalProcess::Shutdown()
{
kill (m_Pid, SIGINT);
usleep (100000);
if (IsRunning ())
kill (m_Pid, SIGKILL);
}
void ExternalProcess::SetReadTimeout(double secs)
{
m_ReadTimeout = secs;
}
double ExternalProcess::GetReadTimeout()
{
return m_ReadTimeout;
}
void ExternalProcess::SetWriteTimeout(double secs)
{
m_WriteTimeout = secs;
}
double ExternalProcess::GetWriteTimeout()
{
return m_WriteTimeout;
}

Просмотреть файл

@ -0,0 +1,420 @@
#include "UnityPrefix.h"
#include "Editor/Platform/Interface/ExternalProcess.h"
#include "Runtime/Utilities/File.h"
#include "PlatformDependent/Win/PathUnicodeConversion.h"
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
ExternalProcess::ExternalProcess(const std::string& app, const std::vector<std::string>& arguments)
: m_LineBufferValid(false), m_ApplicationPath(app), m_Arguments(arguments),
m_ReadTimeout(30.0), m_WriteTimeout(30.0)
{
ZeroMemory( &m_Task, sizeof(m_Task) );
m_Task.hProcess = INVALID_HANDLE_VALUE;
m_Event = INVALID_HANDLE_VALUE;
m_NamedPipe = INVALID_HANDLE_VALUE;
}
ExternalProcess::~ExternalProcess()
{
Shutdown();
}
bool ExternalProcess::Launch()
{
// Most of this is from MSDN's "Creating a Child Process with Redirected Input and Output",
// except for the parts where the code example is wrong :)
//
// See http://www.codeproject.com/KB/threads/redir.aspx for details on where it's wrong...
// Make sure the plugin is not running
Shutdown();
m_Buffer.clear();
// LogString(string("Launching external process ") + m_ApplicationPath);
// Search path if app is not absolute name
wchar_t appWide[kDefaultPathBufferSize];
ConvertUnityPathName( m_ApplicationPath.c_str(), appWide, kDefaultPathBufferSize );
if( !IsAbsoluteFilePath(m_ApplicationPath) )
{
wchar_t pathWide[kDefaultPathBufferSize];
memset(pathWide, 0, sizeof(pathWide));
wchar_t* namePart;
if( SearchPathW( NULL, appWide, NULL, kDefaultPathBufferSize, pathWide, &namePart ) > 0 )
{
memcpy( appWide, pathWide, sizeof(pathWide) );
}
}
// Now create a child process
STARTUPINFOW siStartInfo;
ZeroMemory( &m_Task, sizeof(m_Task) );
ZeroMemory( &siStartInfo, sizeof(siStartInfo) );
siStartInfo.cb = sizeof(siStartInfo);
siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
// siStartInfo.hStdOutput = INVALID_HANDLE_VALUE; // m_Task_stdout_wr.handle;
// siStartInfo.hStdInput = INVALID_HANDLE_VALUE; // m_Task_stdin_rd.handle;
siStartInfo.wShowWindow = SW_HIDE;
// generate an argument list
std::wstring argumentString = std::wstring(L"\"") + appWide + L'"';
std::wstring argWide;
for( int i = 0; i < m_Arguments.size(); ++i ) {
argumentString += L' ';
std::string arg = m_Arguments[ i ];
arg = QuoteString( arg );
ConvertUTF8ToWideString( arg, argWide );
argumentString += argWide;
}
wchar_t* argumentBuffer = new wchar_t[argumentString.size()+1];
memcpy( argumentBuffer, argumentString.c_str(), (argumentString.size()+1)*sizeof(wchar_t) );
// launch process
wchar_t directoryWide[kDefaultPathBufferSize];
ConvertUnityPathName( File::GetCurrentDirectory().c_str(), directoryWide, kDefaultPathBufferSize );
BOOL processResult = CreateProcessW(
appWide, // application
argumentBuffer, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
File::GetCurrentDirectory().empty() ? NULL : directoryWide, // directory
&siStartInfo, // STARTUPINFO pointer
&m_Task ); // receives PROCESS_INFORMATION
delete[] argumentBuffer;
if( processResult == FALSE )
{
LogString("Error creating external process: " + winutils::ErrorCodeToMsg(GetLastError()));
return false;
}
if (!CloseHandle(m_Task.hThread))
LogString("Error closing external process thread: " + winutils::ErrorCodeToMsg(GetLastError()));
//
// Setup named pipe to external process
//
wchar_t pipeWide[kDefaultPathBufferSize];
ConvertUnityPathName("\\\\.\\pipe\\UnityVCS", pipeWide, kDefaultPathBufferSize);
memset(&m_Overlapped, 0, sizeof(m_Overlapped));
m_Event = CreateEvent(NULL, FALSE, FALSE, NULL);
m_Overlapped.hEvent = m_Event;
m_NamedPipe = CreateNamedPipeW(pipeWide,
/* FILE_FLAG_FIRST_PIPE_INSTANCE | */ FILE_FLAG_OVERLAPPED | PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 16384, 16384, 0, NULL);
if (m_NamedPipe == INVALID_HANDLE_VALUE)
{
LogString("Error creating named pipe: " + winutils::ErrorCodeToMsg(GetLastError()));
Cleanup();
return false;
}
int ret = ConnectNamedPipe(m_NamedPipe, &m_Overlapped);
if (ret != 0)
{
LogString("Error connecting named pipe: " + winutils::ErrorCodeToMsg(GetLastError()));
Cleanup();
return false;
}
switch (GetLastError()) {
case ERROR_PIPE_CONNECTED:
return true; // connected to external process
break;
case ERROR_IO_PENDING:
{
// Wait for connect for 30 secs
const int waitConnectTimeoutMs = 60000;
switch (WaitForSingleObject(m_Event, waitConnectTimeoutMs))
{
case WAIT_OBJECT_0:
{
DWORD dwIgnore;
ret = GetOverlappedResult(m_NamedPipe, &m_Overlapped, &dwIgnore, FALSE);
if (!ret)
{
LogString("Error waiting for named pipe connect: " + winutils::ErrorCodeToMsg(GetLastError()));
Cleanup();
return false;
}
return true; // connected to external process
}
case WAIT_TIMEOUT:
LogStringMsg("Timed out connecting pipe to external process: %s", m_ApplicationPath.c_str());
break;
case WAIT_FAILED:
LogString("Wait timout while connecting to named pipe:" + winutils::ErrorCodeToMsg(GetLastError()));
break;
default:
LogString("Unknown error while connecting pipe to external process");
break;
}
break;
}
default:
LogString("Unknown state while connecting pipe to external process");
break;
}
Cleanup();
return false;
}
bool ExternalProcess::IsRunning()
{
if (m_Task.hProcess == INVALID_HANDLE_VALUE)
return false;
DWORD result = WaitForSingleObject(m_Task.hProcess, 0);
if (result == WAIT_FAILED)
LogString("Error checking run state of external process: " + winutils::ErrorCodeToMsg(GetLastError()));
return result == WAIT_TIMEOUT;
}
// Copies the char array into a string, but ignores all \r
static void ConvertToString(CHAR* buffer, std::string& result)
{
result.clear();
for(int i = 0; ; ) {
CHAR c = buffer[i];
switch (c) {
case '\r':
break;
case 0:
return;
default:
result += (char)c;
}
i++;
}
}
string ExternalProcess::ReadLine()
{
if (m_LineBufferValid)
{
m_LineBufferValid = false;
return m_LineBuffer;
}
const DWORD kBufferSize = 1024;
static char buffer[kBufferSize + 1];
DWORD waitReadTimeoutMs = (DWORD)(m_ReadTimeout * 1000.0);
while (true)
{
if (!IsRunning())
{
throw ExternalProcessException(EPSTATE_NotRunning, "Trying to read from process that is not running");
}
std::string::size_type i = m_Buffer.find("\n");
if ( i != std::string::npos)
{
std::string result(m_Buffer, 0, i);
++i; // Eat \n
if (i >= m_Buffer.length())
m_Buffer.clear();
else
m_Buffer = m_Buffer.substr(i);
return result;
}
DWORD bytesRead = 0;
bool success = ReadFile( m_NamedPipe, buffer, kBufferSize, &bytesRead, &m_Overlapped );
if ( success )
{
if (bytesRead == 0)
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error reading external process - read 0 bytes");
}
else
{
switch (GetLastError())
{
case ERROR_IO_PENDING:
// Wait for data to be readable on pipe
switch (WaitForSingleObject(m_Event, waitReadTimeoutMs))
{
case WAIT_OBJECT_0:
{
bool success = GetOverlappedResult( m_NamedPipe, // handle to pipe
&m_Overlapped, // OVERLAPPED structure
&bytesRead, // bytes transferred
FALSE); // do not wait
if (!success)
{
LogString("Error waiting for read external process: " + winutils::ErrorCodeToMsg(GetLastError()));
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error waiting for read in external process");
}
if (bytesRead == 0)
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error reading any data from external process");
break;
}
case WAIT_TIMEOUT:
throw ExternalProcessException(EPSTATE_TimeoutReading, "Timeout reading from external process");
case WAIT_FAILED:
LogString("Timout waiting for read external process: " + winutils::ErrorCodeToMsg(GetLastError()));
throw ExternalProcessException(EPSTATE_BrokenPipe, "Failed waiting for read from external process");
default:
throw ExternalProcessException(EPSTATE_BrokenPipe, "Unknown error during read of external process");
}
break;
default:
LogString("Error reading externalprocess: " + winutils::ErrorCodeToMsg(GetLastError()));
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error read polling external process");
}
}
buffer[bytesRead] = 0;
string newData;
ConvertToString(buffer, newData);
m_Buffer += newData;
}
throw ExternalProcessException(EPSTATE_TimeoutReading, "Too many retries while reading from external process. This cannot happen.");
return string();
}
string ExternalProcess::PeekLine()
{
m_LineBuffer = ReadLine();
m_LineBufferValid = true;
return m_LineBuffer;
}
bool ExternalProcess::Write(const std::string& data)
{
const CHAR* buf = data.c_str();
DWORD written = 0;
size_t toWrite = data.length();
DWORD waitWriteTimeoutMs = (DWORD)(m_WriteTimeout * 1000.0);
while (true)
{
if (!IsRunning())
{
throw ExternalProcessException(EPSTATE_NotRunning, "Trying to write non running external process");
}
bool success = WriteFile(m_NamedPipe, buf, toWrite, &written, &m_Overlapped);
if ( success )
{
if (toWrite != written)
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error writing external process - not all bytes written");
break;
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
// Wait for data to be readable on pipe
switch (WaitForSingleObject(m_Event, waitWriteTimeoutMs))
{
case WAIT_OBJECT_0:
{
bool success = GetOverlappedResult( m_NamedPipe, // handle to pipe
&m_Overlapped, // OVERLAPPED structure
&written, // bytes transferred
FALSE); // do not wait
if (!success)
{
LogString("Error waiting to write external process: " + winutils::ErrorCodeToMsg(GetLastError()));
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error waiting for write in external process");
}
if (written != toWrite)
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error writing all data to external process");
return true;
}
case WAIT_TIMEOUT:
throw ExternalProcessException(EPSTATE_TimeoutReading, "Timeout writing to external process");
case WAIT_FAILED:
LogString("Failed waiting to write external process: " + winutils::ErrorCodeToMsg(GetLastError()));
throw ExternalProcessException(EPSTATE_BrokenPipe, "Failed waiting for write to external process");
default:
throw ExternalProcessException(EPSTATE_BrokenPipe, "Unknown error during write to external process");
}
}
else
{
LogString("Error writing external process: " + winutils::ErrorCodeToMsg(GetLastError()));
throw ExternalProcessException(EPSTATE_BrokenPipe, "Error write polling external process");
}
}
}
return true;
}
void ExternalProcess::Shutdown()
{
// Wait until exit is needed in order for the task to properly deallocate
if (IsRunning() && m_NamedPipe != INVALID_HANDLE_VALUE)
{
FlushFileBuffers(m_NamedPipe);
DisconnectNamedPipe(m_NamedPipe);
}
Cleanup();
}
void ExternalProcess::SetReadTimeout(double secs)
{
m_ReadTimeout = secs;
}
double ExternalProcess::GetReadTimeout()
{
return m_ReadTimeout;
}
void ExternalProcess::SetWriteTimeout(double secs)
{
m_WriteTimeout = secs;
}
double ExternalProcess::GetWriteTimeout()
{
return m_WriteTimeout;
}
void ExternalProcess::Cleanup()
{
if (m_Event != INVALID_HANDLE_VALUE && !CloseHandle(m_Event))
LogString("Error cleaning up external process event: " + winutils::ErrorCodeToMsg(GetLastError()));
m_Event = INVALID_HANDLE_VALUE;
if (m_NamedPipe != INVALID_HANDLE_VALUE && !CloseHandle(m_NamedPipe))
LogString("Error cleaning up external process named pipe: " + winutils::ErrorCodeToMsg(GetLastError()));
m_NamedPipe = INVALID_HANDLE_VALUE;
if (m_Task.hProcess != INVALID_HANDLE_VALUE && !TerminateProcess(m_Task.hProcess, 1) && !CloseHandle(m_Task.hProcess))
LogString("Error cleaning up external process: " + winutils::ErrorCodeToMsg(GetLastError()));
m_Task.hProcess = INVALID_HANDLE_VALUE;
}

145
Test/Source/TestServer.cpp Normal file
Просмотреть файл

@ -0,0 +1,145 @@
#include "ExternalProcess.h"
#include <iostream>
#include <fstream>
using namespace std;
void printStatus(bool ok);
int main(int argc, char* argv[])
{
bool verbose = argc > 3 ? string(argv[3]) == "verbose" : false;
if (verbose)
cout << "Pllugin : " << argv[1] << endl;
cout << "Testing " << argv[2] << " ";
if (verbose)
cout << endl;
vector<string> arguments;
ExternalProcess p(argv[1], arguments);
p.Launch();
ifstream testscript(argv[2]);
const int BUFSIZE = 4096;
char buf[BUFSIZE];
const string restartline = "<restartplugin>";
const string commanddelim = "--";
const string expectdelim = "--";
const string matchtoken = "re:";
const string regextoken = "==:";
const string exittoken = "<exit>";
bool ok = true;
while (!testscript.eof())
{
// Just forward the command to the plugin
if (restartline == buf)
{
if (verbose)
cout << "Restarting plugin";
p = ExternalProcess(argv[1], arguments);
p.Launch();
continue;
}
while (testscript.getline(buf, BUFSIZE))
{
string command(buf);
if (verbose)
cout << command << endl;
if (command.find(commanddelim) == 0)
break; // done command lines
if (command.find(exittoken) == 0)
return 0;
if (command.find(restartline) == 0)
{
p = ExternalProcess(argv[1], arguments);
p.Launch();
continue;
}
p.Write(buf);
p.Write("\n");
}
while (testscript.getline(buf, BUFSIZE))
{
string expect(buf);
if (verbose)
cout << expect << endl;
if (expect.find(expectdelim) == 0)
break; // done expect lines
if (expect.find(exittoken) == 0)
return 0;
if (expect.find(restartline) == 0)
{
p = ExternalProcess(argv[1], arguments);
p.Launch();
continue;
}
string msg = p.ReadLine();
if (expect.find(regextoken) == 0)
{
// TODO: implement regex match
}
else
{
// Optional match token
if (expect.find(matchtoken) == 0)
expect = expect.substr(0, matchtoken.length());
if (expect != msg)
{
ok = false;
printStatus(ok);
cerr << "Output fail: expected '" << expect << "'" << endl;
cerr << " got '" << msg << "'" << endl;
// Read as much as possible from plugin and stop
p.SetReadTimeout(0.3);
try
{
cerr << " reading as much as possible from plugin:" << endl;
do {
string l = p.ReadLine();
cerr << l << endl;
} while (true);
} catch (...)
{
return 1;
}
}
}
}
}
printStatus(ok);
return 0;
}
void printStatus(bool ok)
{
const char * redColor = "\033[;1;31m";
const char * greenColor = "\033[;1;32m";
const char * endColor = "\033[0m";
if (ok)
cout << greenColor << "OK" << endColor << endl;
else
cout << redColor << "Failed" << endColor << endl;
}

1
Test/run_tests.bat Normal file
Просмотреть файл

@ -0,0 +1 @@
TODO: Make bat file

10
Test/run_tests.sh Executable file
Просмотреть файл

@ -0,0 +1,10 @@
#!/bin/bash
SERVEREXEC=$1
PLUGINEXEC=$2
VERBOSE=$3
TESTS=${*:4}
for i in $TESTS ; do
$SERVEREXEC $PLUGINEXEC "$i" $VERBOSE;
done