view C++/tests/Socket/main.cpp @ 350:5a1ec6603230

Socket: add new kqueue backend
author David Demelier <markand@malikania.fr>
date Tue, 07 Apr 2015 14:44:44 +0200
parents b8d6b7f0bec4
children 6c0015524ca9
line wrap: on
line source

/*
 * main.cpp -- test sockets
 *
 * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

#include <gtest/gtest.h>

#include "Socket.h"
#include "SocketAddress.h"
#include "SocketListener.h"
#include "SocketSsl.h"
#include "SocketTcp.h"
#include "SocketUdp.h"

using namespace address;
using namespace std::literals::chrono_literals;

/* --------------------------------------------------------
 * TCP tests
 * -------------------------------------------------------- */

class TcpServerTest : public testing::Test {
protected:
	SocketTcp m_server{AF_INET, 0};
	SocketTcp m_client{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	TcpServerTest()
	{
		m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
	}

	~TcpServerTest()
	{
		if (m_tserver.joinable())
			m_tserver.join();
		if (m_tclient.joinable())
			m_tclient.join();
	}
};

TEST_F(TcpServerTest, connect)
{
	m_tserver = std::thread([this] () {
		m_server.bind(Internet("*", 16000, AF_INET));

		ASSERT_EQ(SocketState::Bound, m_server.state());

		m_server.listen();
		m_server.accept();
		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));

		ASSERT_EQ(SocketState::Connected, m_client.state());

		m_client.close();
	});
}

TEST_F(TcpServerTest, io)
{
	m_tserver = std::thread([this] () {
		m_server.bind(Internet("*", 16000, AF_INET));
		m_server.listen();

		auto client = m_server.accept();
		auto msg = client.recv(512);

		ASSERT_EQ("hello world", msg);

		client.send(msg);
		client.close();

		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_client.send("hello world");

		ASSERT_EQ("hello world", m_client.recv(512));

		m_client.close();
	});
}

/* --------------------------------------------------------
 * UDP tests
 * -------------------------------------------------------- */

