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