1
0
This repository has been archived on 2025-07-27. You can view files and clone it, but cannot push or open issues or pull requests.
Files
siadrive/UnitTests/MockSiad.cpp
Scott E. Graves 6bd4b75d78 Fix unit tests
2017-03-13 01:15:06 -05:00

239 lines
5.9 KiB
C++

#include "stdafx.h"
#include "MockSiad.h"
#include <future>
#include <SiaCommon.h>
using namespace Sia::Api;
class CMockClient
{
private:
const std::string SUCESS_RESPONSE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: {ContentLength}\r\nContent-Type: application/json\r\n\r\n{Content}";
const std::string NOT_FOUND_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 17\r\nContent-Type: application/json\r\n\r\n{\"message\":\"404\"}";
const std::string SERVER_VERSION_SUCCESS_CONTENT = "{\"version\": \"1.1.1\"}";
public:
CMockClient(SOCKET socket, std::function<void(CMockClient*)> removedCallback) :
_socket(socket),
_stopEvent(::CreateEvent(nullptr, FALSE, FALSE, nullptr))
{
}
public:
~CMockClient()
{
::SetEvent(_stopEvent);
_clientThread->join();
::CloseHandle(_stopEvent);
}
private:
SOCKET _socket;
HANDLE _stopEvent;
std::function<void(CMockClient*)> NotifyClientRemoved;
std::unique_ptr<std::thread> _clientThread;
private:
void Run()
{
bool connected = true;
while (connected && (::WaitForSingleObject(_stopEvent, 1) == WAIT_TIMEOUT))
{
timeval tv = { 1, 0 };
FD_SET readFds, exceptFds;
FD_ZERO(&readFds);
FD_ZERO(&exceptFds);
FD_SET(_socket, &readFds);
FD_SET(_socket, &exceptFds);
int selectResult = ::select(0, &readFds, nullptr, &exceptFds, &tv);
if (selectResult != SOCKET_ERROR)
{
if (FD_ISSET(_socket, &readFds))
{
std::vector<char> buffer(1024);
int recvd;
std::string response;
{
std::string tmp;
while (!response.length() && ((recvd = ::recv(_socket, &buffer[0], buffer.size(), 0))))
{
tmp += std::string(&buffer[0], recvd);
int position = tmp.find_first_of("\r\n\r\n");
if (position > -1)
{
std::string header = tmp.substr(0, position);
if (header == "GET /daemon/version HTTP/1.1")
{
response = SUCESS_RESPONSE_JSON;
response = ReplaceStringInPlace(ReplaceStringInPlace(response, "{ContentLength}", std::to_string(SERVER_VERSION_SUCCESS_CONTENT.length())), "{Content}", SERVER_VERSION_SUCCESS_CONTENT);
}
else
{
response = NOT_FOUND_RESPONSE;
}
}
}
}
if (response.size())
{
::send(_socket, &response[0], response.length(), 0);
}
}
else if (FD_ISSET(_socket, &exceptFds))
{
connected = false;
}
}
}
closesocket(_socket);
NotifyClientRemoved(this);
}
public:
void Start()
{
_clientThread.reset(new std::thread([this]()
{
Run();
}));
}
void Close()
{
::SetEvent(_stopEvent);
_clientThread->join();
}
};
CMockSiad::CMockSiad(const SiaHostConfig& hostConfig) :
_hostConfig(hostConfig),
_stopEvent(::CreateEvent(nullptr, FALSE, FALSE, nullptr))
{
const WORD ver = MAKEWORD(2, 2);
WSADATA d = { 0 };
::WSAStartup(ver, &d);
}
CMockSiad::~CMockSiad()
{
Stop();
CloseHandle(_stopEvent);
::WSACleanup();
}
void CMockSiad::Start(const SiadTestType& testType)
{
if (!_serverThread)
{
HANDLE startedEvent = ::CreateEvent(nullptr, false, false, nullptr);
_serverThread.reset(new std::thread([this,&startedEvent]()
{
bool started = false;
bool active = true;
std::mutex clientMutex;
std::vector<CMockClient*> connected;
std::vector<CMockClient*> disconnected;
auto ClearDisconnected = [&]()
{
std::lock_guard<std::mutex> l(clientMutex);
while (disconnected.size())
{
CMockClient* client = disconnected.back();
disconnected.pop_back();
delete client;
}
};
while (active)
{
SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in sockAddr = { 0 };
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr(CW2A(_hostConfig.HostName.c_str()));
sockAddr.sin_port = htons(_hostConfig.HostPort);
bool listening = ((listenSocket != INVALID_SOCKET) && (::bind(listenSocket, reinterpret_cast<const struct sockaddr *>(&sockAddr), sizeof(sockAddr)) != SOCKET_ERROR)) && (::listen(listenSocket, 1) != SOCKET_ERROR);
while (listening && active)
{
if (!started)
{
::SetEvent(startedEvent);
started = true;
}
ClearDisconnected();
timeval tv = { 1, 0 };
FD_SET readFds, exceptFds;
FD_ZERO(&readFds);
FD_ZERO(&exceptFds);
FD_SET(listenSocket, &readFds);
FD_SET(listenSocket, &exceptFds);
int selectResult = ::select(0, &readFds, nullptr, &exceptFds, &tv);
if (selectResult != SOCKET_ERROR)
{
if (FD_ISSET(listenSocket, &readFds))
{
std::lock_guard<std::mutex> l(clientMutex);
SOCKET clientSocket = ::accept(listenSocket, nullptr, nullptr);
CMockClient* client = new CMockClient(clientSocket, [this, &clientMutex, &connected, &disconnected](CMockClient* removedClient)
{
std::lock_guard<std::mutex> l(clientMutex);
connected.erase(std::remove_if(connected.begin(), connected.end(), [&](CMockClient* client) -> bool { return client == removedClient; }), connected.end());
disconnected.push_back(removedClient);
});
client->Start();
}
else if (FD_ISSET(listenSocket, &exceptFds))
{
listening = false;
}
}
active = (::WaitForSingleObject(_stopEvent, 1) == WAIT_TIMEOUT);
listening = listening && listening;
}
closesocket(listenSocket);
while (connected.size())
{
CMockClient* client = nullptr;
{
std::lock_guard<std::mutex> l(clientMutex);
if (connected.size())
{
client = connected.back();
}
}
if (client)
{
client->Close();
}
}
active = active && (::WaitForSingleObject(_stopEvent, 1) == WAIT_TIMEOUT);
}
ClearDisconnected();
}));
::WaitForSingleObject(startedEvent, 5000);
::CloseHandle(startedEvent);
}
}
void CMockSiad::Stop()
{
if (_serverThread)
{
::SetEvent(_stopEvent);
_serverThread->join();
_serverThread.reset(nullptr);
}
}