class UdpServerTest : public testing::Test {
protected:
	SocketUdp m_server{AF_INET, 0};
	SocketUdp m_client{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	UdpServerTest()
	{
		m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
	}

	~UdpServerTest()
	{
		if (m_tserver.joinable())
			m_tserver.join();
		if (m_tclient.joinable())
			m_tclient.join();
	}
};

TEST_F(UdpServerTest, io)
{
	m_tserver = std::thread([this] () {
		SocketAddress info;

		m_server.bind(Internet("*", 16000, AF_INET));

		auto msg = m_server.recvfrom(512, info);

		ASSERT_EQ("hello world", msg);

		m_server.sendto(msg, info);
		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		Internet info("127.0.0.1", 16000, AF_INET);

		m_client.sendto("hello world", info);

		ASSERT_EQ("hello world", m_client.recvfrom(512, info));

		m_client.close();
	});
}

/* --------------------------------------------------------
 * Listener tests (standard)
 * -------------------------------------------------------- */

class ListenerTest : public testing::Test {
protected:
	SocketListenerBase<backend::Select> m_listener;
	SocketTcp socket1{AF_INET, 0};
	SocketUdp socket2{AF_INET, 0};

public:
	~ListenerTest()
	{
		socket1.close();
		socket2.close();
	}
};

TEST_F(ListenerTest, set)
{
	m_listener.set(socket1, SocketListener::Read);

	ASSERT_EQ(1, static_cast<int>(m_listener.size()));
	ASSERT_EQ(SocketListener::Read, m_listener.begin()->second);

	m_listener.set(socket1, SocketListener::Write);

	ASSERT_EQ(1, static_cast<int>(m_listener.size()));
	ASSERT_EQ(0x3, m_listener.begin()->second);

	// Fake a re-insert of the same socket
	m_listener.set(socket1, SocketListener::Write);

	ASSERT_EQ(1, static_cast<int>(m_listener.size()));
	ASSERT_EQ(0x3, m_listener.begin()->second);

	// Add an other socket now
	m_listener.set(socket2, SocketListener::Read | SocketListener::Write);

	ASSERT_EQ(2, static_cast<int>(m_listener.size()));

	for (auto &pair : m_listener) {
		ASSERT_EQ(0x3, pair.second);
		ASSERT_TRUE(pair.first == socket1 || pair.first == socket2);
	}
}

TEST_F(ListenerTest, unset)
{
	m_listener.set(socket1, SocketListener::Read | SocketListener::Write);
	m_listener.set(socket2, SocketListener::Read | SocketListener::Write);

	m_listener.unset(socket1, SocketListener::Read);

	ASSERT_EQ(2, static_cast<int>(m_listener.size()));

	// Use a for loop since it can be ordered differently
	for (auto &pair : m_listener) {
		if (pair.first == socket1) {
			ASSERT_EQ(0x2, pair.second);
		} else if (pair.first == socket2) {
			ASSERT_EQ(0x3, pair.second);
		}
	}

	m_listener.unset(socket1, SocketListener::Write);

	ASSERT_EQ(1, static_cast<int>(m_listener.size()));
	ASSERT_EQ(0x3, m_listener.begin()->second);
}

TEST_F(ListenerTest, remove)
{
	m_listener.set(socket1, SocketListener::Read | SocketListener::Write);
	m_listener.set(socket2, SocketListener::Read | SocketListener::Write);
	m_listener.remove(socket1);

	ASSERT_EQ(1, static_cast<int>(m_listener.size()));
	ASSERT_EQ(0x3, m_listener.begin()->second);
}

TEST_F(ListenerTest, clear)
{
	m_listener.set(socket1, SocketListener::Read | SocketListener::Write);
	m_listener.set(socket2, SocketListener::Read | SocketListener::Write);
	m_listener.clear();

	ASSERT_EQ(0, static_cast<int>(m_listener.size()));
}

/* --------------------------------------------------------
 * Listener: poll
 * -------------------------------------------------------- */

#if defined(SOCKET_HAVE_POLL)

class ListenerPollTest : public testing::Test {
protected:
	SocketListenerBase<backend::Poll> m_listener;
	SocketTcp m_masterTcp{AF_INET, 0};
	SocketTcp m_clientTcp{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	ListenerPollTest()
	{
		m_masterTcp.set(SOL_SOCKET, SO_REUSEADDR, 1);
		m_masterTcp.bind(Internet("*", 16000, AF_INET));
		m_masterTcp.listen();
	}

	~ListenerPollTest()
	{
		if (m_tserver.joinable()) {
			m_tserver.join();
		}
		if (m_tclient.joinable()) {
			m_tclient.join();
		}
	}
};

TEST_F(ListenerPollTest, accept)
{
	m_tserver = std::thread([this] () {
		try {
			m_listener.set(m_masterTcp, SocketListener::Read);
			m_listener.wait();
			m_masterTcp.accept();
			m_masterTcp.close();
		} catch (const std::exception &ex) {
			FAIL() << ex.what();
		}
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_clientTcp.connect(Internet("127.0.0.1", 16000, AF_INET));
	});
}

TEST_F(ListenerPollTest, recv)
{
	m_tserver = std::thread([this] () {
		try {
			m_listener.set(m_masterTcp, SocketListener::Read);
			m_listener.wait();

			auto sc = m_masterTcp.accept();

			ASSERT_EQ("hello", sc.recv(512));

			m_masterTcp.close();
		} catch (const std::exception &ex) {
			FAIL() << ex.what();
		}
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_clientTcp.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_clientTcp.send("hello");
	});
}

#endif

/* --------------------------------------------------------
 * Listener: select
 * -------------------------------------------------------- */

class ListenerSelectTest : public testing::Test {
protected:
	SocketListenerBase<backend::Select> m_listener;
	SocketTcp m_masterTcp{AF_INET, 0};
	SocketTcp m_clientTcp{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	ListenerSelectTest()
	{
		m_masterTcp.set(SOL_SOCKET, SO_REUSEADDR, 1);
		m_masterTcp.bind(Internet("*", 16000, AF_INET));
		m_masterTcp.listen();
	}

	~ListenerSelectTest()
	{
		if (m_tserver.joinable()) {
			m_tserver.join();
		}
		if (m_tclient.joinable()) {
			m_tclient.join();
		}
	}
};

TEST_F(ListenerSelectTest, accept)
{
	m_tserver = std::thread([this] () {
		try {
			m_listener.set(m_masterTcp, SocketListener::Read);
			m_listener.wait();
			m_masterTcp.accept();
			m_masterTcp.close();
		} catch (const std::exception &ex) {
			FAIL() << ex.what();
		}
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_clientTcp.connect(Internet("127.0.0.1", 16000, AF_INET));
	});
}

TEST_F(ListenerSelectTest, recv)
{
	m_tserver = std::thread([this] () {
		try {
			m_listener.set(m_masterTcp, SocketListener::Read);
			m_listener.wait();

			auto sc = m_masterTcp.accept();

			ASSERT_EQ("hello", sc.recv(512));

			m_masterTcp.close();
		} catch (const std::exception &ex) {
			FAIL() << ex.what();
		}
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_clientTcp.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_clientTcp.send("hello");
	});
}

/* --------------------------------------------------------
 * Listener: kqueue
 * -------------------------------------------------------- */

#if defined(SOCKET_HAVE_KQUEUE)

class ListenerKqueueTest : public testing::Test {
protected:
	SocketListenerBase<backend::Kqueue> m_listener;
	SocketTcp m_masterTcp{AF_INET, 0};
	SocketTcp m_clientTcp{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	ListenerKqueueTest()
	{
		m_masterTcp.set(SOL_SOCKET, SO_REUSEADDR, 1);
		m_masterTcp.bind(Internet("*", 16000, AF_INET));
		m_masterTcp.listen();
	}

	~ListenerKqueueTest()
	{
		if (m_tserver.joinable()) {
			m_tserver.join();
		}
		if (m_tclient.joinable()) {
			m_tclient.join();
		}
	}
};

TEST_F(ListenerKqueueTest, accept)
{
	m_tserver = std::thread([this] () {
		try {
			m_listener.set(m_masterTcp, SocketListener::Read);
			m_listener.wait();
			m_masterTcp.accept();
			m_masterTcp.close();
		} catch (const std::exception &ex) {
			FAIL() << ex.what();
		}
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_clientTcp.connect(Internet("127.0.0.1", 16000, AF_INET));
	});
}

TEST_F(ListenerKqueueTest, recv)
{
	m_tserver = std::thread([this] () {
		try {
			m_listener.set(m_masterTcp, SocketListener::Read);
			m_listener.wait();

			auto sc = m_masterTcp.accept();

			ASSERT_EQ("hello", sc.recv(512));

			m_masterTcp.close();
		} catch (const std::exception &ex) {
			FAIL() << ex.what();
		}
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_clientTcp.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_clientTcp.send("hello");
	});
}

#endif // !SOCKET_HAVE_KQUEUE

/* --------------------------------------------------------
 * Non-blocking connect
 * -------------------------------------------------------- */

class NonBlockingConnectTest : public testing::Test {
protected:
	SocketTcp m_server{AF_INET, 0};
	SocketTcp m_client{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	NonBlockingConnectTest()
	{
		m_client.setBlockMode(false);
	}

	~NonBlockingConnectTest()
	{
		if (m_tserver.joinable())
			m_tserver.join();
		if (m_tclient.joinable())
			m_tclient.join();
	}
};

TEST_F(NonBlockingConnectTest, success)
{
	m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
	m_server.bind(Internet("*", 16000, AF_INET));
	m_server.listen();

	m_tserver = std::thread([this] () {
		SocketTcp client = m_server.accept();

		std::this_thread::sleep_for(100ms);

		m_server.close();
		client.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		try {
			m_client.waitConnect(Internet("127.0.0.1", 16000, AF_INET), 3000);
		} catch (const SocketError &error) {
			FAIL() << error.function() << ": " << error.what();
		}

		ASSERT_EQ(SocketState::Connected, m_client.state());

		m_client.close();
	});
}

TEST_F(NonBlockingConnectTest, fail)
{
	/*
	 * /!\ If you find a way to test this locally please tell me /!\
	 */
	m_tclient = std::thread([this] () {
		try {
			m_client.waitConnect(Internet("google.fr", 9000, AF_INET), 100);

			FAIL() << "Expected exception, got success";
		} catch (const SocketError &error) {
			ASSERT_EQ(SocketError::Timeout, error.code());
		}

		m_client.close();
	});
}

/* --------------------------------------------------------
 * TCP accept
 * -------------------------------------------------------- */

class TcpAcceptTest : public testing::Test {
protected:
	SocketTcp m_server{AF_INET, 0};
	SocketTcp m_client{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	TcpAcceptTest()
	{
		m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
		m_server.bind(Internet("*", 16000, AF_INET));
		m_server.listen();
	}

	~TcpAcceptTest()
	{
		if (m_tserver.joinable())
			m_tserver.join();
		if (m_tclient.joinable())
			m_tclient.join();
	}
};

TEST_F(TcpAcceptTest, blockingWaitSuccess)
{
	m_tserver = std::thread([this] () {
		try {
			m_server.waitAccept(3000).close();
		} catch (const SocketError &error) {
			FAIL() << error.what();
		}

		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_client.close();
	});
}

TEST_F(TcpAcceptTest, nonBlockingWaitSuccess)
{
	m_tserver = std::thread([this] () {
		try {
			m_server.setBlockMode(false);
			m_server.waitAccept(3000).close();
		} catch (const SocketError &error) {
			FAIL() << error.what();
		}

		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_client.close();
	});
}

TEST_F(TcpAcceptTest, nonBlockingWaitFail)
{
	// No client, no accept
	try {
		m_server.setBlockMode(false);
		m_server.waitAccept(100).close();

		FAIL() << "Expected exception, got success";
	} catch (const SocketError &error) {
		ASSERT_EQ(SocketError::Timeout, error.code());
	}

	m_server.close();
}

/* --------------------------------------------------------
 * TCP recv
 * -------------------------------------------------------- */

class TcpRecvTest : public testing::Test {
protected:
	SocketTcp m_server{AF_INET, 0};
	SocketTcp m_client{AF_INET, 0};

