diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vespalib/src/tests/websocket |
Publish
Diffstat (limited to 'vespalib/src/tests/websocket')
-rw-r--r-- | vespalib/src/tests/websocket/.gitignore | 3 | ||||
-rw-r--r-- | vespalib/src/tests/websocket/CMakeLists.txt | 14 | ||||
-rw-r--r-- | vespalib/src/tests/websocket/FILES | 1 | ||||
-rw-r--r-- | vespalib/src/tests/websocket/favicon.ico | bin | 0 -> 32038 bytes | |||
-rw-r--r-- | vespalib/src/tests/websocket/index.html | 5 | ||||
-rw-r--r-- | vespalib/src/tests/websocket/test.html | 70 | ||||
-rw-r--r-- | vespalib/src/tests/websocket/websocket_server.cpp | 62 | ||||
-rw-r--r-- | vespalib/src/tests/websocket/websocket_test.cpp | 145 |
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 Binary files differnew file mode 100644 index 00000000000..22f61482bd0 --- /dev/null +++ b/vespalib/src/tests/websocket/favicon.ico 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(); } |