Import dEQP.
Import drawElements Quality Program from an internal repository.
Bug: 17388917
Change-Id: Ic109fe4a57e31b2a816113d90fbdf51a43e7abeb
diff --git a/execserver/CMakeLists.txt b/execserver/CMakeLists.txt
new file mode 100644
index 0000000..b6d0005
--- /dev/null
+++ b/execserver/CMakeLists.txt
@@ -0,0 +1,54 @@
+# ExecServer
+
+set(XSCORE_SRCS
+ xsDefs.cpp
+ xsDefs.hpp
+ xsExecutionServer.cpp
+ xsExecutionServer.hpp
+ xsPosixFileReader.cpp
+ xsPosixFileReader.hpp
+ xsPosixTestProcess.cpp
+ xsPosixTestProcess.hpp
+ xsProtocol.cpp
+ xsProtocol.hpp
+ xsTcpServer.cpp
+ xsTcpServer.hpp
+ xsTestDriver.cpp
+ xsTestDriver.hpp
+ xsTestProcess.cpp
+ xsTestProcess.hpp
+ )
+
+set(XSCORE_LIBS
+ decpp
+ deutil
+ dethread
+ debase
+ )
+
+if (DE_OS_IS_WIN32)
+ set(XSCORE_SRCS
+ ${XSCORE_SRCS}
+ xsWin32TestProcess.cpp
+ xsWin32TestProcess.hpp)
+endif ()
+
+add_library(xscore STATIC ${XSCORE_SRCS})
+target_link_libraries(xscore ${XSCORE_LIBS})
+
+include_directories(.)
+
+if (DE_OS_IS_WIN32 OR DE_OS_IS_OSX OR DE_OS_IS_UNIX)
+ # Build standalone execserver binary
+ add_executable(execserver tools/xsMain.cpp)
+ target_link_libraries(execserver xscore)
+
+ # Tests
+ add_executable(execserver-test tools/xsTest.cpp)
+ target_link_libraries(execserver-test xscore)
+ add_dependencies(execserver-test execserver)
+
+ # Basic client
+ add_executable(execserver-client tools/xsClient.cpp)
+ target_link_libraries(execserver-client xscore)
+endif ()
diff --git a/execserver/tools/xsClient.cpp b/execserver/tools/xsClient.cpp
new file mode 100644
index 0000000..eb71fe6
--- /dev/null
+++ b/execserver/tools/xsClient.cpp
@@ -0,0 +1,356 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ExecServer Client.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "xsProtocol.hpp"
+#include "deSocket.hpp"
+
+#include "deString.h"
+
+#include <memory>
+#include <sstream>
+#include <fstream>
+#include <cstdio>
+#include <cstdlib>
+
+using std::string;
+using std::vector;
+
+namespace xs
+{
+
+typedef std::auto_ptr<Message> ScopedMsgPtr;
+
+class SocketError : public Error
+{
+public:
+ SocketError (deSocketResult result, const char* message, const char* file, int line)
+ : Error (message, deGetSocketResultName(result), file, line)
+ , m_result (result)
+ {
+ }
+
+ deSocketResult getResult (void) const
+ {
+ return m_result;
+ }
+
+private:
+ deSocketResult m_result;
+};
+
+// Helpers.
+void sendMessage (de::Socket& socket, const Message& message)
+{
+ // Format message.
+ vector<deUint8> buf;
+ message.write(buf);
+
+ // Write to socket.
+ int pos = 0;
+ while (pos < (int)buf.size())
+ {
+ int numLeft = (int)buf.size() - pos;
+ int numSent = 0;
+ deSocketResult result = socket.send(&buf[pos], numLeft, &numSent);
+
+ if (result != DE_SOCKETRESULT_SUCCESS)
+ throw SocketError(result, "send() failed", __FILE__, __LINE__);
+
+ pos += numSent;
+ }
+}
+
+void readBytes (de::Socket& socket, vector<deUint8>& dst, int numBytes)
+{
+ int numRead = 0;
+ dst.resize(numBytes);
+ while (numRead < numBytes)
+ {
+ int numLeft = numBytes - numRead;
+ int curNumRead = 0;
+ deSocketResult result = socket.receive(&dst[numRead], numLeft, &curNumRead);
+
+ if (result != DE_SOCKETRESULT_SUCCESS)
+ throw SocketError(result, "receive() failed", __FILE__, __LINE__);
+
+ numRead += curNumRead;
+ }
+}
+
+Message* readMessage (de::Socket& socket)
+{
+ // Header.
+ vector<deUint8> header;
+ readBytes(socket, header, MESSAGE_HEADER_SIZE);
+
+ MessageType type;
+ int messageSize;
+ Message::parseHeader(&header[0], (int)header.size(), type, messageSize);
+
+ // Simple messages without any data.
+ switch (type)
+ {
+ case MESSAGETYPE_KEEPALIVE: return new KeepAliveMessage();
+ case MESSAGETYPE_PROCESS_STARTED: return new ProcessStartedMessage();
+ default:
+ break; // Read message with data.
+ }
+
+ vector<deUint8> messageBuf;
+ readBytes(socket, messageBuf, messageSize-MESSAGE_HEADER_SIZE);
+
+ switch (type)
+ {
+ case MESSAGETYPE_HELLO: return new HelloMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_TEST: return new TestMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_PROCESS_LOG_DATA: return new ProcessLogDataMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_INFO: return new InfoMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_PROCESS_LAUNCH_FAILED: return new ProcessLaunchFailedMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_PROCESS_FINISHED: return new ProcessFinishedMessage(&messageBuf[0], (int)messageBuf.size());
+ default:
+ XS_FAIL("Unknown message");
+ }
+}
+
+class CommandLine
+{
+public:
+ de::SocketAddress address;
+ std::string program;
+ std::string params;
+ std::string workingDir;
+ std::string caseList;
+ std::string dstFileName;
+};
+
+class Client
+{
+public:
+ Client (const CommandLine& cmdLine);
+ ~Client (void);
+
+ void run (void);
+
+private:
+ const CommandLine& m_cmdLine;
+ de::Socket m_socket;
+};
+
+Client::Client (const CommandLine& cmdLine)
+ : m_cmdLine(cmdLine)
+{
+}
+
+Client::~Client (void)
+{
+}
+
+void Client::run (void)
+{
+ // Connect to server.
+ m_socket.connect(m_cmdLine.address);
+
+ printf("Connected to %s:%d!\n", m_cmdLine.address.getHost(), m_cmdLine.address.getPort());
+
+ // Open result file.
+ std::fstream out(m_cmdLine.dstFileName.c_str(), std::fstream::out|std::fstream::binary);
+
+ printf(" writing to %s\n", m_cmdLine.dstFileName.c_str());
+
+ // Send execution request.
+ {
+ ExecuteBinaryMessage msg;
+
+ msg.name = m_cmdLine.program;
+ msg.params = m_cmdLine.params;
+ msg.workDir = m_cmdLine.workingDir;
+ msg.caseList = m_cmdLine.caseList;
+
+ sendMessage(m_socket, msg);
+ printf(" execution request sent.\n");
+ }
+
+ // Run client loop.
+ bool isRunning = true;
+ while (isRunning)
+ {
+ ScopedMsgPtr msg(readMessage(m_socket));
+
+ switch (msg->type)
+ {
+ case MESSAGETYPE_HELLO:
+ printf(" HelloMessage\n");
+ break;
+
+ case MESSAGETYPE_KEEPALIVE:
+ {
+ printf(" KeepAliveMessage\n");
+
+ // Reply with keepalive.
+ sendMessage(m_socket, KeepAliveMessage());
+ break;
+ }
+
+ case MESSAGETYPE_INFO:
+ printf(" InfoMessage: '%s'\n", static_cast<InfoMessage*>(msg.get())->info.c_str());
+ break;
+
+ case MESSAGETYPE_PROCESS_STARTED:
+ printf(" ProcessStartedMessage\n");
+ break;
+
+ case MESSAGETYPE_PROCESS_FINISHED:
+ printf(" ProcessFinished: exit code = %d\n", static_cast<ProcessFinishedMessage*>(msg.get())->exitCode);
+ isRunning = false;
+ break;
+
+ case MESSAGETYPE_PROCESS_LAUNCH_FAILED:
+ printf(" ProcessLaunchFailed: '%s'\n", static_cast<ProcessLaunchFailedMessage*>(msg.get())->reason.c_str());
+ isRunning = false;
+ break;
+
+ case MESSAGETYPE_PROCESS_LOG_DATA:
+ {
+ ProcessLogDataMessage* logDataMsg = static_cast<ProcessLogDataMessage*>(msg.get());
+ printf(" ProcessLogDataMessage: %d bytes\n", (int)logDataMsg->logData.length());
+ out << logDataMsg->logData;
+ break;
+ }
+
+ default:
+ XS_FAIL("Unknown message");
+ break;
+ }
+ }
+
+ // Close output file.
+ out.close();
+
+ // Close connection.
+ m_socket.shutdown();
+ m_socket.close();
+
+ printf("Done!\n");
+}
+
+string parseString (const char* str)
+{
+ if (str[0] == '\'' || str[0] == '"')
+ {
+ const char* p = str;
+ char endChar = *p++;
+ std::ostringstream o;
+
+ while (*p != endChar && *p)
+ {
+ if (*p == '\\')
+ {
+ switch (p[1])
+ {
+ case 0: DE_ASSERT(DE_FALSE); break;
+ case 'n': o << '\n'; break;
+ case 't': o << '\t'; break;
+ default: o << p[1]; break;
+ }
+
+ p += 2;
+ }
+ else
+ o << *p++;
+ }
+
+ return o.str();
+ }
+ else
+ return string(str);
+}
+
+void printHelp (const char* binName)
+{
+ printf("%s:\n", binName);
+ printf(" --host=[host] Connect to host [host]\n");
+ printf(" --port=[name] Use port [port]\n");
+ printf(" --program=[program] Test program\n");
+ printf(" --params=[params] Test program params\n");
+ printf(" --workdir=[dir] Working directory\n");
+ printf(" --caselist=[caselist] Test case list\n");
+ printf(" --out=filename Test result file\n");
+}
+
+int runClient (int argc, const char* const* argv)
+{
+ CommandLine cmdLine;
+
+ // Defaults.
+ cmdLine.address.setHost("127.0.0.1");
+ cmdLine.address.setPort(50016);
+ cmdLine.dstFileName = "TestResults.qpa";
+
+ // Parse command line.
+ for (int argNdx = 1; argNdx < argc; argNdx++)
+ {
+ const char* arg = argv[argNdx];
+
+ if (deStringBeginsWith(arg, "--port="))
+ cmdLine.address.setPort(atoi(arg+7));
+ else if (deStringBeginsWith(arg, "--host="))
+ cmdLine.address.setHost(parseString(arg+7).c_str());
+ else if (deStringBeginsWith(arg, "--program="))
+ cmdLine.program = parseString(arg+10);
+ else if (deStringBeginsWith(arg, "--params="))
+ cmdLine.params = parseString(arg+9);
+ else if (deStringBeginsWith(arg, "--workdir="))
+ cmdLine.workingDir = parseString(arg+10);
+ else if (deStringBeginsWith(arg, "--caselist="))
+ cmdLine.caseList = parseString(arg+11);
+ else if (deStringBeginsWith(arg, "--out="))
+ cmdLine.dstFileName = parseString(arg+6);
+ else
+ {
+ printHelp(argv[0]);
+ return -1;
+ }
+ }
+
+ // Run client.
+ try
+ {
+ Client client(cmdLine);
+ client.run();
+ }
+ catch (const std::exception& e)
+ {
+ printf("%s\n", e.what());
+ return -1;
+ }
+
+ return 0;
+}
+
+} // xs
+
+int main (int argc, const char* const* argv)
+{
+ return xs::runClient(argc, argv);
+}
diff --git a/execserver/tools/xsMain.cpp b/execserver/tools/xsMain.cpp
new file mode 100644
index 0000000..7027e25
--- /dev/null
+++ b/execserver/tools/xsMain.cpp
@@ -0,0 +1,78 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ExecServer main().
+ *//*--------------------------------------------------------------------*/
+
+#include "xsExecutionServer.hpp"
+#include "deString.h"
+
+#if (DE_OS == DE_OS_WIN32)
+# include "xsWin32TestProcess.hpp"
+#else
+# include "xsPosixTestProcess.hpp"
+#endif
+
+#include <cstdlib>
+#include <cstdio>
+
+int main (int argc, const char* const* argv)
+{
+ xs::ExecutionServer::RunMode runMode = xs::ExecutionServer::RUNMODE_FOREVER;
+ int port = 50016;
+
+#if (DE_OS == DE_OS_WIN32)
+ xs::Win32TestProcess testProcess;
+#else
+ xs::PosixTestProcess testProcess;
+#endif
+
+ DE_STATIC_ASSERT(sizeof("a") == 2);
+
+#if (DE_OS != DE_OS_WIN32)
+ // Set line buffered mode to stdout so executor gets any log messages soon enough.
+ setvbuf(stdout, DE_NULL, _IOLBF, 4*1024);
+#endif
+
+ // Parse command line.
+ for (int argNdx = 1; argNdx < argc; argNdx++)
+ {
+ const char* arg = argv[argNdx];
+
+ if (deStringBeginsWith(arg, "--port="))
+ port = atoi(arg+sizeof("--port=")-1);
+ else if (deStringEqual(arg, "--single"))
+ runMode = xs::ExecutionServer::RUNMODE_SINGLE_EXEC;
+ }
+
+ try
+ {
+ xs::ExecutionServer server(&testProcess, DE_SOCKETFAMILY_INET4, port, runMode);
+ printf("Listening on port %d.\n", port);
+ server.runServer();
+ }
+ catch (const std::exception& e)
+ {
+ printf("%s\n", e.what());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/execserver/tools/xsTest.cpp b/execserver/tools/xsTest.cpp
new file mode 100644
index 0000000..38b6120
--- /dev/null
+++ b/execserver/tools/xsTest.cpp
@@ -0,0 +1,1033 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ExecServer Tests.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+
+#include "xsProtocol.hpp"
+#include "deSocket.hpp"
+#include "deRingBuffer.hpp"
+#include "deFilePath.hpp"
+#include "deBlockBuffer.hpp"
+#include "deThread.hpp"
+#include "deStringUtil.hpp"
+
+#include "deClock.h"
+#include "deProcess.h"
+#include "deString.h"
+#include "deRandom.h"
+
+#include <memory>
+#include <algorithm>
+
+using std::string;
+using std::vector;
+
+namespace xs
+{
+
+typedef std::auto_ptr<Message> ScopedMsgPtr;
+
+class SocketError : public Error
+{
+public:
+ SocketError (deSocketResult result, const char* message, const char* file, int line)
+ : Error (message, deGetSocketResultName(result), file, line)
+ , m_result (result)
+ {
+ }
+
+ deSocketResult getResult (void) const
+ {
+ return m_result;
+ }
+
+private:
+ deSocketResult m_result;
+};
+
+// Helpers.
+void sendMessage (de::Socket& socket, const Message& message)
+{
+ // Format message.
+ vector<deUint8> buf;
+ message.write(buf);
+
+ // Write to socket.
+ int pos = 0;
+ while (pos < (int)buf.size())
+ {
+ int numLeft = (int)buf.size() - pos;
+ int numSent = 0;
+ deSocketResult result = socket.send(&buf[pos], numLeft, &numSent);
+
+ if (result != DE_SOCKETRESULT_SUCCESS)
+ throw SocketError(result, "send() failed", __FILE__, __LINE__);
+
+ pos += numSent;
+ }
+}
+
+void readBytes (de::Socket& socket, vector<deUint8>& dst, int numBytes)
+{
+ int numRead = 0;
+ dst.resize(numBytes);
+ while (numRead < numBytes)
+ {
+ int numLeft = numBytes - numRead;
+ int curNumRead = 0;
+ deSocketResult result = socket.receive(&dst[numRead], numLeft, &curNumRead);
+
+ if (result != DE_SOCKETRESULT_SUCCESS)
+ throw SocketError(result, "receive() failed", __FILE__, __LINE__);
+
+ numRead += curNumRead;
+ }
+}
+
+Message* readMessage (de::Socket& socket)
+{
+ // Header.
+ vector<deUint8> header;
+ readBytes(socket, header, MESSAGE_HEADER_SIZE);
+
+ MessageType type;
+ int messageSize;
+ Message::parseHeader(&header[0], (int)header.size(), type, messageSize);
+
+ // Simple messages without any data.
+ switch (type)
+ {
+ case MESSAGETYPE_KEEPALIVE: return new KeepAliveMessage();
+ case MESSAGETYPE_PROCESS_STARTED: return new ProcessStartedMessage();
+ default:
+ break; // Read message with data.
+ }
+
+ vector<deUint8> messageBuf;
+ readBytes(socket, messageBuf, messageSize-MESSAGE_HEADER_SIZE);
+
+ switch (type)
+ {
+ case MESSAGETYPE_HELLO: return new HelloMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_TEST: return new TestMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_PROCESS_LOG_DATA: return new ProcessLogDataMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_INFO: return new InfoMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_PROCESS_LAUNCH_FAILED: return new ProcessLaunchFailedMessage(&messageBuf[0], (int)messageBuf.size());
+ case MESSAGETYPE_PROCESS_FINISHED: return new ProcessFinishedMessage(&messageBuf[0], (int)messageBuf.size());
+ default:
+ XS_FAIL("Unknown message");
+ }
+}
+
+class TestClock
+{
+public:
+ inline TestClock (void)
+ {
+ reset();
+ }
+
+ inline void reset (void)
+ {
+ m_initTime = deGetMicroseconds();
+ }
+
+ inline int getMilliseconds (void)
+ {
+ return (int)((deGetMicroseconds() - m_initTime) / 1000);
+ }
+
+private:
+ deUint64 m_initTime;
+};
+
+class TestContext
+{
+public:
+ TestContext (void) : startServer(false) {}
+
+ std::string serverPath;
+ std::string testerPath;
+ de::SocketAddress address;
+ bool startServer;
+
+ // Passed from execserver.
+ std::string logFileName;
+ std::string caseList;
+
+private:
+ TestContext (const TestContext& other);
+ TestContext& operator= (const TestContext& other);
+};
+
+class TestCase
+{
+public:
+ TestCase (TestContext& testCtx, const char* name) : m_testCtx(testCtx), m_name(name) {}
+ virtual ~TestCase (void) {}
+
+ const char* getName (void) const { return m_name.c_str(); }
+
+ virtual void runClient (de::Socket& socket) = DE_NULL;
+ virtual void runProgram (void) = DE_NULL;
+
+protected:
+ TestContext& m_testCtx;
+ std::string m_name;
+};
+
+class TestExecutor
+{
+public:
+ TestExecutor (TestContext& testCtx);
+ ~TestExecutor (void);
+
+ void runCases (const std::vector<TestCase*>& testCases);
+ bool runCase (TestCase* testCase);
+
+private:
+ TestContext& m_testCtx;
+};
+
+TestExecutor::TestExecutor (TestContext& testCtx)
+ : m_testCtx(testCtx)
+{
+}
+
+TestExecutor::~TestExecutor (void)
+{
+}
+
+void TestExecutor::runCases (const std::vector<TestCase*>& testCases)
+{
+ int numPassed = 0;
+ int numCases = (int)testCases.size();
+
+ for (std::vector<TestCase*>::const_iterator i = testCases.begin(); i != testCases.end(); i++)
+ {
+ if (runCase(*i))
+ numPassed += 1;
+ }
+
+ printf("\n %d/%d passed!\n", numPassed, numCases);
+}
+
+class FilePrinter : public de::Thread
+{
+public:
+ FilePrinter (void)
+ : m_curFile(DE_NULL)
+ {
+ }
+
+ void start (deFile* file)
+ {
+ DE_ASSERT(!m_curFile);
+ m_curFile = file;
+ de::Thread::start();
+ }
+
+ void run (void)
+ {
+ char buf[256];
+ deInt64 numRead = 0;
+
+ while (deFile_read(m_curFile, &buf[0], (deInt64)sizeof(buf), &numRead) == DE_FILERESULT_SUCCESS)
+ fwrite(&buf[0], 1, (size_t)numRead, stdout);
+
+ m_curFile = DE_NULL;
+ }
+
+private:
+ deFile* m_curFile;
+};
+
+bool TestExecutor::runCase (TestCase* testCase)
+{
+ printf("%s\n", testCase->getName());
+
+ bool success = false;
+ deProcess* serverProc = DE_NULL;
+ FilePrinter stdoutPrinter;
+ FilePrinter stderrPrinter;
+
+ try
+ {
+ if (m_testCtx.startServer)
+ {
+ string cmdLine = m_testCtx.serverPath + " --port=" + de::toString(m_testCtx.address.getPort());
+ serverProc = deProcess_create();
+ XS_CHECK(serverProc);
+
+ if (!deProcess_start(serverProc, cmdLine.c_str(), DE_NULL))
+ {
+ string errMsg = deProcess_getLastError(serverProc);
+ deProcess_destroy(serverProc);
+ XS_FAIL(errMsg.c_str());
+ }
+
+ deSleep(200); /* Give 200ms for server to start. */
+ XS_CHECK(deProcess_isRunning(serverProc));
+
+ // Start stdout/stderr printers.
+ stdoutPrinter.start(deProcess_getStdOut(serverProc));
+ stderrPrinter.start(deProcess_getStdErr(serverProc));
+ }
+
+ // Connect.
+ de::Socket socket;
+ socket.connect(m_testCtx.address);
+
+ // Flags.
+ socket.setFlags(DE_SOCKET_CLOSE_ON_EXEC);
+
+ // Run case.
+ testCase->runClient(socket);
+
+ // Disconnect.
+ if (socket.isConnected())
+ socket.shutdown();
+
+ // Kill server.
+ if (serverProc && deProcess_isRunning(serverProc))
+ {
+ XS_CHECK(deProcess_terminate(serverProc));
+ deSleep(100);
+ XS_CHECK(deProcess_waitForFinish(serverProc));
+
+ stdoutPrinter.join();
+ stderrPrinter.join();
+ }
+
+ success = true;
+ }
+ catch (const std::exception& e)
+ {
+ printf("FAIL: %s\n\n", e.what());
+ }
+
+ if (serverProc)
+ deProcess_destroy(serverProc);
+
+ return success;
+}
+
+class ConnectTest : public TestCase
+{
+public:
+ ConnectTest (TestContext& testCtx)
+ : TestCase(testCtx, "connect")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ DE_UNREF(socket);
+ }
+
+ void runProgram (void) { /* nothing */ }
+};
+
+class HelloTest : public TestCase
+{
+public:
+ HelloTest (TestContext& testCtx)
+ : TestCase(testCtx, "hello")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ xs::HelloMessage msg;
+ sendMessage(socket, (const xs::Message&)msg);
+ }
+
+ void runProgram (void) { /* nothing */ }
+};
+
+class ExecFailTest : public TestCase
+{
+public:
+ ExecFailTest (TestContext& testCtx)
+ : TestCase(testCtx, "exec-fail")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ xs::ExecuteBinaryMessage execMsg;
+ execMsg.name = "foobar-notfound";
+ execMsg.params = "";
+ execMsg.caseList = "";
+ execMsg.workDir = "";
+
+ sendMessage(socket, execMsg);
+
+ const int timeout = 100; // 100ms.
+ TestClock clock;
+
+ for (;;)
+ {
+ if (clock.getMilliseconds() > timeout)
+ XS_FAIL("Didn't receive PROCESS_LAUNCH_FAILED");
+
+ ScopedMsgPtr msg(readMessage(socket));
+
+ if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
+ break;
+ else if (msg->type == MESSAGETYPE_KEEPALIVE)
+ continue;
+ else
+ XS_FAIL("Invalid message");
+ }
+ }
+
+ void runProgram (void) { /* nothing */ }
+};
+
+class SimpleExecTest : public TestCase
+{
+public:
+ SimpleExecTest (TestContext& testCtx)
+ : TestCase(testCtx, "simple-exec")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ xs::ExecuteBinaryMessage execMsg;
+ execMsg.name = m_testCtx.testerPath;
+ execMsg.params = "--program=simple-exec";
+ execMsg.caseList = "";
+ execMsg.workDir = "";
+
+ sendMessage(socket, execMsg);
+
+ const int timeout = 5000; // 5s.
+ TestClock clock;
+
+ bool gotProcessStarted = false;
+ bool gotProcessFinished = false;
+
+ for (;;)
+ {
+ if (clock.getMilliseconds() > timeout)
+ break;
+
+ ScopedMsgPtr msg(readMessage(socket));
+
+ if (msg->type == MESSAGETYPE_PROCESS_STARTED)
+ gotProcessStarted = true;
+ else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
+ XS_FAIL("Got PROCESS_LAUNCH_FAILED");
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
+ {
+ gotProcessFinished = true;
+ break;
+ }
+ else if (msg->type == MESSAGETYPE_KEEPALIVE || msg->type == MESSAGETYPE_INFO)
+ continue;
+ else
+ XS_FAIL((string("Invalid message: ") + de::toString(msg->type)).c_str());
+ }
+
+ if (!gotProcessStarted)
+ XS_FAIL("Did't get PROCESS_STARTED message");
+
+ if (!gotProcessFinished)
+ XS_FAIL("Did't get PROCESS_FINISHED message");
+ }
+
+ void runProgram (void) { /* print nothing. */ }
+};
+
+class InfoTest : public TestCase
+{
+public:
+ std::string infoStr;
+
+ InfoTest (TestContext& testCtx)
+ : TestCase (testCtx, "info")
+ , infoStr ("Hello, World")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ xs::ExecuteBinaryMessage execMsg;
+ execMsg.name = m_testCtx.testerPath;
+ execMsg.params = "--program=info";
+ execMsg.caseList = "";
+ execMsg.workDir = "";
+
+ sendMessage(socket, execMsg);
+
+ const int timeout = 10000; // 10s.
+ TestClock clock;
+
+ bool gotProcessStarted = false;
+ bool gotProcessFinished = false;
+ std::string receivedInfo = "";
+
+ for (;;)
+ {
+ if (clock.getMilliseconds() > timeout)
+ break;
+
+ ScopedMsgPtr msg(readMessage(socket));
+
+ if (msg->type == MESSAGETYPE_PROCESS_STARTED)
+ gotProcessStarted = true;
+ else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
+ XS_FAIL("Got PROCESS_LAUNCH_FAILED");
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_INFO)
+ receivedInfo += static_cast<const InfoMessage*>(msg.get())->info;
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
+ {
+ gotProcessFinished = true;
+ break;
+ }
+ else if (msg->type == MESSAGETYPE_KEEPALIVE)
+ continue;
+ else
+ XS_FAIL("Invalid message");
+ }
+
+ if (!gotProcessStarted)
+ XS_FAIL("Did't get PROCESS_STARTED message");
+
+ if (!gotProcessFinished)
+ XS_FAIL("Did't get PROCESS_FINISHED message");
+
+ if (receivedInfo != infoStr)
+ XS_FAIL("Info data doesn't match");
+ }
+
+ void runProgram (void) { printf("%s", infoStr.c_str()); }
+};
+
+class LogDataTest : public TestCase
+{
+public:
+ LogDataTest (TestContext& testCtx)
+ : TestCase(testCtx, "logdata")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ xs::ExecuteBinaryMessage execMsg;
+ execMsg.name = m_testCtx.testerPath;
+ execMsg.params = "--program=logdata";
+ execMsg.caseList = "";
+ execMsg.workDir = "";
+
+ sendMessage(socket, execMsg);
+
+ const int timeout = 10000; // 10s.
+ TestClock clock;
+
+ bool gotProcessStarted = false;
+ bool gotProcessFinished = false;
+ std::string receivedData = "";
+
+ for (;;)
+ {
+ if (clock.getMilliseconds() > timeout)
+ break;
+
+ ScopedMsgPtr msg(readMessage(socket));
+
+ if (msg->type == MESSAGETYPE_PROCESS_STARTED)
+ gotProcessStarted = true;
+ else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
+ XS_FAIL("Got PROCESS_LAUNCH_FAILED");
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_LOG_DATA)
+ receivedData += static_cast<const ProcessLogDataMessage*>(msg.get())->logData;
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
+ {
+ gotProcessFinished = true;
+ break;
+ }
+ else if (msg->type == MESSAGETYPE_KEEPALIVE)
+ continue;
+ else if (msg->type == MESSAGETYPE_INFO)
+ XS_FAIL(static_cast<const InfoMessage*>(msg.get())->info.c_str());
+ else
+ XS_FAIL("Invalid message");
+ }
+
+ if (!gotProcessStarted)
+ XS_FAIL("Did't get PROCESS_STARTED message");
+
+ if (!gotProcessFinished)
+ XS_FAIL("Did't get PROCESS_FINISHED message");
+
+ const char* expected = "Foo\nBar\n";
+ if (receivedData != expected)
+ {
+ printf(" received: '%s'\n expected: '%s'\n", receivedData.c_str(), expected);
+ XS_FAIL("Log data doesn't match");
+ }
+ }
+
+ void runProgram (void)
+ {
+ deFile* file = deFile_create(m_testCtx.logFileName.c_str(), DE_FILEMODE_OPEN|DE_FILEMODE_CREATE|DE_FILEMODE_TRUNCATE|DE_FILEMODE_WRITE);
+ XS_CHECK(file);
+
+ const char line0[] = "Foo\n";
+ const char line1[] = "Bar\n";
+ deInt64 numWritten = 0;
+
+ // Write first line.
+ XS_CHECK(deFile_write(file, line0, sizeof(line0)-1, &numWritten) == DE_FILERESULT_SUCCESS);
+ XS_CHECK(numWritten == sizeof(line0)-1);
+
+ // Sleep for 0.5s and write line 2.
+ deSleep(500);
+ XS_CHECK(deFile_write(file, line1, sizeof(line1)-1, &numWritten) == DE_FILERESULT_SUCCESS);
+ XS_CHECK(numWritten == sizeof(line1)-1);
+
+ deFile_destroy(file);
+ }
+};
+
+class BigLogDataTest : public TestCase
+{
+public:
+ enum
+ {
+ DATA_SIZE = 100*1024*1024
+ };
+
+ BigLogDataTest (TestContext& testCtx)
+ : TestCase(testCtx, "biglogdata")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ xs::ExecuteBinaryMessage execMsg;
+ execMsg.name = m_testCtx.testerPath;
+ execMsg.params = "--program=biglogdata";
+ execMsg.caseList = "";
+ execMsg.workDir = "";
+
+ sendMessage(socket, execMsg);
+
+ const int timeout = 30000; // 30s.
+ TestClock clock;
+
+ bool gotProcessStarted = false;
+ bool gotProcessFinished = false;
+ int receivedBytes = 0;
+
+ for (;;)
+ {
+ if (clock.getMilliseconds() > timeout)
+ break;
+
+ ScopedMsgPtr msg(readMessage(socket));
+
+ if (msg->type == MESSAGETYPE_PROCESS_STARTED)
+ gotProcessStarted = true;
+ else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
+ XS_FAIL("Got PROCESS_LAUNCH_FAILED");
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_LOG_DATA)
+ receivedBytes += (int)static_cast<const ProcessLogDataMessage*>(msg.get())->logData.length();
+ else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
+ {
+ gotProcessFinished = true;
+ break;
+ }
+ else if (msg->type == MESSAGETYPE_KEEPALIVE)
+ {
+ // Reply with keepalive.
+ sendMessage(socket, KeepAliveMessage());
+ continue;
+ }
+ else if (msg->type == MESSAGETYPE_INFO)
+ printf("%s", static_cast<const InfoMessage*>(msg.get())->info.c_str());
+ else
+ XS_FAIL("Invalid message");
+ }
+
+ if (!gotProcessStarted)
+ XS_FAIL("Did't get PROCESS_STARTED message");
+
+ if (!gotProcessFinished)
+ XS_FAIL("Did't get PROCESS_FINISHED message");
+
+ if (receivedBytes != DATA_SIZE)
+ {
+ printf(" received: %d bytes\n expected: %d bytes\n", receivedBytes, DATA_SIZE);
+ XS_FAIL("Log data size doesn't match");
+ }
+
+ int timeMs = clock.getMilliseconds();
+ printf(" Streamed %d bytes in %d ms: %.2f MiB/s\n", DATA_SIZE, timeMs, ((float)DATA_SIZE / (float)(1024*1024)) / ((float)timeMs / 1000.0f));
+ }
+
+ void runProgram (void)
+ {
+ deFile* file = deFile_create(m_testCtx.logFileName.c_str(), DE_FILEMODE_OPEN|DE_FILEMODE_CREATE|DE_FILEMODE_TRUNCATE|DE_FILEMODE_WRITE);
+ XS_CHECK(file);
+
+ deUint8 tmpBuf[1024*16];
+ int numWritten = 0;
+
+ deMemset(&tmpBuf, 'a', sizeof(tmpBuf));
+
+ while (numWritten < DATA_SIZE)
+ {
+ deInt64 numWrittenInBatch = 0;
+ XS_CHECK(deFile_write(file, &tmpBuf[0], de::min((int)sizeof(tmpBuf), DATA_SIZE-numWritten), &numWrittenInBatch) == DE_FILERESULT_SUCCESS);
+ numWritten += (int)numWrittenInBatch;
+ }
+
+ deFile_destroy(file);
+ }
+};
+
+class KeepAliveTest : public TestCase
+{
+public:
+ KeepAliveTest (TestContext& testCtx)
+ : TestCase(testCtx, "keepalive")
+ {
+ }
+
+ void runClient (de::Socket& socket)
+ {
+ // In milliseconds.
+ const int sendInterval = 5000;
+ const int minReceiveInterval = 10000;
+ const int testTime = 30000;
+ const int sleepTime = 200;
+ const int expectedTimeout = 40000;
+ int curTime = 0;
+ int lastSendTime = 0;
+ int lastReceiveTime = 0;
+ TestClock clock;
+
+ DE_ASSERT(sendInterval < minReceiveInterval);
+
+ curTime = clock.getMilliseconds();
+
+ while (curTime < testTime)
+ {
+ bool tryGetKeepalive = false;
+
+ if (curTime-lastSendTime > sendInterval)
+ {
+ printf(" %d ms: sending keepalive\n", curTime);
+ sendMessage(socket, KeepAliveMessage());
+ curTime = clock.getMilliseconds();
+ lastSendTime = curTime;
+ tryGetKeepalive = true;
+ }
+
+ if (tryGetKeepalive)
+ {
+ // Try to acquire keepalive.
+ printf(" %d ms: waiting for keepalive\n", curTime);
+ ScopedMsgPtr msg(readMessage(socket));
+ int recvTime = clock.getMilliseconds();
+
+ if (msg->type != MESSAGETYPE_KEEPALIVE)
+ XS_FAIL("Got invalid message");
+
+ printf(" %d ms: got keepalive\n", curTime);
+
+ if (recvTime-lastReceiveTime > minReceiveInterval)
+ XS_FAIL("Server doesn't send keepalives");
+
+ lastReceiveTime = recvTime;
+ }
+
+ deSleep(sleepTime);
+ curTime = clock.getMilliseconds();
+ }
+
+ // Verify that server actually kills the connection upon timeout.
+ sendMessage(socket, KeepAliveMessage());
+ printf(" waiting %d ms for keepalive timeout...\n", expectedTimeout);
+ bool isClosed = false;
+
+ try
+ {
+ // Reset timer.
+ clock.reset();
+ curTime = clock.getMilliseconds();
+
+ while (curTime < expectedTimeout)
+ {
+ // Try to get keepalive message.
+ ScopedMsgPtr msg(readMessage(socket));
+ if (msg->type != MESSAGETYPE_KEEPALIVE)
+ XS_FAIL("Got invalid message");
+
+ curTime = clock.getMilliseconds();
+ printf(" %d ms: got keepalive\n", curTime);
+ }
+ }
+ catch (const SocketError& e)
+ {
+ if (e.getResult() == DE_SOCKETRESULT_CONNECTION_CLOSED)
+ {
+ printf(" %d ms: server closed connection", clock.getMilliseconds());
+ isClosed = true;
+ }
+ else
+ throw;
+ }
+
+ if (isClosed)
+ printf(" ok!\n");
+ else
+ XS_FAIL("Server didn't close connection");
+ }
+
+ void runProgram (void) { /* nothing */ }
+};
+
+void printHelp (const char* binName)
+{
+ printf("%s:\n", binName);
+ printf(" --client=[name] Run test [name]\n");
+ printf(" --program=[name] Run program for test [name]\n");
+ printf(" --host=[host] Connect to host [host]\n");
+ printf(" --port=[name] Use port [port]\n");
+ printf(" --tester-cmd=[cmd] Launch tester with [cmd]\n");
+ printf(" --server-cmd=[cmd] Launch server with [cmd]\n");
+ printf(" --start-server Start server for test execution\n");
+}
+
+struct CompareCaseName
+{
+ std::string name;
+
+ CompareCaseName (const string& name_) : name(name_) {}
+
+ bool operator() (const TestCase* testCase) const
+ {
+ return name == testCase->getName();
+ }
+};
+
+void runExecServerTests (int argc, const char* const* argv)
+{
+ // Construct test context.
+ TestContext testCtx;
+
+ testCtx.serverPath = "execserver";
+ testCtx.testerPath = argv[0];
+ testCtx.startServer = false;
+ testCtx.address.setHost("127.0.0.1");
+ testCtx.address.setPort(50016);
+
+ std::string runClient = "";
+ std::string runProgram = "";
+
+ // Parse command line.
+ for (int argNdx = 1; argNdx < argc; argNdx++)
+ {
+ const char* arg = argv[argNdx];
+
+ if (deStringBeginsWith(arg, "--client="))
+ runClient = arg+9;
+ else if (deStringBeginsWith(arg, "--program="))
+ runProgram = arg+10;
+ else if (deStringBeginsWith(arg, "--port="))
+ testCtx.address.setPort(atoi(arg+7));
+ else if (deStringBeginsWith(arg, "--host="))
+ testCtx.address.setHost(arg+7);
+ else if (deStringBeginsWith(arg, "--server-cmd="))
+ testCtx.serverPath = arg+13;
+ else if (deStringBeginsWith(arg, "--tester-cmd="))
+ testCtx.testerPath = arg+13;
+ else if (deStringBeginsWith(arg, "--deqp-log-filename="))
+ testCtx.logFileName = arg+20;
+ else if (deStringBeginsWith(arg, "--deqp-caselist="))
+ testCtx.caseList = arg+16;
+ else if (deStringEqual(arg, "--deqp-stdin-caselist"))
+ {
+ // \todo [pyry] This is rather brute-force solution...
+ char c;
+ while (fread(&c, 1, 1, stdin) == 1 && c != 0)
+ testCtx.caseList += c;
+ }
+ else if (deStringEqual(arg, "--start-server"))
+ testCtx.startServer = true;
+ else
+ {
+ printHelp(argv[0]);
+ return;
+ }
+ }
+
+ // Test case list.
+ std::vector<TestCase*> testCases;
+ testCases.push_back(new ConnectTest(testCtx));
+ testCases.push_back(new HelloTest(testCtx));
+ testCases.push_back(new ExecFailTest(testCtx));
+ testCases.push_back(new SimpleExecTest(testCtx));
+ testCases.push_back(new InfoTest(testCtx));
+ testCases.push_back(new LogDataTest(testCtx));
+ testCases.push_back(new KeepAliveTest(testCtx));
+ testCases.push_back(new BigLogDataTest(testCtx));
+
+ try
+ {
+ if (!runClient.empty())
+ {
+ // Run single case.
+ vector<TestCase*>::iterator casePos = std::find_if(testCases.begin(), testCases.end(), CompareCaseName(runClient));
+ XS_CHECK(casePos != testCases.end());
+ TestExecutor executor(testCtx);
+ executor.runCase(*casePos);
+ }
+ else if (!runProgram.empty())
+ {
+ // Run program part.
+ vector<TestCase*>::iterator casePos = std::find_if(testCases.begin(), testCases.end(), CompareCaseName(runProgram));
+ XS_CHECK(casePos != testCases.end());
+ (*casePos)->runProgram();
+ fflush(stdout); // Make sure handles are flushed.
+ fflush(stderr);
+ }
+ else
+ {
+ // Run all tests.
+ TestExecutor executor(testCtx);
+ executor.runCases(testCases);
+ }
+ }
+ catch (const std::exception& e)
+ {
+ printf("ERROR: %s\n", e.what());
+ }
+
+ // Destroy cases.
+ for (std::vector<TestCase*>::const_iterator i = testCases.begin(); i != testCases.end(); i++)
+ delete *i;
+}
+
+} // xs
+
+#if 0
+void testProcFile (void)
+{
+ /* Test file api. */
+ if (deFileExists("test.txt"))
+ deDeleteFile("test.txt");
+ deFile* file = deFile_create("test.txt", DE_FILEMODE_CREATE|DE_FILEMODE_WRITE);
+ const char test[] = "Hello";
+ XS_CHECK(deFile_write(file, test, sizeof(test), DE_NULL) == DE_FILERESULT_SUCCESS);
+ deFile_destroy(file);
+
+ /* Read. */
+ char buf[10] = { 0 };
+ file = deFile_create("test.txt", DE_FILEMODE_OPEN|DE_FILEMODE_READ);
+ XS_CHECK(deFile_read(file, buf, sizeof(test), DE_NULL) == DE_FILERESULT_SUCCESS);
+ printf("buf: %s\n", buf);
+ deFile_destroy(file);
+
+ /* Process test. */
+ deProcess* proc = deProcess_create("ls -lah /Users/pyry", DE_NULL);
+ deFile* out = deProcess_getStdOut(proc);
+
+ deInt64 numRead = 0;
+ printf("ls:\n");
+ while (deFile_read(out, buf, sizeof(buf)-1, &numRead) == DE_FILERESULT_SUCCESS)
+ {
+ buf[numRead] = 0;
+ printf("%s", buf);
+ }
+ deProcess_destroy(proc);
+}
+#endif
+
+#if 0
+void testBlockingFile (const char* filename)
+{
+ deRandom rnd;
+ int dataSize = 1024*1024;
+ deUint8* data = (deUint8*)deCalloc(dataSize);
+ deFile* file;
+
+ deRandom_init(&rnd, 0);
+
+ if (deFileExists(filename))
+ DE_VERIFY(deDeleteFile(filename));
+
+ /* Fill in with random data. */
+ DE_ASSERT(dataSize % sizeof(int) == 0);
+ for (int ndx = 0; ndx < (int)(dataSize/sizeof(int)); ndx++)
+ ((deUint32*)data)[ndx] = deRandom_getUint32(&rnd);
+
+ /* Write with random-sized blocks. */
+ file = deFile_create(filename, DE_FILEMODE_CREATE|DE_FILEMODE_WRITE);
+ DE_VERIFY(file);
+
+ int curPos = 0;
+ while (curPos < dataSize)
+ {
+ int blockSize = 1 + deRandom_getUint32(&rnd) % (dataSize-curPos);
+ deInt64 numWritten = 0;
+ deFileResult result = deFile_write(file, &data[curPos], blockSize, &numWritten);
+
+ DE_VERIFY(result == DE_FILERESULT_SUCCESS);
+ DE_VERIFY(numWritten == blockSize);
+
+ curPos += blockSize;
+ }
+
+ deFile_destroy(file);
+
+ /* Read and verify file. */
+ file = deFile_create(filename, DE_FILEMODE_OPEN|DE_FILEMODE_READ);
+ curPos = 0;
+ while (curPos < dataSize)
+ {
+ deUint8 block[1024];
+ int numToRead = 1 + deRandom_getUint32(&rnd) % deMin(dataSize-curPos, DE_LENGTH_OF_ARRAY(block));
+ deInt64 numRead = 0;
+ deFileResult result = deFile_read(file, block, numToRead, &numRead);
+
+ DE_VERIFY(result == DE_FILERESULT_SUCCESS);
+ DE_VERIFY((int)numRead == numToRead);
+ DE_VERIFY(deMemCmp(block, &data[curPos], numToRead) == 0);
+
+ curPos += numToRead;
+ }
+ deFile_destroy(file);
+}
+#endif
+
+int main (int argc, const char* const* argv)
+{
+ xs::runExecServerTests(argc, argv);
+ return 0;
+}
diff --git a/execserver/xsDefs.cpp b/execserver/xsDefs.cpp
new file mode 100644
index 0000000..5b07570
--- /dev/null
+++ b/execserver/xsDefs.cpp
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ExecServer defines.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+
+#include <sstream>
+
+namespace xs
+{
+
+static std::string formatError (const char* message, const char* expr, const char* file, int line)
+{
+ std::ostringstream msg;
+ msg << (message ? message : "Runtime check failed") << ": ";
+ if (expr)
+ msg << '\'' << expr << '\'';
+ msg << " at " << file << ":" << line;
+ return msg.str();
+}
+
+Error::Error (const char* message, const char* expr, const char* file, int line)
+ : std::runtime_error(formatError(message, expr, file, line))
+{
+}
+
+} // xs
diff --git a/execserver/xsDefs.hpp b/execserver/xsDefs.hpp
new file mode 100644
index 0000000..a818322
--- /dev/null
+++ b/execserver/xsDefs.hpp
@@ -0,0 +1,89 @@
+#ifndef _XSDEFS_HPP
+#define _XSDEFS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ExecServer defines.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.hpp"
+#include "deRingBuffer.hpp"
+#include "deBlockBuffer.hpp"
+
+#include <stdexcept>
+
+namespace xs
+{
+
+// Configuration.
+enum
+{
+ // Times are in milliseconds.
+ LOG_FILE_TIMEOUT = 5000,
+ READ_DATA_TIMEOUT = 500,
+
+ SERVER_IDLE_THRESHOLD = 10,
+ SERVER_IDLE_SLEEP = 50,
+ FILEREADER_IDLE_SLEEP = 100,
+
+ LOG_BUFFER_BLOCK_SIZE = 1024,
+ LOG_BUFFER_NUM_BLOCKS = 512,
+
+ INFO_BUFFER_BLOCK_SIZE = 64,
+ INFO_BUFFER_NUM_BLOCKS = 128,
+
+ SEND_BUFFER_SIZE = 16*1024,
+ RECV_BUFFER_SIZE = 4*1024,
+
+ FILEREADER_TMP_BUFFER_SIZE = 1024,
+ SEND_RECV_TMP_BUFFER_SIZE = 4*1024,
+
+ MIN_MSG_PAYLOAD_SIZE = 32
+};
+
+typedef de::RingBuffer<deUint8> ByteBuffer;
+typedef de::BlockBuffer<deUint8> ThreadedByteBuffer;
+
+class Error : public std::runtime_error
+{
+public:
+ Error (const std::string& message) : std::runtime_error(message) {}
+ Error (const char* message, const char* expr, const char* file, int line);
+};
+
+class ConnectionError : public Error
+{
+public:
+ ConnectionError (const std::string& message) : Error(message) {}
+};
+
+class ProtocolError : public ConnectionError
+{
+public:
+ ProtocolError (const std::string& message) : ConnectionError(message) {}
+};
+
+} // xs
+
+#define XS_FAIL(MSG) throw xs::Error(MSG, "", __FILE__, __LINE__)
+#define XS_CHECK(X) do { if ((!deGetFalse() && (X)) ? DE_FALSE : DE_TRUE) throw xs::Error(NULL, #X, __FILE__, __LINE__); } while(deGetFalse())
+#define XS_CHECK_MSG(X, MSG) do { if ((!deGetFalse() && (X)) ? DE_FALSE : DE_TRUE) throw xs::Error(MSG, #X, __FILE__, __LINE__); } while(deGetFalse())
+
+#endif // _XSDEFS_HPP
diff --git a/execserver/xsExecutionServer.cpp b/execserver/xsExecutionServer.cpp
new file mode 100644
index 0000000..eee2084
--- /dev/null
+++ b/execserver/xsExecutionServer.cpp
@@ -0,0 +1,406 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test Execution Server.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsExecutionServer.hpp"
+#include "deClock.h"
+
+#include <cstdio>
+
+using std::vector;
+using std::string;
+
+#if 1
+# define DBG_PRINT(X) printf X
+#else
+# define DBG_PRINT(X)
+#endif
+
+namespace xs
+{
+
+inline bool MessageBuilder::isComplete (void) const
+{
+ if (m_buffer.size() < MESSAGE_HEADER_SIZE)
+ return false;
+ else
+ return (int)m_buffer.size() == getMessageSize();
+}
+
+const deUint8* MessageBuilder::getMessageData (void) const
+{
+ return m_buffer.size() > MESSAGE_HEADER_SIZE ? &m_buffer[MESSAGE_HEADER_SIZE] : DE_NULL;
+}
+
+int MessageBuilder::getMessageDataSize (void) const
+{
+ DE_ASSERT(isComplete());
+ return (int)m_buffer.size() - MESSAGE_HEADER_SIZE;
+}
+
+void MessageBuilder::read (ByteBuffer& src)
+{
+ // Try to get header.
+ if (m_buffer.size() < MESSAGE_HEADER_SIZE)
+ {
+ while (m_buffer.size() < MESSAGE_HEADER_SIZE &&
+ src.getNumElements() > 0)
+ m_buffer.push_back(src.popBack());
+
+ DE_ASSERT(m_buffer.size() <= MESSAGE_HEADER_SIZE);
+
+ if (m_buffer.size() == MESSAGE_HEADER_SIZE)
+ {
+ // Got whole header, parse it.
+ Message::parseHeader(&m_buffer[0], (int)m_buffer.size(), m_messageType, m_messageSize);
+ }
+ }
+
+ if (m_buffer.size() >= MESSAGE_HEADER_SIZE)
+ {
+ // We have header.
+ int msgSize = getMessageSize();
+ int numBytesLeft = msgSize - (int)m_buffer.size();
+ int numToRead = de::min(src.getNumElements(), numBytesLeft);
+
+ if (numToRead > 0)
+ {
+ int curBufPos = (int)m_buffer.size();
+ m_buffer.resize(curBufPos+numToRead);
+ src.popBack(&m_buffer[curBufPos], numToRead);
+ }
+ }
+}
+
+void MessageBuilder::clear (void)
+{
+ m_buffer.clear();
+ m_messageType = MESSAGETYPE_NONE;
+ m_messageSize = 0;
+}
+
+ExecutionServer::ExecutionServer (xs::TestProcess* testProcess, deSocketFamily family, int port, RunMode runMode)
+ : TcpServer (family, port)
+ , m_testDriver (testProcess)
+ , m_runMode (runMode)
+{
+}
+
+ExecutionServer::~ExecutionServer (void)
+{
+}
+
+TestDriver* ExecutionServer::acquireTestDriver (void)
+{
+ if (!m_testDriverLock.tryLock())
+ throw Error("Failed to acquire test driver");
+
+ return &m_testDriver;
+}
+
+void ExecutionServer::releaseTestDriver (TestDriver* driver)
+{
+ DE_ASSERT(&m_testDriver == driver);
+ DE_UNREF(driver);
+ m_testDriverLock.unlock();
+}
+
+ConnectionHandler* ExecutionServer::createHandler (de::Socket* socket, const de::SocketAddress& clientAddress)
+{
+ printf("ExecutionServer: New connection from %s:%d\n", clientAddress.getHost(), clientAddress.getPort());
+ return new ExecutionRequestHandler(this, socket);
+}
+
+void ExecutionServer::connectionDone (ConnectionHandler* handler)
+{
+ if (m_runMode == RUNMODE_SINGLE_EXEC)
+ m_socket.close();
+
+ TcpServer::connectionDone(handler);
+}
+
+ExecutionRequestHandler::ExecutionRequestHandler (ExecutionServer* server, de::Socket* socket)
+ : ConnectionHandler (server, socket)
+ , m_execServer (server)
+ , m_testDriver (DE_NULL)
+ , m_bufferIn (RECV_BUFFER_SIZE)
+ , m_bufferOut (SEND_BUFFER_SIZE)
+ , m_run (false)
+ , m_sendRecvTmpBuf (SEND_RECV_TMP_BUFFER_SIZE)
+{
+ // Set flags.
+ m_socket->setFlags(DE_SOCKET_NONBLOCKING|DE_SOCKET_KEEPALIVE|DE_SOCKET_CLOSE_ON_EXEC);
+
+ // Init protocol keepalives.
+ initKeepAlives();
+}
+
+ExecutionRequestHandler::~ExecutionRequestHandler (void)
+{
+ if (m_testDriver)
+ m_execServer->releaseTestDriver(m_testDriver);
+}
+
+void ExecutionRequestHandler::handle (void)
+{
+ DBG_PRINT(("ExecutionRequestHandler::handle()\n"));
+
+ try
+ {
+ // Process execution session.
+ processSession();
+ }
+ catch (const std::exception& e)
+ {
+ printf("ExecutionRequestHandler::run(): %s\n", e.what());
+ }
+
+ DBG_PRINT(("ExecutionRequestHandler::handle(): Done!\n"));
+
+ // Release test driver.
+ if (m_testDriver)
+ {
+ try
+ {
+ m_testDriver->reset();
+ }
+ catch (...)
+ {
+ }
+ m_execServer->releaseTestDriver(m_testDriver);
+ m_testDriver = DE_NULL;
+ }
+
+ // Close connection.
+ if (m_socket->isConnected())
+ m_socket->shutdown();
+}
+
+void ExecutionRequestHandler::acquireTestDriver (void)
+{
+ DE_ASSERT(!m_testDriver);
+
+ // Try to acquire test driver - may fail.
+ m_testDriver = m_execServer->acquireTestDriver();
+ DE_ASSERT(m_testDriver);
+ m_testDriver->reset();
+
+}
+
+void ExecutionRequestHandler::processSession (void)
+{
+ m_run = true;
+
+ deUint64 lastIoTime = deGetMicroseconds();
+
+ while (m_run)
+ {
+ bool anyIO = false;
+
+ // Read from socket to buffer.
+ anyIO = receive() || anyIO;
+
+ // Send bytes in buffer.
+ anyIO = send() || anyIO;
+
+ // Process incoming data.
+ if (m_bufferIn.getNumElements() > 0)
+ {
+ DE_ASSERT(!m_msgBuilder.isComplete());
+ m_msgBuilder.read(m_bufferIn);
+ }
+
+ if (m_msgBuilder.isComplete())
+ {
+ // Process message.
+ processMessage(m_msgBuilder.getMessageType(), m_msgBuilder.getMessageData(), m_msgBuilder.getMessageDataSize());
+
+ m_msgBuilder.clear();
+ }
+
+ // Keepalives, anyone?
+ pollKeepAlives();
+
+ // Poll test driver for IO.
+ if (m_testDriver)
+ anyIO = getTestDriver()->poll(m_bufferOut) || anyIO;
+
+ // If no IO happens in a reasonable amount of time, go to sleep.
+ {
+ deUint64 curTime = deGetMicroseconds();
+ if (anyIO)
+ lastIoTime = curTime;
+ else if (curTime-lastIoTime > SERVER_IDLE_THRESHOLD*1000)
+ deSleep(SERVER_IDLE_SLEEP); // Too long since last IO, sleep for a while.
+ else
+ deYield(); // Just give other threads chance to run.
+ }
+ }
+}
+
+void ExecutionRequestHandler::processMessage (MessageType type, const deUint8* data, int dataSize)
+{
+ switch (type)
+ {
+ case MESSAGETYPE_HELLO:
+ {
+ HelloMessage msg(data, dataSize);
+ DBG_PRINT(("HelloMessage: version = %d\n", msg.version));
+ if (msg.version != PROTOCOL_VERSION)
+ throw ProtocolError("Unsupported protocol version");
+ break;
+ }
+
+ case MESSAGETYPE_TEST:
+ {
+ TestMessage msg(data, dataSize);
+ DBG_PRINT(("TestMessage: '%s'\n", msg.test.c_str()));
+ break;
+ }
+
+ case MESSAGETYPE_KEEPALIVE:
+ {
+ KeepAliveMessage msg(data, dataSize);
+ DBG_PRINT(("KeepAliveMessage\n"));
+ keepAliveReceived();
+ break;
+ }
+
+ case MESSAGETYPE_EXECUTE_BINARY:
+ {
+ ExecuteBinaryMessage msg(data, dataSize);
+ DBG_PRINT(("ExecuteBinaryMessage: '%s', '%s', '%s', '%s'\n", msg.name.c_str(), msg.params.c_str(), msg.workDir.c_str(), msg.caseList.substr(0, 10).c_str()));
+ getTestDriver()->startProcess(msg.name.c_str(), msg.params.c_str(), msg.workDir.c_str(), msg.caseList.c_str());
+ keepAliveReceived(); // \todo [2011-10-11 pyry] Remove this once Candy is fixed.
+ break;
+ }
+
+ case MESSAGETYPE_STOP_EXECUTION:
+ {
+ StopExecutionMessage msg(data, dataSize);
+ DBG_PRINT(("StopExecutionMessage\n"));
+ getTestDriver()->stopProcess();
+ break;
+ }
+
+ default:
+ throw ProtocolError("Unsupported message");
+ }
+}
+
+void ExecutionRequestHandler::initKeepAlives (void)
+{
+ deUint64 curTime = deGetMicroseconds();
+ m_lastKeepAliveSent = curTime;
+ m_lastKeepAliveReceived = curTime;
+}
+
+void ExecutionRequestHandler::keepAliveReceived (void)
+{
+ m_lastKeepAliveReceived = deGetMicroseconds();
+}
+
+void ExecutionRequestHandler::pollKeepAlives (void)
+{
+ deUint64 curTime = deGetMicroseconds();
+
+ // Check that we've got keepalives in timely fashion.
+ if (curTime - m_lastKeepAliveReceived > KEEPALIVE_TIMEOUT*1000)
+ throw ProtocolError("Keepalive timeout occurred");
+
+ // Send some?
+ if (curTime - m_lastKeepAliveSent > KEEPALIVE_SEND_INTERVAL*1000 &&
+ m_bufferOut.getNumFree() >= MESSAGE_HEADER_SIZE)
+ {
+ vector<deUint8> buf;
+ KeepAliveMessage().write(buf);
+ m_bufferOut.pushFront(&buf[0], (int)buf.size());
+
+ m_lastKeepAliveSent = deGetMicroseconds();
+ }
+}
+
+bool ExecutionRequestHandler::receive (void)
+{
+ int maxLen = de::min<int>((int)m_sendRecvTmpBuf.size(), m_bufferIn.getNumFree());
+
+ if (maxLen > 0)
+ {
+ int numRecv;
+ deSocketResult result = m_socket->receive(&m_sendRecvTmpBuf[0], maxLen, &numRecv);
+
+ if (result == DE_SOCKETRESULT_SUCCESS)
+ {
+ DE_ASSERT(numRecv > 0);
+ m_bufferIn.pushFront(&m_sendRecvTmpBuf[0], numRecv);
+ return true;
+ }
+ else if (result == DE_SOCKETRESULT_CONNECTION_CLOSED)
+ {
+ m_run = false;
+ return true;
+ }
+ else if (result == DE_SOCKETRESULT_WOULD_BLOCK)
+ return false;
+ else if (result == DE_SOCKETRESULT_CONNECTION_TERMINATED)
+ throw ConnectionError("Connection terminated");
+ else
+ throw ConnectionError("receive() failed");
+ }
+ else
+ return false;
+}
+
+bool ExecutionRequestHandler::send (void)
+{
+ int maxLen = de::min<int>((int)m_sendRecvTmpBuf.size(), m_bufferOut.getNumElements());
+
+ if (maxLen > 0)
+ {
+ m_bufferOut.peekBack(&m_sendRecvTmpBuf[0], maxLen);
+
+ int numSent;
+ deSocketResult result = m_socket->send(&m_sendRecvTmpBuf[0], maxLen, &numSent);
+
+ if (result == DE_SOCKETRESULT_SUCCESS)
+ {
+ DE_ASSERT(numSent > 0);
+ m_bufferOut.popBack(numSent);
+ return true;
+ }
+ else if (result == DE_SOCKETRESULT_CONNECTION_CLOSED)
+ {
+ m_run = false;
+ return true;
+ }
+ else if (result == DE_SOCKETRESULT_WOULD_BLOCK)
+ return false;
+ else if (result == DE_SOCKETRESULT_CONNECTION_TERMINATED)
+ throw ConnectionError("Connection terminated");
+ else
+ throw ConnectionError("send() failed");
+ }
+ else
+ return false;
+}
+
+} // xs
diff --git a/execserver/xsExecutionServer.hpp b/execserver/xsExecutionServer.hpp
new file mode 100644
index 0000000..c9ad1ae
--- /dev/null
+++ b/execserver/xsExecutionServer.hpp
@@ -0,0 +1,129 @@
+#ifndef _XSEXECUTIONSERVER_HPP
+#define _XSEXECUTIONSERVER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test Execution Server.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "xsTcpServer.hpp"
+#include "xsTestDriver.hpp"
+#include "xsProtocol.hpp"
+#include "xsTestProcess.hpp"
+
+#include <vector>
+
+namespace xs
+{
+
+class ExecutionServer : public TcpServer
+{
+public:
+ enum RunMode
+ {
+ RUNMODE_SINGLE_EXEC = 0,
+ RUNMODE_FOREVER,
+
+ RUNMODE_LAST
+ };
+
+ ExecutionServer (xs::TestProcess* testProcess, deSocketFamily family, int port, RunMode runMode);
+ ~ExecutionServer (void);
+
+ ConnectionHandler* createHandler (de::Socket* socket, const de::SocketAddress& clientAddress);
+
+ TestDriver* acquireTestDriver (void);
+ void releaseTestDriver (TestDriver* driver);
+
+ void connectionDone (ConnectionHandler* handler);
+
+private:
+ TestDriver m_testDriver;
+ de::Mutex m_testDriverLock;
+ RunMode m_runMode;
+};
+
+class MessageBuilder
+{
+public:
+ MessageBuilder (void) { clear(); }
+ ~MessageBuilder (void) {}
+
+ void read (ByteBuffer& buffer);
+ void clear (void);
+
+ bool isComplete (void) const;
+ MessageType getMessageType (void) const { return m_messageType; }
+ int getMessageSize (void) const { return m_messageSize; }
+ const deUint8* getMessageData (void) const;
+ int getMessageDataSize (void) const;
+
+private:
+ std::vector<deUint8> m_buffer;
+ MessageType m_messageType;
+ int m_messageSize;
+};
+
+class ExecutionRequestHandler : public ConnectionHandler
+{
+public:
+ ExecutionRequestHandler (ExecutionServer* server, de::Socket* socket);
+ ~ExecutionRequestHandler (void);
+
+protected:
+ void handle (void);
+
+private:
+ ExecutionRequestHandler (const ExecutionRequestHandler& handler);
+ ExecutionRequestHandler& operator= (const ExecutionRequestHandler& handler);
+
+ void processSession (void);
+ void processMessage (MessageType type, const deUint8* data, int dataSize);
+
+ inline TestDriver* getTestDriver (void) { if (!m_testDriver) acquireTestDriver(); return m_testDriver; }
+ void acquireTestDriver (void);
+
+ void initKeepAlives (void);
+ void keepAliveReceived (void);
+ void pollKeepAlives (void);
+
+ bool receive (void);
+ bool send (void);
+
+ ExecutionServer* m_execServer;
+ TestDriver* m_testDriver;
+
+ ByteBuffer m_bufferIn;
+ ByteBuffer m_bufferOut;
+
+ bool m_run;
+ MessageBuilder m_msgBuilder;
+
+ // \todo [2011-09-30 pyry] Move to some watchdog class instead.
+ deUint64 m_lastKeepAliveSent;
+ deUint64 m_lastKeepAliveReceived;
+
+ std::vector<deUint8> m_sendRecvTmpBuf;
+};
+
+} // xs
+
+#endif // _XSEXECUTIONSERVER_HPP
diff --git a/execserver/xsPosixFileReader.cpp b/execserver/xsPosixFileReader.cpp
new file mode 100644
index 0000000..5974657
--- /dev/null
+++ b/execserver/xsPosixFileReader.cpp
@@ -0,0 +1,121 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief File Reader.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsPosixFileReader.hpp"
+
+#include <vector>
+
+namespace xs
+{
+namespace posix
+{
+
+FileReader::FileReader (int blockSize, int numBlocks)
+ : m_file (DE_NULL)
+ , m_buf (blockSize, numBlocks)
+ , m_isRunning (false)
+{
+}
+
+FileReader::~FileReader (void)
+{
+}
+
+void FileReader::start (const char* filename)
+{
+ DE_ASSERT(!m_isRunning);
+
+ m_file = deFile_create(filename, DE_FILEMODE_OPEN|DE_FILEMODE_READ);
+ XS_CHECK(m_file);
+
+#if (DE_OS != DE_OS_IOS)
+ // Set to non-blocking mode.
+ if (!deFile_setFlags(m_file, DE_FILE_NONBLOCKING))
+ {
+ deFile_destroy(m_file);
+ m_file = DE_NULL;
+ XS_FAIL("Failed to set non-blocking mode");
+ }
+#endif
+
+ m_isRunning = true;
+
+ de::Thread::start();
+}
+
+void FileReader::run (void)
+{
+ std::vector<deUint8> tmpBuf (FILEREADER_TMP_BUFFER_SIZE);
+ deInt64 numRead = 0;
+
+ while (!m_buf.isCanceled())
+ {
+ deFileResult result = deFile_read(m_file, &tmpBuf[0], (deInt64)tmpBuf.size(), &numRead);
+
+ if (result == DE_FILERESULT_SUCCESS)
+ {
+ // Write to buffer.
+ try
+ {
+ m_buf.write((int)numRead, &tmpBuf[0]);
+ m_buf.flush();
+ }
+ catch (const ThreadedByteBuffer::CanceledException&)
+ {
+ // Canceled.
+ break;
+ }
+ }
+ else if (result == DE_FILERESULT_END_OF_FILE ||
+ result == DE_FILERESULT_WOULD_BLOCK)
+ {
+ // Wait for more data.
+ deSleep(FILEREADER_IDLE_SLEEP);
+ }
+ else
+ break; // Error.
+ }
+}
+
+void FileReader::stop (void)
+{
+ if (!m_isRunning)
+ return; // Nothing to do.
+
+ m_buf.cancel();
+
+ // Join thread.
+ join();
+
+ // Destroy file.
+ deFile_destroy(m_file);
+ m_file = DE_NULL;
+
+ // Reset buffer.
+ m_buf.clear();
+
+ m_isRunning = false;
+}
+
+} // posix
+} // xs
diff --git a/execserver/xsPosixFileReader.hpp b/execserver/xsPosixFileReader.hpp
new file mode 100644
index 0000000..5d5d63a
--- /dev/null
+++ b/execserver/xsPosixFileReader.hpp
@@ -0,0 +1,58 @@
+#ifndef _XSPOSIXFILEREADER_HPP
+#define _XSPOSIXFILEREADER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief File Reader.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "deFile.h"
+#include "deThread.hpp"
+
+namespace xs
+{
+namespace posix
+{
+
+class FileReader : public de::Thread
+{
+public:
+ FileReader (int blockSize, int numBlocks);
+ ~FileReader (void);
+
+ void start (const char* filename);
+ void stop (void);
+
+ bool isRunning (void) const { return m_isRunning; }
+ int read (deUint8* dst, int numBytes) { return m_buf.tryRead(numBytes, dst); }
+
+ void run (void);
+
+private:
+ deFile* m_file;
+ ThreadedByteBuffer m_buf;
+ bool m_isRunning;
+};
+
+} // posix
+} // xs
+
+#endif // _XSPOSIXFILEREADER_HPP
diff --git a/execserver/xsPosixTestProcess.cpp b/execserver/xsPosixTestProcess.cpp
new file mode 100644
index 0000000..5c9dcb6
--- /dev/null
+++ b/execserver/xsPosixTestProcess.cpp
@@ -0,0 +1,336 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief TestProcess implementation for Unix-like systems.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsPosixTestProcess.hpp"
+#include "deFilePath.hpp"
+#include "deClock.h"
+
+#include <string.h>
+#include <stdio.h>
+
+using std::string;
+using std::vector;
+
+namespace xs
+{
+
+namespace posix
+{
+
+CaseListWriter::CaseListWriter (void)
+ : m_file (DE_NULL)
+ , m_run (false)
+{
+}
+
+CaseListWriter::~CaseListWriter (void)
+{
+}
+
+void CaseListWriter::start (const char* caseList, deFile* dst)
+{
+ DE_ASSERT(!isStarted());
+ m_file = dst;
+ m_run = true;
+
+ int caseListSize = (int)strlen(caseList)+1;
+ m_caseList.resize(caseListSize);
+ std::copy(caseList, caseList+caseListSize, m_caseList.begin());
+
+ // Set to non-blocking mode.
+ if (!deFile_setFlags(m_file, DE_FILE_NONBLOCKING))
+ XS_FAIL("Failed to set non-blocking mode");
+
+ de::Thread::start();
+}
+
+void CaseListWriter::run (void)
+{
+ deInt64 pos = 0;
+
+ while (m_run && pos < (deInt64)m_caseList.size())
+ {
+ deInt64 numWritten = 0;
+ deFileResult result = deFile_write(m_file, &m_caseList[0] + pos, m_caseList.size()-pos, &numWritten);
+
+ if (result == DE_FILERESULT_SUCCESS)
+ pos += numWritten;
+ else if (result == DE_FILERESULT_WOULD_BLOCK)
+ deSleep(1); // Yield.
+ else
+ break; // Error.
+ }
+}
+
+void CaseListWriter::stop (void)
+{
+ if (!isStarted())
+ return; // Nothing to do.
+
+ m_run = false;
+
+ // Join thread.
+ join();
+
+ m_file = DE_NULL;
+}
+
+PipeReader::PipeReader (ThreadedByteBuffer* dst)
+ : m_file (DE_NULL)
+ , m_buf (dst)
+{
+}
+
+PipeReader::~PipeReader (void)
+{
+}
+
+void PipeReader::start (deFile* file)
+{
+ DE_ASSERT(!isStarted());
+
+ // Set to non-blocking mode.
+ if (!deFile_setFlags(file, DE_FILE_NONBLOCKING))
+ XS_FAIL("Failed to set non-blocking mode");
+
+ m_file = file;
+
+ de::Thread::start();
+}
+
+void PipeReader::run (void)
+{
+ std::vector<deUint8> tmpBuf (FILEREADER_TMP_BUFFER_SIZE);
+ deInt64 numRead = 0;
+
+ while (!m_buf->isCanceled())
+ {
+ deFileResult result = deFile_read(m_file, &tmpBuf[0], (deInt64)tmpBuf.size(), &numRead);
+
+ if (result == DE_FILERESULT_SUCCESS)
+ {
+ // Write to buffer.
+ try
+ {
+ m_buf->write((int)numRead, &tmpBuf[0]);
+ m_buf->flush();
+ }
+ catch (const ThreadedByteBuffer::CanceledException&)
+ {
+ // Canceled.
+ break;
+ }
+ }
+ else if (result == DE_FILERESULT_END_OF_FILE ||
+ result == DE_FILERESULT_WOULD_BLOCK)
+ {
+ // Wait for more data.
+ deSleep(FILEREADER_IDLE_SLEEP);
+ }
+ else
+ break; // Error.
+ }
+}
+
+void PipeReader::stop (void)
+{
+ if (!isStarted())
+ return; // Nothing to do.
+
+ // Buffer must be in canceled state or otherwise stopping reader might block.
+ DE_ASSERT(m_buf->isCanceled());
+
+ // Join thread.
+ join();
+
+ m_file = DE_NULL;
+}
+
+} // unix
+
+PosixTestProcess::PosixTestProcess (void)
+ : m_process (DE_NULL)
+ , m_processStartTime (0)
+ , m_infoBuffer (INFO_BUFFER_BLOCK_SIZE, INFO_BUFFER_NUM_BLOCKS)
+ , m_stdOutReader (&m_infoBuffer)
+ , m_stdErrReader (&m_infoBuffer)
+ , m_logReader (LOG_BUFFER_BLOCK_SIZE, LOG_BUFFER_NUM_BLOCKS)
+{
+}
+
+PosixTestProcess::~PosixTestProcess (void)
+{
+ delete m_process;
+}
+
+void PosixTestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList)
+{
+ bool hasCaseList = strlen(caseList) > 0;
+
+ XS_CHECK(!m_process);
+
+ de::FilePath logFilePath = de::FilePath::join(workingDir, "TestResults.qpa");
+ m_logFileName = logFilePath.getPath();
+
+ // Remove old file if such exists.
+ if (deFileExists(m_logFileName.c_str()))
+ {
+ if (!deDeleteFile(m_logFileName.c_str()) || deFileExists(m_logFileName.c_str()))
+ throw TestProcessException(string("Failed to remove '") + m_logFileName + "'");
+ }
+
+ // Construct command line.
+ string cmdLine = de::FilePath(name).isAbsolutePath() ? name : de::FilePath::join(workingDir, name).normalize().getPath();
+ cmdLine += string(" --deqp-log-filename=") + logFilePath.getBaseName();
+
+ if (hasCaseList)
+ cmdLine += " --deqp-stdin-caselist";
+
+ if (strlen(params) > 0)
+ cmdLine += string(" ") + params;
+
+ DE_ASSERT(!m_process);
+ m_process = new de::Process();
+
+ try
+ {
+ m_process->start(cmdLine.c_str(), strlen(workingDir) > 0 ? workingDir : DE_NULL);
+ }
+ catch (const de::ProcessError& e)
+ {
+ delete m_process;
+ m_process = DE_NULL;
+ throw TestProcessException(e.what());
+ }
+
+ m_processStartTime = deGetMicroseconds();
+
+ // Create stdout & stderr readers.
+ if (m_process->getStdOut())
+ m_stdOutReader.start(m_process->getStdOut());
+
+ if (m_process->getStdErr())
+ m_stdErrReader.start(m_process->getStdErr());
+
+ // Start case list writer.
+ if (hasCaseList)
+ {
+ deFile* dst = m_process->getStdIn();
+ if (dst)
+ m_caseListWriter.start(caseList, dst);
+ else
+ {
+ cleanup();
+ throw TestProcessException("Failed to write case list");
+ }
+ }
+}
+
+void PosixTestProcess::terminate (void)
+{
+ if (m_process)
+ {
+ try
+ {
+ m_process->kill();
+ }
+ catch (const std::exception& e)
+ {
+ printf("PosixTestProcess::terminate(): Failed to kill process: %s\n", e.what());
+ }
+ }
+}
+
+void PosixTestProcess::cleanup (void)
+{
+ m_caseListWriter.stop();
+ m_logReader.stop();
+
+ // \note Info buffer must be canceled before stopping pipe readers.
+ m_infoBuffer.cancel();
+
+ m_stdErrReader.stop();
+ m_stdOutReader.stop();
+
+ // Reset info buffer.
+ m_infoBuffer.clear();
+
+ if (m_process)
+ {
+ try
+ {
+ if (m_process->isRunning())
+ {
+ m_process->kill();
+ m_process->waitForFinish();
+ }
+ }
+ catch (const de::ProcessError& e)
+ {
+ printf("PosixTestProcess::stop(): Failed to kill process: %s\n", e.what());
+ }
+
+ delete m_process;
+ m_process = DE_NULL;
+ }
+}
+
+bool PosixTestProcess::isRunning (void)
+{
+ if (m_process)
+ return m_process->isRunning();
+ else
+ return false;
+}
+
+int PosixTestProcess::getExitCode (void) const
+{
+ if (m_process)
+ return m_process->getExitCode();
+ else
+ return -1;
+}
+
+int PosixTestProcess::readTestLog (deUint8* dst, int numBytes)
+{
+ if (!m_logReader.isRunning())
+ {
+ if (deGetMicroseconds() - m_processStartTime > LOG_FILE_TIMEOUT*1000)
+ {
+ // Timeout, kill process.
+ terminate();
+ return 0; // \todo [2013-08-13 pyry] Throw exception?
+ }
+
+ if (!deFileExists(m_logFileName.c_str()))
+ return 0;
+
+ // Start reader.
+ m_logReader.start(m_logFileName.c_str());
+ }
+
+ DE_ASSERT(m_logReader.isRunning());
+ return m_logReader.read(dst, numBytes);
+}
+
+} // xs
diff --git a/execserver/xsPosixTestProcess.hpp b/execserver/xsPosixTestProcess.hpp
new file mode 100644
index 0000000..3e85901
--- /dev/null
+++ b/execserver/xsPosixTestProcess.hpp
@@ -0,0 +1,110 @@
+#ifndef _XSPOSIXTESTPROCESS_HPP
+#define _XSPOSIXTESTPROCESS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief TestProcess implementation for Unix-like systems.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "xsTestProcess.hpp"
+#include "xsPosixFileReader.hpp"
+#include "deProcess.hpp"
+#include "deThread.hpp"
+
+#include <vector>
+#include <string>
+
+namespace xs
+{
+namespace posix
+{
+
+class CaseListWriter : public de::Thread
+{
+public:
+ CaseListWriter (void);
+ ~CaseListWriter (void);
+
+ void start (const char* caseList, deFile* dst);
+ void stop (void);
+
+ void run (void);
+
+private:
+ deFile* m_file;
+ std::vector<char> m_caseList;
+ bool m_run;
+};
+
+class PipeReader : public de::Thread
+{
+public:
+ PipeReader (ThreadedByteBuffer* dst);
+ ~PipeReader (void);
+
+ void start (deFile* file);
+ void stop (void);
+
+ void run (void);
+
+private:
+ deFile* m_file;
+ ThreadedByteBuffer* m_buf;
+};
+
+} // posix
+
+class PosixTestProcess : public TestProcess
+{
+public:
+ PosixTestProcess (void);
+ virtual ~PosixTestProcess (void);
+
+ virtual void start (const char* name, const char* params, const char* workingDir, const char* caseList);
+ virtual void terminate (void);
+ virtual void cleanup (void);
+
+ virtual bool isRunning (void);
+
+ virtual int getExitCode (void) const;
+
+ virtual int readTestLog (deUint8* dst, int numBytes);
+ virtual int readInfoLog (deUint8* dst, int numBytes) { return m_infoBuffer.tryRead(numBytes, dst); }
+
+private:
+ PosixTestProcess (const PosixTestProcess& other);
+ PosixTestProcess& operator= (const PosixTestProcess& other);
+
+ de::Process* m_process;
+ deUint64 m_processStartTime; //!< Used for determining log file timeout.
+ std::string m_logFileName;
+ ThreadedByteBuffer m_infoBuffer;
+
+ // Threads.
+ posix::CaseListWriter m_caseListWriter;
+ posix::PipeReader m_stdOutReader;
+ posix::PipeReader m_stdErrReader;
+ posix::FileReader m_logReader;
+};
+
+} // xs
+
+#endif // _XSPOSIXTESTPROCESS_HPP
diff --git a/execserver/xsProtocol.cpp b/execserver/xsProtocol.cpp
new file mode 100644
index 0000000..e2471fa
--- /dev/null
+++ b/execserver/xsProtocol.cpp
@@ -0,0 +1,267 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Execution Server Protocol.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsProtocol.hpp"
+
+using std::string;
+using std::vector;
+
+namespace xs
+{
+
+inline deUint32 swapEndianess (deUint32 value)
+{
+ deUint32 b0 = (value >> 0) & 0xFF;
+ deUint32 b1 = (value >> 8) & 0xFF;
+ deUint32 b2 = (value >> 16) & 0xFF;
+ deUint32 b3 = (value >> 24) & 0xFF;
+ return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
+}
+
+template <typename T> T networkToHost (T value);
+template <typename T> T hostToNetwork (T value);
+
+template <> int networkToHost (int value) { return (int)swapEndianess((deUint32)value); }
+template <> int hostToNetwork (int value) { return (int)swapEndianess((deUint32)value); }
+
+class MessageParser
+{
+public:
+ MessageParser (const deUint8* data, int dataSize)
+ : m_data (data)
+ , m_size (dataSize)
+ , m_pos (0)
+ {
+ }
+
+ template <typename T>
+ T get (void)
+ {
+ XS_CHECK_MSG(m_pos + (int)sizeof(T) <= m_size, "Invalid payload size");
+ T netValue;
+ deMemcpy(&netValue, &m_data[m_pos], sizeof(T));
+ m_pos += sizeof(T);
+ return networkToHost(netValue);
+ }
+
+ void getString (std::string& dst)
+ {
+ // \todo [2011-09-30 pyry] We should really send a size parameter instead.
+ while (m_data[m_pos] != 0)
+ {
+ dst += (char)m_data[m_pos++];
+ XS_CHECK_MSG(m_pos < m_size, "Unterminated string payload");
+ }
+
+ m_pos += 1;
+ }
+
+ void assumEnd (void)
+ {
+ if (m_pos != m_size)
+ XS_FAIL("Invalid payload size");
+ }
+
+private:
+ const deUint8* m_data;
+ int m_size;
+ int m_pos;
+};
+
+class MessageWriter
+{
+public:
+ MessageWriter (MessageType msgType, std::vector<deUint8>& buf)
+ : m_buf(buf)
+ {
+ // Place for size.
+ put<int>(0);
+
+ // Write message type.
+ put<int>(msgType);
+ }
+
+ ~MessageWriter (void)
+ {
+ finalize();
+ }
+
+ void finalize (void)
+ {
+ DE_ASSERT(m_buf.size() >= MESSAGE_HEADER_SIZE);
+
+ // Write actual size.
+ int size = hostToNetwork((int)m_buf.size());
+ deMemcpy(&m_buf[0], &size, sizeof(int));
+ }
+
+ template <typename T>
+ void put (T value)
+ {
+ T netValue = hostToNetwork(value);
+ size_t curPos = m_buf.size();
+ m_buf.resize(curPos + sizeof(T));
+ deMemcpy(&m_buf[curPos], &netValue, sizeof(T));
+ }
+
+private:
+ std::vector<deUint8>& m_buf;
+};
+
+template <>
+void MessageWriter::put<const char*> (const char* value)
+{
+ int curPos = (int)m_buf.size();
+ int strLen = (int)strlen(value);
+
+ m_buf.resize(curPos + strLen+1);
+ deMemcpy(&m_buf[curPos], &value[0], strLen+1);
+}
+
+void Message::parseHeader (const deUint8* data, int dataSize, MessageType& type, int& size)
+{
+ XS_CHECK_MSG(dataSize >= MESSAGE_HEADER_SIZE, "Incomplete header");
+ MessageParser parser(data, dataSize);
+ size = (MessageType)parser.get<int>();
+ type = (MessageType)parser.get<int>();
+}
+
+void Message::writeHeader (MessageType type, int messageSize, deUint8* dst, int bufSize)
+{
+ XS_CHECK_MSG(bufSize >= MESSAGE_HEADER_SIZE, "Incomplete header");
+ int netSize = hostToNetwork(messageSize);
+ int netType = hostToNetwork((int)type);
+ deMemcpy(dst+0, &netSize, sizeof(netSize));
+ deMemcpy(dst+4, &netType, sizeof(netType));
+}
+
+void Message::writeNoData (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+}
+
+HelloMessage::HelloMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_HELLO)
+{
+ MessageParser parser(data, dataSize);
+ version = parser.get<int>();
+ parser.assumEnd();
+}
+
+void HelloMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(version);
+}
+
+TestMessage::TestMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_TEST)
+{
+ MessageParser parser(data, dataSize);
+ parser.getString(test);
+ parser.assumEnd();
+}
+
+void TestMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(test.c_str());
+}
+
+ExecuteBinaryMessage::ExecuteBinaryMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_EXECUTE_BINARY)
+{
+ MessageParser parser(data, dataSize);
+ parser.getString(name);
+ parser.getString(params);
+ parser.getString(workDir);
+ parser.getString(caseList);
+ parser.assumEnd();
+}
+
+void ExecuteBinaryMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(name.c_str());
+ writer.put(params.c_str());
+ writer.put(workDir.c_str());
+ writer.put(caseList.c_str());
+}
+
+ProcessLogDataMessage::ProcessLogDataMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_PROCESS_LOG_DATA)
+{
+ MessageParser parser(data, dataSize);
+ parser.getString(logData);
+ parser.assumEnd();
+}
+
+void ProcessLogDataMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(logData.c_str());
+}
+
+ProcessLaunchFailedMessage::ProcessLaunchFailedMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_PROCESS_LAUNCH_FAILED)
+{
+ MessageParser parser(data, dataSize);
+ parser.getString(reason);
+ parser.assumEnd();
+}
+
+void ProcessLaunchFailedMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(reason.c_str());
+}
+
+ProcessFinishedMessage::ProcessFinishedMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_PROCESS_FINISHED)
+{
+ MessageParser parser(data, dataSize);
+ exitCode = parser.get<int>();
+ parser.assumEnd();
+}
+
+void ProcessFinishedMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(exitCode);
+}
+
+InfoMessage::InfoMessage (const deUint8* data, int dataSize)
+ : Message(MESSAGETYPE_INFO)
+{
+ MessageParser parser(data, dataSize);
+ parser.getString(info);
+ parser.assumEnd();
+}
+
+void InfoMessage::write (vector<deUint8>& buf) const
+{
+ MessageWriter writer(type, buf);
+ writer.put(info.c_str());
+}
+
+} // xs
diff --git a/execserver/xsProtocol.hpp b/execserver/xsProtocol.hpp
new file mode 100644
index 0000000..369ee92
--- /dev/null
+++ b/execserver/xsProtocol.hpp
@@ -0,0 +1,190 @@
+#ifndef _XSPROTOCOL_HPP
+#define _XSPROTOCOL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Execution Server Protocol.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "deMemory.h"
+
+#include <string>
+#include <vector>
+
+namespace xs
+{
+
+enum
+{
+ PROTOCOL_VERSION = 18,
+ MESSAGE_HEADER_SIZE = 8,
+
+ // Times are in milliseconds.
+ KEEPALIVE_SEND_INTERVAL = 5000,
+ KEEPALIVE_TIMEOUT = 30000,
+};
+
+enum MessageType
+{
+ MESSAGETYPE_NONE = 0, //!< Not valid.
+
+ // Commands (from Client to ExecServer).
+ MESSAGETYPE_HELLO = 100, //!< First message from client, specifies the protocol version
+ MESSAGETYPE_TEST = 101, //!< Debug only
+ MESSAGETYPE_EXECUTE_BINARY = 111, //!< Request execution of a test package binary.
+ MESSAGETYPE_STOP_EXECUTION = 112, //!< Request cancellation of the currently executing binary.
+
+ // Responses (from ExecServer to Client)
+ MESSAGETYPE_PROCESS_STARTED = 200, //!< Requested process has started.
+ MESSAGETYPE_PROCESS_LAUNCH_FAILED = 201, //!< Requested process failed to launch.
+ MESSAGETYPE_PROCESS_FINISHED = 202, //!< Requested process has finished (for any reason).
+ MESSAGETYPE_PROCESS_LOG_DATA = 203, //!< Unprocessed log data from TestResults.qpa.
+ MESSAGETYPE_INFO = 204, //!< Generic info message from ExecServer (for debugging purposes).
+
+ MESSAGETYPE_KEEPALIVE = 102 //!< Keep-alive packet
+};
+
+class MessageWriter;
+
+class Message
+{
+public:
+ MessageType type;
+
+ Message (MessageType type_) : type(type_) {}
+ virtual ~Message (void) {}
+
+ virtual void write (std::vector<deUint8>& buf) const = DE_NULL;
+
+ static void parseHeader (const deUint8* data, int dataSize, MessageType& type, int& messageSize);
+ static void writeHeader (MessageType type, int messageSize, deUint8* dst, int bufSize);
+
+protected:
+ void writeNoData (std::vector<deUint8>& buf) const;
+
+ Message (const Message& other);
+ Message& operator= (const Message& other);
+};
+
+// Simple messages without any data.
+template <int MsgType>
+class SimpleMessage : public Message
+{
+public:
+ SimpleMessage (const deUint8* data, int dataSize) : Message((MessageType)MsgType) { DE_UNREF(data); XS_CHECK_MSG(dataSize == 0, "No payload expected"); }
+ SimpleMessage (void) : Message((MessageType)MsgType) {}
+ ~SimpleMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const { writeNoData(buf); }
+};
+
+typedef SimpleMessage<MESSAGETYPE_STOP_EXECUTION> StopExecutionMessage;
+typedef SimpleMessage<MESSAGETYPE_PROCESS_STARTED> ProcessStartedMessage;
+typedef SimpleMessage<MESSAGETYPE_KEEPALIVE> KeepAliveMessage;
+
+class HelloMessage : public Message
+{
+public:
+ int version;
+
+ HelloMessage (const deUint8* data, int dataSize);
+ HelloMessage (void) : Message(MESSAGETYPE_HELLO), version(PROTOCOL_VERSION) {}
+ ~HelloMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+class ExecuteBinaryMessage : public Message
+{
+public:
+ std::string name;
+ std::string params;
+ std::string workDir;
+ std::string caseList;
+
+ ExecuteBinaryMessage (const deUint8* data, int dataSize);
+ ExecuteBinaryMessage (void) : Message(MESSAGETYPE_EXECUTE_BINARY) {}
+ ~ExecuteBinaryMessage (void) {};
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+class ProcessLogDataMessage : public Message
+{
+public:
+ std::string logData;
+
+ ProcessLogDataMessage (const deUint8* data, int dataSize);
+ ~ProcessLogDataMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+class ProcessLaunchFailedMessage : public Message
+{
+public:
+ std::string reason;
+
+ ProcessLaunchFailedMessage (const deUint8* data, int dataSize);
+ ProcessLaunchFailedMessage (const char* reason_) : Message(MESSAGETYPE_PROCESS_LAUNCH_FAILED), reason(reason_) {}
+ ~ProcessLaunchFailedMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+class ProcessFinishedMessage : public Message
+{
+public:
+ int exitCode;
+
+ ProcessFinishedMessage (const deUint8* data, int dataSize);
+ ProcessFinishedMessage (int exitCode_) : Message(MESSAGETYPE_PROCESS_FINISHED), exitCode(exitCode_) {}
+ ~ProcessFinishedMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+class InfoMessage : public Message
+{
+public:
+ std::string info;
+
+ InfoMessage (const deUint8* data, int dataSize);
+ ~InfoMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+// For debug purposes only.
+class TestMessage : public Message
+{
+public:
+ std::string test;
+
+ TestMessage (const deUint8* data, int dataSize);
+ ~TestMessage (void) {}
+
+ void write (std::vector<deUint8>& buf) const;
+};
+
+} // xs
+
+#endif // _XSPROTOCOL_HPP
diff --git a/execserver/xsTcpServer.cpp b/execserver/xsTcpServer.cpp
new file mode 100644
index 0000000..c8028c2
--- /dev/null
+++ b/execserver/xsTcpServer.cpp
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief TCP Server.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsTcpServer.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <cstdio>
+
+namespace xs
+{
+
+TcpServer::TcpServer (deSocketFamily family, int port)
+ : m_socket()
+{
+ de::SocketAddress address;
+ address.setFamily(family);
+ address.setPort(port);
+ address.setType(DE_SOCKETTYPE_STREAM);
+ address.setProtocol(DE_SOCKETPROTOCOL_TCP);
+
+ m_socket.listen(address);
+ m_socket.setFlags(DE_SOCKET_CLOSE_ON_EXEC);
+}
+
+void TcpServer::runServer (void)
+{
+ de::Socket* clientSocket = DE_NULL;
+ de::SocketAddress clientAddr;
+
+ while ((clientSocket = m_socket.accept(clientAddr)) != DE_NULL)
+ {
+ ConnectionHandler* handler = DE_NULL;
+
+ try
+ {
+ handler = createHandler(clientSocket, clientAddr);
+ }
+ catch (...)
+ {
+ delete clientSocket;
+ throw;
+ }
+
+ try
+ {
+ addLiveConnection(handler);
+ }
+ catch (...)
+ {
+ delete handler;
+ throw;
+ }
+
+ // Start handler.
+ handler->start();
+
+ // Perform connection list cleanup.
+ deleteDoneConnections();
+ }
+
+ // One more cleanup pass.
+ deleteDoneConnections();
+}
+
+void TcpServer::connectionDone (ConnectionHandler* handler)
+{
+ de::ScopedLock lock(m_connectionListLock);
+
+ std::vector<ConnectionHandler*>::iterator liveListPos = std::find(m_liveConnections.begin(), m_liveConnections.end(), handler);
+ DE_ASSERT(liveListPos != m_liveConnections.end());
+
+ m_doneConnections.reserve(m_doneConnections.size()+1);
+ m_liveConnections.erase(liveListPos);
+ m_doneConnections.push_back(handler);
+}
+
+void TcpServer::addLiveConnection (ConnectionHandler* handler)
+{
+ de::ScopedLock lock(m_connectionListLock);
+ m_liveConnections.push_back(handler);
+}
+
+void TcpServer::deleteDoneConnections (void)
+{
+ de::ScopedLock lock(m_connectionListLock);
+
+ for (std::vector<ConnectionHandler*>::iterator i = m_doneConnections.begin(); i != m_doneConnections.end(); i++)
+ delete *i;
+
+ m_doneConnections.clear();
+}
+
+void TcpServer::stopServer (void)
+{
+ // Close socket. This should get accept() to return null.
+ m_socket.close();
+}
+
+TcpServer::~TcpServer (void)
+{
+ try
+ {
+ std::vector<ConnectionHandler*> allConnections;
+
+ if (m_connectionListLock.tryLock())
+ {
+ // \note [pyry] It is possible that cleanup actually fails.
+ try
+ {
+ std::copy(m_liveConnections.begin(), m_liveConnections.end(), std::inserter(allConnections, allConnections.end()));
+ std::copy(m_doneConnections.begin(), m_doneConnections.end(), std::inserter(allConnections, allConnections.end()));
+ }
+ catch (...)
+ {
+ }
+ m_connectionListLock.unlock();
+ }
+
+ for (std::vector<ConnectionHandler*>::const_iterator i = allConnections.begin(); i != allConnections.end(); i++)
+ delete *i;
+
+ if (m_socket.getState() != DE_SOCKETSTATE_CLOSED)
+ m_socket.close();
+ }
+ catch (...)
+ {
+ // Nada, we're at destructor.
+ }
+}
+
+ConnectionHandler::~ConnectionHandler (void)
+{
+ delete m_socket;
+}
+
+void ConnectionHandler::run (void)
+{
+ try
+ {
+ handle();
+ }
+ catch (const std::exception& e)
+ {
+ printf("ConnectionHandler::run(): %s\n", e.what());
+ }
+
+ // Notify server that this connection is done.
+ m_server->connectionDone(this);
+}
+
+} // xs
diff --git a/execserver/xsTcpServer.hpp b/execserver/xsTcpServer.hpp
new file mode 100644
index 0000000..8a7c4cc
--- /dev/null
+++ b/execserver/xsTcpServer.hpp
@@ -0,0 +1,84 @@
+#ifndef _XSTCPSERVER_HPP
+#define _XSTCPSERVER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief TCP Server.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "deSocket.hpp"
+#include "deThread.hpp"
+#include "deMutex.hpp"
+
+#include <vector>
+
+namespace xs
+{
+
+class ConnectionHandler;
+
+class TcpServer
+{
+public:
+ TcpServer (deSocketFamily family, int port);
+ virtual ~TcpServer (void);
+
+ virtual ConnectionHandler* createHandler (de::Socket* socket, const de::SocketAddress& clientAddress) = DE_NULL;
+
+ virtual void runServer (void);
+ void stopServer (void);
+
+ virtual void connectionDone (ConnectionHandler* handler);
+
+protected:
+ de::Socket m_socket;
+
+private:
+ TcpServer (const TcpServer& other);
+ TcpServer& operator= (const TcpServer& other);
+
+ void addLiveConnection (ConnectionHandler* handler);
+ void deleteDoneConnections (void);
+
+ de::Mutex m_connectionListLock;
+ std::vector<ConnectionHandler*> m_liveConnections;
+ std::vector<ConnectionHandler*> m_doneConnections;
+};
+
+class ConnectionHandler : public de::Thread
+{
+public:
+ ConnectionHandler (TcpServer* server, de::Socket* socket) : m_server(server), m_socket(socket) {}
+ virtual ~ConnectionHandler (void);
+
+ void run (void);
+
+protected:
+ virtual void handle (void) = DE_NULL;
+
+protected:
+ TcpServer* m_server;
+ de::Socket* m_socket;
+};
+
+} // xs
+
+#endif // _XSTCPSERVER_HPP
diff --git a/execserver/xsTestDriver.cpp b/execserver/xsTestDriver.cpp
new file mode 100644
index 0000000..cf3bca0
--- /dev/null
+++ b/execserver/xsTestDriver.cpp
@@ -0,0 +1,239 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test Driver.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsTestDriver.hpp"
+#include "deClock.h"
+
+#include <string>
+#include <vector>
+#include <cstdio>
+
+using std::string;
+using std::vector;
+
+#if 0
+# define DBG_PRINT(X) printf X
+#else
+# define DBG_PRINT(X)
+#endif
+
+namespace xs
+{
+
+TestDriver::TestDriver (xs::TestProcess* testProcess)
+ : m_state (STATE_NOT_STARTED)
+ , m_lastExitCode (0)
+ , m_process (testProcess)
+ , m_lastProcessDataTime (0)
+ , m_dataMsgTmpBuf (SEND_RECV_TMP_BUFFER_SIZE)
+{
+}
+
+TestDriver::~TestDriver (void)
+{
+ reset();
+}
+
+void TestDriver::reset (void)
+{
+ m_process->cleanup();
+
+ m_state = STATE_NOT_STARTED;
+}
+
+void TestDriver::startProcess (const char* name, const char* params, const char* workingDir, const char* caseList)
+{
+ try
+ {
+ m_process->start(name, params, workingDir, caseList);
+ m_state = STATE_PROCESS_STARTED;
+ }
+ catch (const TestProcessException& e)
+ {
+ printf("Failed to launch test process: %s\n", e.what());
+ m_state = STATE_PROCESS_LAUNCH_FAILED;
+ m_lastLaunchFailure = e.what();
+ }
+}
+
+void TestDriver::stopProcess (void)
+{
+ m_process->terminate();
+}
+
+bool TestDriver::poll (ByteBuffer& messageBuffer)
+{
+ switch (m_state)
+ {
+ case STATE_NOT_STARTED:
+ return false; // Nothing to report.
+
+ case STATE_PROCESS_LAUNCH_FAILED:
+ DBG_PRINT((" STATE_PROCESS_LAUNCH_FAILED\n"));
+ if (writeMessage(messageBuffer, ProcessLaunchFailedMessage(m_lastLaunchFailure.c_str())))
+ {
+ m_state = STATE_NOT_STARTED;
+ m_lastLaunchFailure = "";
+ return true;
+ }
+ else
+ return false;
+
+ case STATE_PROCESS_STARTED:
+ DBG_PRINT((" STATE_PROCESS_STARTED\n"));
+ if (writeMessage(messageBuffer, ProcessStartedMessage()))
+ {
+ m_state = STATE_PROCESS_RUNNING;
+ return true;
+ }
+ else
+ return false;
+
+ case STATE_PROCESS_RUNNING:
+ {
+ DBG_PRINT((" STATE_PROCESS_RUNNING\n"));
+ bool gotProcessData = false;
+
+ // Poll log file and info buffer.
+ gotProcessData = pollLogFile(messageBuffer) || gotProcessData;
+ gotProcessData = pollInfo(messageBuffer) || gotProcessData;
+
+ if (gotProcessData)
+ return true; // Got IO.
+
+ if (!m_process->isRunning())
+ {
+ // Process died.
+ m_state = STATE_READING_DATA;
+ m_lastExitCode = m_process->getExitCode();
+ m_lastProcessDataTime = deGetMicroseconds();
+
+ return true; // Got state change.
+ }
+
+ return false; // Nothing to report.
+ }
+
+ case STATE_READING_DATA:
+ {
+ DBG_PRINT((" STATE_READING_DATA\n"));
+ bool gotProcessData = false;
+
+ // Poll log file and info buffer.
+ gotProcessData = pollLogFile(messageBuffer) || gotProcessData;
+ gotProcessData = pollInfo(messageBuffer) || gotProcessData;
+
+ if (gotProcessData)
+ {
+ // Got data.
+ m_lastProcessDataTime = deGetMicroseconds();
+ return true;
+ }
+ else if (deGetMicroseconds() - m_lastProcessDataTime > READ_DATA_TIMEOUT*1000)
+ {
+ // Read timeout occurred.
+ m_state = STATE_PROCESS_FINISHED;
+ return true; // State change.
+ }
+ else
+ return false; // Still waiting for data.
+ }
+
+ case STATE_PROCESS_FINISHED:
+ DBG_PRINT((" STATE_PROCESS_FINISHED\n"));
+ if (writeMessage(messageBuffer, ProcessFinishedMessage(m_lastExitCode)))
+ {
+ // Signal TestProcess to clean up any remaining resources.
+ m_process->cleanup();
+
+ m_state = STATE_NOT_STARTED;
+ m_lastExitCode = 0;
+ return true;
+ }
+ else
+ return false;
+
+ default:
+ DE_ASSERT(DE_FALSE);
+ return false;
+ }
+}
+
+bool TestDriver::pollLogFile (ByteBuffer& messageBuffer)
+{
+ return pollBuffer(messageBuffer, MESSAGETYPE_PROCESS_LOG_DATA);
+}
+
+bool TestDriver::pollInfo (ByteBuffer& messageBuffer)
+{
+ return pollBuffer(messageBuffer, MESSAGETYPE_INFO);
+}
+
+bool TestDriver::pollBuffer (ByteBuffer& messageBuffer, MessageType msgType)
+{
+ const int minBytesAvailable = MESSAGE_HEADER_SIZE + MIN_MSG_PAYLOAD_SIZE;
+
+ if (messageBuffer.getNumFree() < minBytesAvailable)
+ return false; // Not enough space in message buffer.
+
+ const int maxMsgSize = de::min((int)m_dataMsgTmpBuf.size(), messageBuffer.getNumFree());
+ int numRead = 0;
+ int msgSize = MESSAGE_HEADER_SIZE+1; // One byte is reserved for terminating 0.
+
+ // Fill in data \note Last byte is reserved for 0.
+ numRead = msgType == MESSAGETYPE_PROCESS_LOG_DATA
+ ? m_process->readTestLog(&m_dataMsgTmpBuf[MESSAGE_HEADER_SIZE], maxMsgSize-MESSAGE_HEADER_SIZE-1)
+ : m_process->readInfoLog(&m_dataMsgTmpBuf[MESSAGE_HEADER_SIZE], maxMsgSize-MESSAGE_HEADER_SIZE-1);
+
+ if (numRead <= 0)
+ return false; // Didn't get any data.
+
+ msgSize += numRead;
+
+ // Terminate with 0.
+ m_dataMsgTmpBuf[msgSize-1] = 0;
+
+ // Write header.
+ Message::writeHeader(msgType, msgSize, &m_dataMsgTmpBuf[0], MESSAGE_HEADER_SIZE);
+
+ // Write to messagebuffer.
+ messageBuffer.pushFront(&m_dataMsgTmpBuf[0], msgSize);
+
+ DBG_PRINT((" wrote %d bytes of %s data\n", msgSize, msgType == MESSAGETYPE_INFO ? "info" : "log"));
+
+ return true;
+}
+
+bool TestDriver::writeMessage (ByteBuffer& messageBuffer, const Message& message)
+{
+ vector<deUint8> buf;
+ message.write(buf);
+
+ if (messageBuffer.getNumFree() < (int)buf.size())
+ return false;
+
+ messageBuffer.pushFront(&buf[0], (int)buf.size());
+ return true;
+}
+
+} // xs
diff --git a/execserver/xsTestDriver.hpp b/execserver/xsTestDriver.hpp
new file mode 100644
index 0000000..9fd7a16
--- /dev/null
+++ b/execserver/xsTestDriver.hpp
@@ -0,0 +1,80 @@
+#ifndef _XSTESTDRIVER_HPP
+#define _XSTESTDRIVER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test Driver.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "xsProtocol.hpp"
+#include "xsTestProcess.hpp"
+
+#include <vector>
+
+namespace xs
+{
+
+class TestDriver
+{
+public:
+ TestDriver (xs::TestProcess* testProcess);
+ ~TestDriver (void);
+
+ void reset (void);
+
+ void startProcess (const char* name, const char* params, const char* workingDir, const char* caseList);
+ void stopProcess (void);
+
+ bool poll (ByteBuffer& messageBuffer);
+
+private:
+ enum State
+ {
+ STATE_NOT_STARTED = 0,
+ STATE_PROCESS_LAUNCH_FAILED,
+ STATE_PROCESS_STARTED,
+ STATE_PROCESS_RUNNING,
+ STATE_READING_DATA,
+ STATE_PROCESS_FINISHED,
+
+ STATE_LAST
+ };
+
+ bool pollLogFile (ByteBuffer& messageBuffer);
+ bool pollInfo (ByteBuffer& messageBuffer);
+ bool pollBuffer (ByteBuffer& messageBuffer, MessageType msgType);
+
+ bool writeMessage (ByteBuffer& messageBuffer, const Message& message);
+
+ State m_state;
+
+ std::string m_lastLaunchFailure;
+ int m_lastExitCode;
+
+ xs::TestProcess* m_process;
+ deUint64 m_lastProcessDataTime;
+
+ std::vector<deUint8> m_dataMsgTmpBuf;
+};
+
+} // xs
+
+#endif // _XSTESTDRIVER_HPP
diff --git a/execserver/xsTestProcess.cpp b/execserver/xsTestProcess.cpp
new file mode 100644
index 0000000..58e8142
--- /dev/null
+++ b/execserver/xsTestProcess.cpp
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test Process Abstraction.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsTestProcess.hpp"
+
+DE_EMPTY_CPP_FILE
diff --git a/execserver/xsTestProcess.hpp b/execserver/xsTestProcess.hpp
new file mode 100644
index 0000000..243e27d
--- /dev/null
+++ b/execserver/xsTestProcess.hpp
@@ -0,0 +1,60 @@
+#ifndef _XSTESTPROCESS_HPP
+#define _XSTESTPROCESS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Test Process Abstraction.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+
+#include <stdexcept>
+
+namespace xs
+{
+
+class TestProcessException : public std::runtime_error
+{
+public:
+ TestProcessException (const std::string& message) : std::runtime_error(message) {}
+};
+
+class TestProcess
+{
+public:
+ virtual ~TestProcess (void) {}
+
+ virtual void start (const char* name, const char* params, const char* workingDir, const char* caseList) = DE_NULL;
+ virtual void terminate (void) = DE_NULL;
+ virtual void cleanup (void) = DE_NULL;
+
+ virtual bool isRunning (void) = DE_NULL;
+ virtual int getExitCode (void) const = DE_NULL;
+
+ virtual int readTestLog (deUint8* dst, int numBytes) = DE_NULL;
+ virtual int readInfoLog (deUint8* dst, int numBytes) = DE_NULL;
+
+protected:
+ TestProcess (void) {}
+};
+
+} // xs
+
+#endif // _XSTESTPROCESS_HPP
diff --git a/execserver/xsWin32TestProcess.cpp b/execserver/xsWin32TestProcess.cpp
new file mode 100644
index 0000000..4dae80a
--- /dev/null
+++ b/execserver/xsWin32TestProcess.cpp
@@ -0,0 +1,792 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief TestProcess implementation for Win32.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsWin32TestProcess.hpp"
+#include "deFilePath.hpp"
+#include "deString.h"
+#include "deMemory.h"
+#include "deClock.h"
+#include "deFile.h"
+
+#include <sstream>
+#include <string.h>
+
+using std::string;
+using std::vector;
+
+namespace xs
+{
+
+enum
+{
+ MAX_OLD_LOGFILE_DELETE_ATTEMPTS = 20, //!< How many times execserver tries to delete old log file
+ LOGFILE_DELETE_SLEEP_MS = 50 //!< Sleep time (in ms) between log file delete attempts
+};
+
+namespace win32
+{
+
+// Error
+
+static std::string formatErrMsg (DWORD error, const char* msg)
+{
+ std::ostringstream str;
+ LPSTR msgBuf;
+
+#if defined(UNICODE)
+# error Unicode not supported.
+#endif
+
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msgBuf, 0, DE_NULL) > 0)
+ str << msg << ", error " << error << ": " << msgBuf;
+ else
+ str << msg << ", error " << error;
+
+ return str.str();
+}
+
+Error::Error (DWORD error, const char* msg)
+ : std::runtime_error(formatErrMsg(error, msg))
+ , m_error (error)
+{
+}
+
+// Event
+
+Event::Event (bool manualReset, bool initialState)
+ : m_handle(0)
+{
+ m_handle = CreateEvent(NULL, manualReset ? TRUE : FALSE, initialState ? TRUE : FALSE, NULL);
+ if (!m_handle)
+ throw Error(GetLastError(), "CreateEvent() failed");
+}
+
+Event::~Event (void)
+{
+ CloseHandle(m_handle);
+}
+
+void Event::setSignaled (void)
+{
+ if (!SetEvent(m_handle))
+ throw Error(GetLastError(), "SetEvent() failed");
+}
+
+void Event::reset (void)
+{
+ if (!ResetEvent(m_handle))
+ throw Error(GetLastError(), "ResetEvent() failed");
+}
+
+// CaseListWriter
+
+CaseListWriter::CaseListWriter (void)
+ : m_dst (INVALID_HANDLE_VALUE)
+ , m_cancelEvent (true, false)
+{
+}
+
+CaseListWriter::~CaseListWriter (void)
+{
+}
+
+void CaseListWriter::start (const char* caseList, HANDLE dst)
+{
+ DE_ASSERT(!isStarted());
+
+ m_dst = dst;
+
+ int caseListSize = (int)strlen(caseList)+1;
+ m_caseList.resize(caseListSize);
+ std::copy(caseList, caseList+caseListSize, m_caseList.begin());
+
+ de::Thread::start();
+}
+
+void CaseListWriter::run (void)
+{
+ try
+ {
+ Event ioEvent (true, false); // Manual reset, non-signaled state.
+ HANDLE waitHandles[] = { ioEvent.getHandle(), m_cancelEvent.getHandle() };
+ OVERLAPPED overlapped;
+ int curPos = 0;
+
+ deMemset(&overlapped, 0, sizeof(overlapped));
+ overlapped.hEvent = ioEvent.getHandle();
+
+ while (curPos < (int)m_caseList.size())
+ {
+ const int maxWriteSize = 4096;
+ const int numToWrite = de::min(maxWriteSize, (int)m_caseList.size() - curPos);
+ DWORD waitRes = 0;
+
+ if (!WriteFile(m_dst, &m_caseList[curPos], (DWORD)numToWrite, NULL, &overlapped))
+ {
+ DWORD err = GetLastError();
+ if (err != ERROR_IO_PENDING)
+ throw Error(err, "WriteFile() failed");
+ }
+
+ waitRes = WaitForMultipleObjects(DE_LENGTH_OF_ARRAY(waitHandles), &waitHandles[0], FALSE, INFINITE);
+
+ if (waitRes == WAIT_OBJECT_0)
+ {
+ DWORD numBytesWritten = 0;
+
+ // \note GetOverlappedResult() will fail with ERROR_IO_INCOMPLETE if IO event is not complete (should be).
+ if (!GetOverlappedResult(m_dst, &overlapped, &numBytesWritten, FALSE))
+ throw Error(GetLastError(), "GetOverlappedResult() failed");
+
+ if (numBytesWritten == 0)
+ throw Error(GetLastError(), "Writing to pipe failed (pipe closed?)");
+
+ curPos += (int)numBytesWritten;
+ }
+ else if (waitRes == WAIT_OBJECT_0 + 1)
+ {
+ // Cancel.
+ if (!CancelIo(m_dst))
+ throw Error(GetLastError(), "CancelIo() failed");
+ break;
+ }
+ else
+ throw Error(GetLastError(), "WaitForMultipleObjects() failed");
+ }
+ }
+ catch (const std::exception& e)
+ {
+ // \todo [2013-08-13 pyry] What to do about this?
+ printf("win32::CaseListWriter::run(): %s\n", e.what());
+ }
+}
+
+void CaseListWriter::stop (void)
+{
+ if (!isStarted())
+ return; // Nothing to do.
+
+ m_cancelEvent.setSignaled();
+
+ // Join thread.
+ join();
+
+ m_cancelEvent.reset();
+
+ m_dst = INVALID_HANDLE_VALUE;
+}
+
+// FileReader
+
+FileReader::FileReader (ThreadedByteBuffer* dst)
+ : m_dstBuf (dst)
+ , m_handle (INVALID_HANDLE_VALUE)
+ , m_cancelEvent (false, false)
+{
+}
+
+FileReader::~FileReader (void)
+{
+}
+
+void FileReader::start (HANDLE file)
+{
+ DE_ASSERT(!isStarted());
+
+ m_handle = file;
+
+ de::Thread::start();
+}
+
+void FileReader::run (void)
+{
+ try
+ {
+ Event ioEvent (true, false); // Manual reset, not signaled state.
+ HANDLE waitHandles[] = { ioEvent.getHandle(), m_cancelEvent.getHandle() };
+ OVERLAPPED overlapped;
+ std::vector<deUint8> tmpBuf (FILEREADER_TMP_BUFFER_SIZE);
+ deUint64 offset = 0; // Overlapped IO requires manual offset keeping.
+
+ deMemset(&overlapped, 0, sizeof(overlapped));
+ overlapped.hEvent = ioEvent.getHandle();
+
+ for (;;)
+ {
+ DWORD numBytesRead = 0;
+ DWORD waitRes;
+
+ overlapped.Offset = (DWORD)(offset & 0xffffffffu);
+ overlapped.OffsetHigh = (DWORD)(offset >> 32);
+
+ if (!ReadFile(m_handle, &tmpBuf[0], (DWORD)tmpBuf.size(), NULL, &overlapped))
+ {
+ DWORD err = GetLastError();
+
+ if (err == ERROR_BROKEN_PIPE)
+ break;
+ else if (err == ERROR_HANDLE_EOF)
+ {
+ if (m_dstBuf->isCanceled())
+ break;
+
+ deSleep(FILEREADER_IDLE_SLEEP);
+
+ if (m_dstBuf->isCanceled())
+ break;
+ else
+ continue;
+ }
+ else if (err != ERROR_IO_PENDING)
+ throw Error(err, "ReadFile() failed");
+ }
+
+ waitRes = WaitForMultipleObjects(DE_LENGTH_OF_ARRAY(waitHandles), &waitHandles[0], FALSE, INFINITE);
+
+ if (waitRes == WAIT_OBJECT_0)
+ {
+ // \note GetOverlappedResult() will fail with ERROR_IO_INCOMPLETE if IO event is not complete (should be).
+ if (!GetOverlappedResult(m_handle, &overlapped, &numBytesRead, FALSE))
+ {
+ DWORD err = GetLastError();
+
+ if (err == ERROR_HANDLE_EOF)
+ {
+ // End of file - for now.
+ // \note Should check for end of buffer here, or otherwise may end up in infinite loop.
+ if (m_dstBuf->isCanceled())
+ break;
+
+ deSleep(FILEREADER_IDLE_SLEEP);
+
+ if (m_dstBuf->isCanceled())
+ break;
+ else
+ continue;
+ }
+ else if (err == ERROR_BROKEN_PIPE)
+ break;
+ else
+ throw Error(err, "GetOverlappedResult() failed");
+ }
+
+ if (numBytesRead == 0)
+ throw Error(GetLastError(), "Reading from file failed");
+ else
+ offset += (deUint64)numBytesRead;
+ }
+ else if (waitRes == WAIT_OBJECT_0 + 1)
+ {
+ // Cancel.
+ if (!CancelIo(m_handle))
+ throw Error(GetLastError(), "CancelIo() failed");
+ break;
+ }
+ else
+ throw Error(GetLastError(), "WaitForMultipleObjects() failed");
+
+ try
+ {
+ m_dstBuf->write((int)numBytesRead, &tmpBuf[0]);
+ m_dstBuf->flush();
+ }
+ catch (const ThreadedByteBuffer::CanceledException&)
+ {
+ // Canceled.
+ break;
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ // \todo [2013-08-13 pyry] What to do?
+ printf("win32::FileReader::run(): %s\n", e.what());
+ }
+}
+
+void FileReader::stop (void)
+{
+ if (!isStarted())
+ return; // Nothing to do.
+
+ m_cancelEvent.setSignaled();
+
+ // Join thread.
+ join();
+
+ m_cancelEvent.reset();
+
+ m_handle = INVALID_HANDLE_VALUE;
+}
+
+// TestLogReader
+
+TestLogReader::TestLogReader (void)
+ : m_logBuffer (LOG_BUFFER_BLOCK_SIZE, LOG_BUFFER_NUM_BLOCKS)
+ , m_logFile (INVALID_HANDLE_VALUE)
+ , m_reader (&m_logBuffer)
+{
+}
+
+TestLogReader::~TestLogReader (void)
+{
+ if (m_logFile != INVALID_HANDLE_VALUE)
+ CloseHandle(m_logFile);
+}
+
+void TestLogReader::start (const char* filename)
+{
+ DE_ASSERT(m_logFile == INVALID_HANDLE_VALUE && !m_reader.isStarted());
+
+ m_logFile = CreateFile(filename,
+ GENERIC_READ,
+ FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
+ DE_NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
+ DE_NULL);
+
+ if (m_logFile == INVALID_HANDLE_VALUE)
+ throw Error(GetLastError(), "Failed to open log file");
+
+ m_reader.start(m_logFile);
+}
+
+void TestLogReader::stop (void)
+{
+ if (!m_reader.isStarted())
+ return; // Nothing to do.
+
+ m_logBuffer.cancel();
+ m_reader.stop();
+
+ CloseHandle(m_logFile);
+ m_logFile = INVALID_HANDLE_VALUE;
+
+ m_logBuffer.clear();
+}
+
+// Process
+
+Process::Process (void)
+ : m_state (STATE_NOT_STARTED)
+ , m_exitCode (0)
+ , m_standardIn (INVALID_HANDLE_VALUE)
+ , m_standardOut (INVALID_HANDLE_VALUE)
+ , m_standardErr (INVALID_HANDLE_VALUE)
+{
+ deMemset(&m_procInfo, 0, sizeof(m_procInfo));
+}
+
+Process::~Process (void)
+{
+ try
+ {
+ if (isRunning())
+ {
+ kill();
+ waitForFinish();
+ }
+ }
+ catch (...)
+ {
+ }
+
+ cleanupHandles();
+}
+
+void Process::cleanupHandles (void)
+{
+ DE_ASSERT(!isRunning());
+
+ if (m_standardErr != INVALID_HANDLE_VALUE)
+ CloseHandle(m_standardErr);
+
+ if (m_standardOut != INVALID_HANDLE_VALUE)
+ CloseHandle(m_standardOut);
+
+ if (m_standardIn != INVALID_HANDLE_VALUE)
+ CloseHandle(m_standardIn);
+
+ if (m_procInfo.hProcess)
+ CloseHandle(m_procInfo.hProcess);
+
+ if (m_procInfo.hThread)
+ CloseHandle(m_procInfo.hThread);
+
+ m_standardErr = INVALID_HANDLE_VALUE;
+ m_standardOut = INVALID_HANDLE_VALUE;
+ m_standardIn = INVALID_HANDLE_VALUE;
+
+ deMemset(&m_procInfo, 0, sizeof(m_procInfo));
+}
+
+__declspec(thread) static int t_pipeNdx = 0;
+
+static void createPipeWithOverlappedIO (HANDLE* readHandleOut, HANDLE* writeHandleOut, deUint32 readMode, deUint32 writeMode, SECURITY_ATTRIBUTES* securityAttr)
+{
+ const int defaultBufSize = 4096;
+ char pipeName[128];
+ HANDLE readHandle;
+ HANDLE writeHandle;
+
+ DE_ASSERT(((readMode | writeMode) & ~FILE_FLAG_OVERLAPPED) == 0);
+
+ deSprintf(pipeName, sizeof(pipeName), "\\\\.\\Pipe\\dEQP-ExecServer-%08x-%08x-%08x",
+ GetCurrentProcessId(),
+ GetCurrentThreadId(),
+ t_pipeNdx++);
+
+ readHandle = CreateNamedPipe(pipeName, /* Pipe name. */
+ PIPE_ACCESS_INBOUND|readMode, /* Open mode. */
+ PIPE_TYPE_BYTE|PIPE_WAIT, /* Pipe flags. */
+ 1, /* Max number of instances. */
+ defaultBufSize, /* Output buffer size. */
+ defaultBufSize, /* Input buffer size. */
+ 0, /* Use default timeout. */
+ securityAttr);
+
+ if (readHandle == INVALID_HANDLE_VALUE)
+ throw Error(GetLastError(), "CreateNamedPipe() failed");
+
+ writeHandle = CreateFile(pipeName,
+ GENERIC_WRITE, /* Access mode. */
+ 0, /* No sharing. */
+ securityAttr,
+ OPEN_EXISTING, /* Assume existing object. */
+ FILE_ATTRIBUTE_NORMAL|writeMode, /* Open mode / flags. */
+ DE_NULL /* Template file. */);
+
+ if (writeHandle == INVALID_HANDLE_VALUE)
+ {
+ DWORD openErr = GetLastError();
+ CloseHandle(readHandle);
+ throw Error(openErr, "Failed to open created pipe, CreateFile() failed");
+ }
+
+ *readHandleOut = readHandle;
+ *writeHandleOut = writeHandle;
+}
+
+void Process::start (const char* commandLine, const char* workingDirectory)
+{
+ // Pipes.
+ HANDLE stdInRead = INVALID_HANDLE_VALUE;
+ HANDLE stdInWrite = INVALID_HANDLE_VALUE;
+ HANDLE stdOutRead = INVALID_HANDLE_VALUE;
+ HANDLE stdOutWrite = INVALID_HANDLE_VALUE;
+ HANDLE stdErrRead = INVALID_HANDLE_VALUE;
+ HANDLE stdErrWrite = INVALID_HANDLE_VALUE;
+
+ if (m_state == STATE_RUNNING)
+ throw std::runtime_error("Process already running");
+ else if (m_state == STATE_FINISHED)
+ {
+ // Process finished, clean up old cruft.
+ cleanupHandles();
+ m_state = STATE_NOT_STARTED;
+ }
+
+ // Create pipes
+ try
+ {
+ SECURITY_ATTRIBUTES securityAttr;
+ STARTUPINFO startInfo;
+
+ deMemset(&startInfo, 0, sizeof(startInfo));
+ deMemset(&securityAttr, 0, sizeof(securityAttr));
+
+ // Security attributes for inheriting handle.
+ securityAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ securityAttr.bInheritHandle = TRUE;
+ securityAttr.lpSecurityDescriptor = DE_NULL;
+
+ createPipeWithOverlappedIO(&stdInRead, &stdInWrite, 0, FILE_FLAG_OVERLAPPED, &securityAttr);
+ createPipeWithOverlappedIO(&stdOutRead, &stdOutWrite, FILE_FLAG_OVERLAPPED, 0, &securityAttr);
+ createPipeWithOverlappedIO(&stdErrRead, &stdErrWrite, FILE_FLAG_OVERLAPPED, 0, &securityAttr);
+
+ if (!SetHandleInformation(stdInWrite, HANDLE_FLAG_INHERIT, 0) ||
+ !SetHandleInformation(stdOutRead, HANDLE_FLAG_INHERIT, 0) ||
+ !SetHandleInformation(stdErrRead, HANDLE_FLAG_INHERIT, 0))
+ throw Error(GetLastError(), "SetHandleInformation() failed");
+
+ // Startup info for process.
+ startInfo.cb = sizeof(startInfo);
+ startInfo.hStdError = stdErrWrite;
+ startInfo.hStdOutput = stdOutWrite;
+ startInfo.hStdInput = stdInRead;
+ startInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ if (!CreateProcess(DE_NULL, (LPTSTR)commandLine, DE_NULL, DE_NULL, TRUE /* inherit handles */, 0, DE_NULL, workingDirectory, &startInfo, &m_procInfo))
+ throw Error(GetLastError(), "CreateProcess() failed");
+ }
+ catch (...)
+ {
+ if (stdInRead != INVALID_HANDLE_VALUE) CloseHandle(stdInRead);
+ if (stdInWrite != INVALID_HANDLE_VALUE) CloseHandle(stdInWrite);
+ if (stdOutRead != INVALID_HANDLE_VALUE) CloseHandle(stdOutRead);
+ if (stdOutWrite != INVALID_HANDLE_VALUE) CloseHandle(stdOutWrite);
+ if (stdErrRead != INVALID_HANDLE_VALUE) CloseHandle(stdErrRead);
+ if (stdErrWrite != INVALID_HANDLE_VALUE) CloseHandle(stdErrWrite);
+ throw;
+ }
+
+ // Store handles to be kept.
+ m_standardIn = stdInWrite;
+ m_standardOut = stdOutRead;
+ m_standardErr = stdErrRead;
+
+ // Close other ends of handles.
+ CloseHandle(stdErrWrite);
+ CloseHandle(stdOutWrite);
+ CloseHandle(stdInRead);
+
+ m_state = STATE_RUNNING;
+}
+
+bool Process::isRunning (void)
+{
+ if (m_state == STATE_RUNNING)
+ {
+ int exitCode;
+ BOOL result = GetExitCodeProcess(m_procInfo.hProcess, (LPDWORD)&exitCode);
+
+ if (result != TRUE)
+ throw Error(GetLastError(), "GetExitCodeProcess() failed");
+
+ if (exitCode == STILL_ACTIVE)
+ return true;
+ else
+ {
+ // Done.
+ m_exitCode = exitCode;
+ m_state = STATE_FINISHED;
+ return false;
+ }
+ }
+ else
+ return false;
+}
+
+void Process::waitForFinish (void)
+{
+ if (m_state == STATE_RUNNING)
+ {
+ if (WaitForSingleObject(m_procInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
+ throw Error(GetLastError(), "Waiting for process failed, WaitForSingleObject() failed");
+
+ if (isRunning())
+ throw std::runtime_error("Process is still alive");
+ }
+ else
+ throw std::runtime_error("Process is not running");
+}
+
+void Process::stopProcess (bool kill)
+{
+ if (m_state == STATE_RUNNING)
+ {
+ if (!TerminateProcess(m_procInfo.hProcess, kill ? -1 : 0))
+ throw Error(GetLastError(), "TerminateProcess() failed");
+ }
+ else
+ throw std::runtime_error("Process is not running");
+}
+
+void Process::terminate (void)
+{
+ stopProcess(false);
+}
+
+void Process::kill (void)
+{
+ stopProcess(true);
+}
+
+} // win32
+
+Win32TestProcess::Win32TestProcess (void)
+ : m_process (DE_NULL)
+ , m_processStartTime (0)
+ , m_infoBuffer (INFO_BUFFER_BLOCK_SIZE, INFO_BUFFER_NUM_BLOCKS)
+ , m_stdOutReader (&m_infoBuffer)
+ , m_stdErrReader (&m_infoBuffer)
+{
+}
+
+Win32TestProcess::~Win32TestProcess (void)
+{
+ delete m_process;
+}
+
+void Win32TestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList)
+{
+ bool hasCaseList = strlen(caseList) > 0;
+
+ XS_CHECK(!m_process);
+
+ de::FilePath logFilePath = de::FilePath::join(workingDir, "TestResults.qpa");
+ m_logFileName = logFilePath.getPath();
+
+ // Remove old file if such exists.
+ // \note Sometimes on Windows the test process dies slowly and may not release handle to log file
+ // until a bit later.
+ // \todo [2013-07-15 pyry] This should be solved by improving deProcess and killing all child processes as well.
+ {
+ int tryNdx = 0;
+ while (tryNdx < MAX_OLD_LOGFILE_DELETE_ATTEMPTS && deFileExists(m_logFileName.c_str()))
+ {
+ if (deDeleteFile(m_logFileName.c_str()))
+ break;
+ deSleep(LOGFILE_DELETE_SLEEP_MS);
+ tryNdx += 1;
+ }
+
+ if (deFileExists(m_logFileName.c_str()))
+ throw TestProcessException(string("Failed to remove '") + m_logFileName + "'");
+ }
+
+ // Construct command line.
+ string cmdLine = de::FilePath(name).isAbsolutePath() ? name : de::FilePath::join(workingDir, name).normalize().getPath();
+ cmdLine += string(" --deqp-log-filename=") + logFilePath.getBaseName();
+
+ if (hasCaseList)
+ cmdLine += " --deqp-stdin-caselist";
+
+ if (strlen(params) > 0)
+ cmdLine += string(" ") + params;
+
+ DE_ASSERT(!m_process);
+ m_process = new win32::Process();
+
+ try
+ {
+ m_process->start(cmdLine.c_str(), strlen(workingDir) > 0 ? workingDir : DE_NULL);
+ }
+ catch (const std::exception& e)
+ {
+ delete m_process;
+ m_process = DE_NULL;
+ throw TestProcessException(e.what());
+ }
+
+ m_processStartTime = deGetMicroseconds();
+
+ // Create stdout & stderr readers.
+ m_stdOutReader.start(m_process->getStdOut());
+ m_stdErrReader.start(m_process->getStdErr());
+
+ // Start case list writer.
+ if (hasCaseList)
+ m_caseListWriter.start(caseList, m_process->getStdIn());
+}
+
+void Win32TestProcess::terminate (void)
+{
+ if (m_process)
+ {
+ try
+ {
+ m_process->kill();
+ }
+ catch (const std::exception& e)
+ {
+ printf("Win32TestProcess::terminate(): Failed to kill process: %s\n", e.what());
+ }
+ }
+}
+
+void Win32TestProcess::cleanup (void)
+{
+ m_caseListWriter.stop();
+
+ // \note Buffers must be canceled before stopping readers.
+ m_infoBuffer.cancel();
+
+ m_stdErrReader.stop();
+ m_stdOutReader.stop();
+ m_testLogReader.stop();
+
+ // Reset buffers.
+ m_infoBuffer.clear();
+
+ if (m_process)
+ {
+ try
+ {
+ if (m_process->isRunning())
+ {
+ m_process->kill();
+ m_process->waitForFinish();
+ }
+ }
+ catch (const std::exception& e)
+ {
+ printf("Win32TestProcess::cleanup(): Failed to kill process: %s\n", e.what());
+ }
+
+ delete m_process;
+ m_process = DE_NULL;
+ }
+}
+
+int Win32TestProcess::readTestLog (deUint8* dst, int numBytes)
+{
+ if (!m_testLogReader.isRunning())
+ {
+ if (deGetMicroseconds() - m_processStartTime > LOG_FILE_TIMEOUT*1000)
+ {
+ // Timeout, kill process.
+ terminate();
+ return 0; // \todo [2013-08-13 pyry] Throw exception?
+ }
+
+ if (!deFileExists(m_logFileName.c_str()))
+ return 0;
+
+ // Start reader.
+ m_testLogReader.start(m_logFileName.c_str());
+ }
+
+ DE_ASSERT(m_testLogReader.isRunning());
+ return m_testLogReader.read(dst, numBytes);
+}
+
+bool Win32TestProcess::isRunning (void)
+{
+ if (m_process)
+ return m_process->isRunning();
+ else
+ return false;
+}
+
+int Win32TestProcess::getExitCode (void) const
+{
+ if (m_process)
+ return m_process->getExitCode();
+ else
+ return -1;
+}
+
+} // xs
diff --git a/execserver/xsWin32TestProcess.hpp b/execserver/xsWin32TestProcess.hpp
new file mode 100644
index 0000000..b7acf89
--- /dev/null
+++ b/execserver/xsWin32TestProcess.hpp
@@ -0,0 +1,214 @@
+#ifndef _XSWIN32TESTPROCESS_HPP
+#define _XSWIN32TESTPROCESS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Execution Server
+ * ---------------------------------------------
+ *
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief TestProcess implementation for Win32.
+ *//*--------------------------------------------------------------------*/
+
+#include "xsDefs.hpp"
+#include "xsTestProcess.hpp"
+#include "deThread.hpp"
+
+#include <vector>
+#include <string>
+
+#if !defined(VC_EXTRALEAN)
+# define VC_EXTRALEAN 1
+#endif
+#if !defined(WIN32_LEAN_AND_MEAN)
+# define WIN32_LEAN_AND_MEAN 1
+#endif
+#if !defined(NOMINMAX)
+# define NOMINMAX 1
+#endif
+#include <windows.h>
+
+namespace xs
+{
+namespace win32
+{
+
+class Error : public std::runtime_error
+{
+public:
+ Error (DWORD error, const char* msg);
+
+private:
+ DWORD m_error;
+};
+
+class Event
+{
+public:
+ Event (bool manualReset, bool initialState);
+ ~Event (void);
+
+ void setSignaled (void);
+ void reset (void);
+
+ HANDLE getHandle (void) const { return m_handle; }
+
+private:
+ Event (const Event& other);
+ Event& operator= (const Event& other);
+
+ HANDLE m_handle;
+};
+
+class CaseListWriter : public de::Thread
+{
+public:
+ CaseListWriter (void);
+ ~CaseListWriter (void);
+
+ void start (const char* caseList, HANDLE dst);
+ void stop (void);
+
+ void run (void);
+
+private:
+ std::vector<char> m_caseList;
+ HANDLE m_dst;
+ Event m_cancelEvent;
+};
+
+class FileReader : public de::Thread
+{
+public:
+ FileReader (ThreadedByteBuffer* dst);
+ ~FileReader (void);
+
+ void start (HANDLE file);
+ void stop (void);
+
+ void run (void);
+
+private:
+ ThreadedByteBuffer* m_dstBuf;
+ HANDLE m_handle;
+ Event m_cancelEvent;
+};
+
+class TestLogReader
+{
+public:
+ TestLogReader (void);
+ ~TestLogReader (void);
+
+ void start (const char* filename);
+ void stop (void);
+
+ bool isRunning (void) const { return m_reader.isStarted(); }
+
+ int read (deUint8* dst, int numBytes) { return m_logBuffer.tryRead(numBytes, dst); }
+
+private:
+ ThreadedByteBuffer m_logBuffer;
+ HANDLE m_logFile;
+
+ FileReader m_reader;
+};
+
+// \note deProcess uses anonymous pipes that don't have overlapped IO available.
+// For ExecServer purposes we need overlapped IO, and it makes the handles
+// incompatible with deFile. Thus separate Process implementation is used for now.
+class Process
+{
+public:
+ Process (void);
+ ~Process (void);
+
+ void start (const char* commandLine, const char* workingDirectory);
+
+ void waitForFinish (void);
+ void terminate (void);
+ void kill (void);
+
+ bool isRunning (void);
+ int getExitCode (void) const { return m_exitCode; }
+
+ HANDLE getStdIn (void) const { return m_standardIn; }
+ HANDLE getStdOut (void) const { return m_standardOut; }
+ HANDLE getStdErr (void) const { return m_standardErr; }
+
+private:
+ Process (const Process& other);
+ Process& operator= (const Process& other);
+
+ void stopProcess (bool kill);
+ void cleanupHandles (void);
+
+ enum State
+ {
+ STATE_NOT_STARTED = 0,
+ STATE_RUNNING,
+ STATE_FINISHED,
+
+ STATE_LAST
+ };
+
+ State m_state;
+ int m_exitCode;
+
+ PROCESS_INFORMATION m_procInfo;
+ HANDLE m_standardIn;
+ HANDLE m_standardOut;
+ HANDLE m_standardErr;
+};
+
+} // win32
+
+class Win32TestProcess : public TestProcess
+{
+public:
+ Win32TestProcess (void);
+ virtual ~Win32TestProcess (void);
+
+ virtual void start (const char* name, const char* params, const char* workingDir, const char* caseList);
+ virtual void terminate (void);
+ virtual void cleanup (void);
+
+ virtual bool isRunning (void);
+ virtual int getExitCode (void) const;
+
+ virtual int readTestLog (deUint8* dst, int numBytes);
+ virtual int readInfoLog (deUint8* dst, int numBytes) { return m_infoBuffer.tryRead(numBytes, dst); }
+
+private:
+ Win32TestProcess (const Win32TestProcess& other);
+ Win32TestProcess& operator= (const Win32TestProcess& other);
+
+ win32::Process* m_process;
+ deUint64 m_processStartTime;
+ std::string m_logFileName;
+
+ ThreadedByteBuffer m_infoBuffer;
+
+ // Threads.
+ win32::CaseListWriter m_caseListWriter;
+ win32::FileReader m_stdOutReader;
+ win32::FileReader m_stdErrReader;
+ win32::TestLogReader m_testLogReader;
+};
+
+} // xs
+
+#endif // _XSWIN32TESTPROCESS_HPP