	std::thread m_tserver;
	std::thread m_tclient;

public:
	TcpRecvTest()
	{
		m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
		m_server.bind(Internet("*", 16000, AF_INET));
		m_server.listen();
	}

	~TcpRecvTest()
	{
		if (m_tserver.joinable())
			m_tserver.join();
		if (m_tclient.joinable())
			m_tclient.join();
	}
};

TEST_F(TcpRecvTest, blockingSuccess)
{
	m_tserver = std::thread([this] () {
		SocketTcp client = m_server.accept();

		ASSERT_EQ("hello", client.recv(32));

		client.close();
		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_client.send("hello");
		m_client.close();
	});
}

TEST_F(TcpRecvTest, blockingWaitSuccess)
{
	m_tserver = std::thread([this] () {
		SocketTcp client = m_server.accept();

		ASSERT_EQ("hello", client.waitRecv(32, 3000));

		client.close();
		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_client.send("hello");
		m_client.close();
	});
}

TEST_F(TcpRecvTest, nonBlockingWaitSuccess)
{
	m_tserver = std::thread([this] () {
		SocketTcp client = m_server.accept();

		client.setBlockMode(false);

		ASSERT_EQ("hello", client.waitRecv(32, 3000));

		client.close();
		m_server.close();
	});

	std::this_thread::sleep_for(100ms);

	m_tclient = std::thread([this] () {
		m_client.connect(Internet("127.0.0.1", 16000, AF_INET));
		m_client.send("hello");
		m_client.close();
	});
}

/* --------------------------------------------------------
 * Socket SSL
 * -------------------------------------------------------- */

class SslTest : public testing::Test {
protected:
	SocketSsl client{AF_INET, 0};
};

TEST_F(SslTest, connect)
{
	try {
		client.connect(Internet("google.fr", 443, AF_INET));
		client.close();
	} catch (const SocketError &error) {
		FAIL() << error.what();
	}
}

TEST_F(SslTest, recv)
{
	try {
		client.connect(Internet("google.fr", 443, AF_INET));
		client.send("GET / HTTP/1.0\r\n\r\n");

		std::string msg = client.recv(512);
		std::string content = msg.substr(0, 18);

		ASSERT_EQ("HTTP/1.0 302 Found", content);

		client.close();
	} catch (const SocketError &error) {
		FAIL() << error.what();
	}
}

int main(int argc, char **argv)
{
	testing::InitGoogleTest(&argc, argv);

	return RUN_ALL_TESTS();
}