/*-------------------------------------------------------------------------
* 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 "deUniquePtr.hpp"
#include "deString.h"
#include <memory>
#include <sstream>
#include <fstream>
#include <cstdio>
#include <cstdlib>
using std::string;
using std::vector;
namespace xs
{
typedef de::UniquePtr<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.
size_t pos = 0;
while (pos < buf.size())
{
size_t numLeft = buf.size() - pos;
size_t 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, size_t numBytes)
{
size_t numRead = 0;
dst.resize(numBytes);
while (numRead < numBytes)
{
size_t numLeft = numBytes - numRead;
size_t 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;
size_t 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);
}