Starting compliance test framework. Mac only for now.
This commit is contained in:
Родитель
141675735c
Коммит
0e9c80498e
Двоичный файл не отображается.
26
Makefile.osx
26
Makefile.osx
|
@ -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
|
||||
|
@ -24,12 +27,25 @@ default: all
|
|||
|
||||
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 =
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
TODO: Make bat 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
|
Загрузка…
Ссылка в новой задаче