aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/tests/websocket
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vespalib/src/tests/websocket
Publish
Diffstat (limited to 'vespalib/src/tests/websocket')
-rw-r--r--vespalib/src/tests/websocket/.gitignore3
-rw-r--r--vespalib/src/tests/websocket/CMakeLists.txt14
-rw-r--r--vespalib/src/tests/websocket/FILES1
-rw-r--r--vespalib/src/tests/websocket/favicon.icobin0 -> 32038 bytes
-rw-r--r--vespalib/src/tests/websocket/index.html5
-rw-r--r--vespalib/src/tests/websocket/test.html70
-rw-r--r--vespalib/src/tests/websocket/websocket_server.cpp62
-rw-r--r--vespalib/src/tests/websocket/websocket_test.cpp145
8 files changed, 300 insertions, 0 deletions
diff --git a/vespalib/src/tests/websocket/.gitignore b/vespalib/src/tests/websocket/.gitignore
new file mode 100644
index 00000000000..379d76b3ece
--- /dev/null
+++ b/vespalib/src/tests/websocket/.gitignore
@@ -0,0 +1,3 @@
+/websocket_server
+vespalib_websocket_test_app
+vespalib_websocket_server_app
diff --git a/vespalib/src/tests/websocket/CMakeLists.txt b/vespalib/src/tests/websocket/CMakeLists.txt
new file mode 100644
index 00000000000..c74aad84692
--- /dev/null
+++ b/vespalib/src/tests/websocket/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_websocket_test_app
+ SOURCES
+ websocket_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_websocket_test_app COMMAND vespalib_websocket_test_app)
+vespa_add_executable(vespalib_websocket_server_app
+ SOURCES
+ websocket_server.cpp
+ DEPENDS
+ vespalib
+)
diff --git a/vespalib/src/tests/websocket/FILES b/vespalib/src/tests/websocket/FILES
new file mode 100644
index 00000000000..b3d0cb52dee
--- /dev/null
+++ b/vespalib/src/tests/websocket/FILES
@@ -0,0 +1 @@
+websocket_test.cpp
diff --git a/vespalib/src/tests/websocket/favicon.ico b/vespalib/src/tests/websocket/favicon.ico
new file mode 100644
index 00000000000..22f61482bd0
--- /dev/null
+++ b/vespalib/src/tests/websocket/favicon.ico
Binary files differ
diff --git a/vespalib/src/tests/websocket/index.html b/vespalib/src/tests/websocket/index.html
new file mode 100644
index 00000000000..3d705380d10
--- /dev/null
+++ b/vespalib/src/tests/websocket/index.html
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<html>
+<h1>Websocket server</h1>
+<a href="test.html">run test</a>
+</html>
diff --git a/vespalib/src/tests/websocket/test.html b/vespalib/src/tests/websocket/test.html
new file mode 100644
index 00000000000..6a9a00ce001
--- /dev/null
+++ b/vespalib/src/tests/websocket/test.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+
+<meta charset="utf-8" />
+
+<title>WebSocket Test</title>
+
+<script language="javascript" type="text/javascript">
+
+ var wsUri = "ws://[SELF]/echo";
+ var output;
+
+ function init()
+ {
+ output = document.getElementById("output");
+ testWebSocket();
+ }
+
+ function testWebSocket()
+ {
+ websocket = new WebSocket(wsUri);
+ websocket.onopen = function(evt) { onOpen(evt) };
+ websocket.onclose = function(evt) { onClose(evt) };
+ websocket.onmessage = function(evt) { onMessage(evt) };
+ websocket.onerror = function(evt) { onError(evt) };
+ }
+
+ function onOpen(evt)
+ {
+ writeToScreen("CONNECTED");
+ doSend("WebSocket rocks");
+ }
+
+ function onClose(evt)
+ {
+ writeToScreen("DISCONNECTED");
+ }
+
+ function onMessage(evt)
+ {
+ writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
+ websocket.close();
+ }
+
+ function onError(evt)
+ {
+ writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
+ }
+
+ function doSend(message)
+ {
+ writeToScreen("SENT: " + message);
+ websocket.send(message);
+ }
+
+ function writeToScreen(message)
+ {
+ var pre = document.createElement("p");
+ pre.style.wordWrap = "break-word";
+ pre.innerHTML = message;
+ output.appendChild(pre);
+ }
+
+ window.addEventListener("load", init, false);
+
+</script>
+
+<h2>WebSocket Test</h2>
+
+<div id="output"></div>
diff --git a/vespalib/src/tests/websocket/websocket_server.cpp b/vespalib/src/tests/websocket/websocket_server.cpp
new file mode 100644
index 00000000000..e7b6d23e566
--- /dev/null
+++ b/vespalib/src/tests/websocket/websocket_server.cpp
@@ -0,0 +1,62 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/websocket/websocket_server.h>
+#include <vespa/vespalib/util/host_name.h>
+#include <vespa/vespalib/util/signalhandler.h>
+#include <thread>
+#include <chrono>
+
+using namespace vespalib;
+
+vespalib::string read_file(const vespalib::string &file_name) {
+ vespalib::string ret;
+ struct stat info;
+ int fd = open(file_name.c_str(), O_RDONLY);
+ if (fd >= 0 && fstat(fd, &info) == 0) {
+ char *data = (char*)(mmap(0, info.st_size, PROT_READ, MAP_SHARED, fd, 0));
+ if (data != MAP_FAILED) {
+ ret = vespalib::string(data, info.st_size);
+ }
+ munmap(data, info.st_size);
+ }
+ close(fd);
+ return ret;
+}
+
+vespalib::string find_content_type(const vespalib::string &file_name) {
+ if (ends_with(file_name, ".html")) {
+ return "text/html";
+ }
+ if (ends_with(file_name, ".js")) {
+ return "text/javascript";
+ }
+ if (ends_with(file_name, ".ico")) {
+ return "image/x-icon";
+ }
+ return "text/plain";
+}
+
+int main(int argc, char **argv) {
+ ws::WebsocketServer::StaticRepo repo;
+ for (int i = 1; i < argc; ++i) {
+ vespalib::string file_name(argv[i]);
+ vespalib::string content = read_file(file_name);
+ vespalib::string content_type = find_content_type(file_name);
+ if (!content.empty()) {
+ fprintf(stderr, "loaded file: %s as content %s\n", file_name.c_str(), content_type.c_str());
+ repo.emplace("/" + file_name, ws::WebsocketServer::StaticPage{content_type, content});
+ }
+ }
+ ws::WebsocketServer server(0, std::move(repo));
+ int port = server.port();
+ SignalHandler::INT.hook();
+ fprintf(stderr, "running websocket server at http://%s:%d/index.html\n",
+ HostName::get().c_str(), port);
+ fprintf(stderr, "use ^C (SIGINT) to exit\n");
+ while (!SignalHandler::INT.check()) {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ fprintf(stderr, "exiting...\n");
+ kill(getpid(), SIGTERM);
+ return 0;
+}
diff --git a/vespalib/src/tests/websocket/websocket_test.cpp b/vespalib/src/tests/websocket/websocket_test.cpp
new file mode 100644
index 00000000000..c756cb3d22d
--- /dev/null
+++ b/vespalib/src/tests/websocket/websocket_test.cpp
@@ -0,0 +1,145 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/websocket/socket.h>
+#include <vespa/vespalib/websocket/server_socket.h>
+#include <vespa/vespalib/websocket/handler.h>
+#include <vespa/vespalib/websocket/acceptor.h>
+#include <vespa/vespalib/websocket/key.h>
+#include <vespa/vespalib/websocket/buffer.h>
+#include <vespa/vespalib/util/sync.h>
+#include <thread>
+#include <functional>
+#include <chrono>
+
+using namespace vespalib::ws;
+
+template <typename T>
+struct Receptor : vespalib::ws::Handler<T> {
+ std::unique_ptr<T> obj;
+ vespalib::Gate gate;
+ void handle(std::unique_ptr<T> t) override {
+ obj = std::move(t);
+ gate.countDown();
+ }
+};
+
+vespalib::string read_bytes(Socket &socket, size_t wanted_bytes) {
+ char tmp[64];
+ vespalib::string result;
+ while (result.size() < wanted_bytes) {
+ size_t read_size = std::min(sizeof(tmp), wanted_bytes - result.size());
+ size_t read_result = socket.read(tmp, read_size);
+ if (read_result <= 0) {
+ return result;
+ }
+ result.append(tmp, read_result);
+ }
+ return result;
+}
+
+void verify_socket_io(bool is_server, Socket &socket) {
+ vespalib::string server_message = "hello, this is the server speaking";
+ vespalib::string client_message = "please pick up, I need to talk to you";
+ if(is_server) {
+ socket.write(server_message.data(), server_message.size());
+ vespalib::string read = read_bytes(socket, client_message.size());
+ EXPECT_EQUAL(client_message, read);
+ } else {
+ socket.write(client_message.data(), client_message.size());
+ vespalib::string read = read_bytes(socket, server_message.size());
+ EXPECT_EQUAL(server_message, read);
+ }
+}
+
+void verify_socket_io_async(Socket &server, Socket &client) {
+ std::thread server_thread(verify_socket_io, true, std::ref(server));
+ std::thread client_thread(verify_socket_io, false, std::ref(client));
+ server_thread.join();
+ client_thread.join();
+}
+
+Socket::UP connect_sockets(bool is_server, ServerSocket &server_socket) {
+ if (is_server) {
+ return server_socket.accept();
+ } else {
+ return Socket::UP(new Socket("localhost", server_socket.port()));
+ }
+}
+
+void check_buffer_stats(const Buffer &buffer, size_t dead, size_t used, size_t free) {
+ EXPECT_EQUAL(dead, buffer.dead());
+ EXPECT_EQUAL(used, buffer.used());
+ EXPECT_EQUAL(free, buffer.free());
+}
+
+TEST("require that basic reserve/commit/obtain/evict buffer cycle works") {
+ Buffer buffer;
+ check_buffer_stats(buffer, 0, 0, 0);
+ char *a = buffer.reserve(1);
+ check_buffer_stats(buffer, 0, 0, 1);
+ *a = 'x';
+ buffer.commit(1);
+ check_buffer_stats(buffer, 0, 1, 0);
+ EXPECT_EQUAL('x', *buffer.obtain());
+ check_buffer_stats(buffer, 0, 1, 0);
+ buffer.evict(1);
+ check_buffer_stats(buffer, 1, 0, 0);
+}
+
+TEST("require that buffer moves contained data when more space is needed") {
+ Buffer buffer;
+ strncpy(buffer.reserve(3), "xyz", 3);
+ buffer.commit(3);
+ EXPECT_EQUAL('x', *buffer.obtain());
+ buffer.evict(1);
+ EXPECT_EQUAL('y', *buffer.obtain());
+ check_buffer_stats(buffer, 1, 2, 0);
+ buffer.reserve(1);
+ check_buffer_stats(buffer, 0, 2, 1);
+ EXPECT_EQUAL('y', *buffer.obtain());
+ buffer.evict(1);
+ EXPECT_EQUAL('z', *buffer.obtain());
+ check_buffer_stats(buffer, 1, 1, 1);
+ buffer.reserve(3);
+ check_buffer_stats(buffer, 0, 1, 3);
+ EXPECT_EQUAL('z', *buffer.obtain());
+}
+
+TEST_MT_F("require that basic socket io works", 2, ServerSocket(0)) {
+ bool is_server = (thread_id == 0);
+ Socket::UP socket = connect_sockets(is_server, f1);
+ TEST_DO(verify_socket_io(is_server, *socket));
+}
+
+TEST_MT_F("require that server accept can be interrupted", 2, ServerSocket(0)) {
+ bool is_server = (thread_id == 0);
+ if (is_server) {
+ fprintf(stderr, "--> calling accept\n");
+ Socket::UP socket = f1.accept();
+ fprintf(stderr, "<-- accept returned\n");
+ EXPECT_TRUE(socket.get() == nullptr);
+ EXPECT_TRUE(f1.is_closed());
+ } else {
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ fprintf(stderr, "--- closing server socket\n");
+ f1.close();
+ }
+}
+
+TEST("require that an acceptor can accept connections asynchronously") {
+ Receptor<Socket> server;
+ Acceptor acceptor(0, server);
+ Socket::UP client(new Socket("localhost", acceptor.port()));
+ server.gate.await(60000);
+ EXPECT_TRUE(server.obj.get() != nullptr);
+ EXPECT_TRUE(client.get() != nullptr);
+ TEST_DO(verify_socket_io_async(*server.obj, *client));
+}
+
+TEST("require that websocket accept tokens are generated correctly") {
+ vespalib::string key("dGhlIHNhbXBsZSBub25jZQ==");
+ vespalib::string accept_token("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
+ EXPECT_EQUAL(accept_token, Key::accept(key));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }