// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ppapi/tests/test_websocket.h" #include #include #include #include #include #include #include "ppapi/c/pp_bool.h" #include "ppapi/c/pp_completion_callback.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_instance.h" #include "ppapi/c/pp_resource.h" #include "ppapi/c/pp_var.h" #include "ppapi/c/ppb_core.h" #include "ppapi/c/ppb_var.h" #include "ppapi/c/ppb_var_array_buffer.h" #include "ppapi/c/ppb_websocket.h" #include "ppapi/c/private/ppb_testing_private.h" #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/var_array_buffer.h" #include "ppapi/cpp/websocket.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" #include "ppapi/utility/websocket/websocket_api.h" // net::SpawnedTestServer serves WebSocket service for testing. // Following URLs are handled by pywebsocket handlers in // net/data/websocket/*_wsh.py. const char kEchoServerURL[] = "echo-with-no-extension"; const char kCloseServerURL[] = "close"; const char kCloseWithCodeAndReasonServerURL[] = "close-code-and-reason"; const char kProtocolTestServerURL[] = "protocol-test?protocol="; const char* const kInvalidURLs[] = { "http://www.google.com/invalid_scheme", "ws://www.google.com/invalid#fragment", "ws://www.google.com:65535/invalid_port", NULL }; // Internal packet sizes. const uint64_t kMessageFrameOverhead = 6; namespace { struct WebSocketEvent { enum EventType { EVENT_OPEN, EVENT_MESSAGE, EVENT_ERROR, EVENT_CLOSE }; WebSocketEvent(EventType type, bool was_clean, uint16_t close_code, const pp::Var& var) : event_type(type), was_clean(was_clean), close_code(close_code), var(var) { } EventType event_type; bool was_clean; uint16_t close_code; pp::Var var; }; class ReleaseResourceDelegate : public TestCompletionCallback::Delegate { public: explicit ReleaseResourceDelegate(const PPB_Core* core_interface, PP_Resource resource) : core_interface_(core_interface), resource_(resource) { } // TestCompletionCallback::Delegate implementation. virtual void OnCallback(void* user_data, int32_t result) { if (resource_) core_interface_->ReleaseResource(resource_); } private: const PPB_Core* core_interface_; PP_Resource resource_; }; class TestWebSocketAPI : public pp::WebSocketAPI { public: explicit TestWebSocketAPI(pp::Instance* instance) : pp::WebSocketAPI(instance), connected_(false), received_(false), closed_(false), wait_for_connected_(false), wait_for_received_(false), wait_for_closed_(false), instance_(instance->pp_instance()) { } virtual void WebSocketDidOpen() { events_.push_back( WebSocketEvent(WebSocketEvent::EVENT_OPEN, true, 0U, pp::Var())); connected_ = true; if (wait_for_connected_) { GetTestingInterface()->QuitMessageLoop(instance_); wait_for_connected_ = false; } } virtual void WebSocketDidClose( bool was_clean, uint16_t code, const pp::Var& reason) { events_.push_back( WebSocketEvent(WebSocketEvent::EVENT_CLOSE, was_clean, code, reason)); connected_ = true; closed_ = true; if (wait_for_connected_ || wait_for_closed_) { GetTestingInterface()->QuitMessageLoop(instance_); wait_for_connected_ = false; wait_for_closed_ = false; } } virtual void HandleWebSocketMessage(const pp::Var &message) { events_.push_back( WebSocketEvent(WebSocketEvent::EVENT_MESSAGE, true, 0U, message)); received_ = true; if (wait_for_received_) { GetTestingInterface()->QuitMessageLoop(instance_); wait_for_received_ = false; received_ = false; } } virtual void HandleWebSocketError() { events_.push_back( WebSocketEvent(WebSocketEvent::EVENT_ERROR, true, 0U, pp::Var())); } void WaitForConnected() { if (!connected_) { wait_for_connected_ = true; GetTestingInterface()->RunMessageLoop(instance_); } } void WaitForReceived() { if (!received_) { wait_for_received_ = true; GetTestingInterface()->RunMessageLoop(instance_); } } void WaitForClosed() { if (!closed_) { wait_for_closed_ = true; GetTestingInterface()->RunMessageLoop(instance_); } } const std::vector& GetSeenEvents() const { return events_; } private: std::vector events_; bool connected_; bool received_; bool closed_; bool wait_for_connected_; bool wait_for_received_; bool wait_for_closed_; PP_Instance instance_; }; } // namespace REGISTER_TEST_CASE(WebSocket); bool TestWebSocket::Init() { websocket_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_WEBSOCKET_INTERFACE)); var_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); arraybuffer_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_VAR_ARRAY_BUFFER_INTERFACE)); core_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE)); if (!websocket_interface_ || !var_interface_ || !arraybuffer_interface_ || !core_interface_) return false; return CheckTestingInterface(); } void TestWebSocket::RunTests(const std::string& filter) { RUN_TEST_WITH_REFERENCE_CHECK(IsWebSocket, filter); RUN_TEST_WITH_REFERENCE_CHECK(UninitializedPropertiesAccess, filter); RUN_TEST_WITH_REFERENCE_CHECK(InvalidConnect, filter); RUN_TEST_WITH_REFERENCE_CHECK(Protocols, filter); RUN_TEST_WITH_REFERENCE_CHECK(GetURL, filter); RUN_TEST_WITH_REFERENCE_CHECK(ValidConnect, filter); RUN_TEST_WITH_REFERENCE_CHECK(InvalidClose, filter); RUN_TEST_WITH_REFERENCE_CHECK(ValidClose, filter); RUN_TEST_WITH_REFERENCE_CHECK(GetProtocol, filter); RUN_TEST_WITH_REFERENCE_CHECK(TextSendReceive, filter); RUN_TEST_BACKGROUND(TestWebSocket, TextSendReceiveTwice, filter); RUN_TEST_WITH_REFERENCE_CHECK(BinarySendReceive, filter); RUN_TEST_WITH_REFERENCE_CHECK(StressedSendReceive, filter); RUN_TEST_WITH_REFERENCE_CHECK(BufferedAmount, filter); // PP_Resource for WebSocket may be released later because of an internal // reference for asynchronous IPC handling. So, suppress reference check on // the following AbortCallsWithCallback test. RUN_TEST(AbortCallsWithCallback, filter); RUN_TEST_WITH_REFERENCE_CHECK(AbortSendMessageCall, filter); RUN_TEST_WITH_REFERENCE_CHECK(AbortCloseCall, filter); RUN_TEST_WITH_REFERENCE_CHECK(AbortReceiveMessageCall, filter); RUN_TEST_WITH_REFERENCE_CHECK(ClosedFromServerWhileSending, filter); RUN_TEST_WITH_REFERENCE_CHECK(CcInterfaces, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityInvalidConnect, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityProtocols, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityGetURL, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityValidConnect, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityInvalidClose, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityValidClose, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityGetProtocol, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityTextSendReceive, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityBinarySendReceive, filter); RUN_TEST_WITH_REFERENCE_CHECK(UtilityBufferedAmount, filter); } std::string TestWebSocket::GetFullURL(const char* url) { std::string rv = "ws://"; // Some WebSocket tests don't start the server so there'll be no host and // port. if (instance_->websocket_host().empty()) rv += "127.0.0.1"; else rv += instance_->websocket_host(); if (instance_->websocket_port() != -1) { char buffer[10]; sprintf(buffer, ":%d", instance_->websocket_port()); rv += std::string(buffer); } rv += "/"; rv += url; return rv; } PP_Var TestWebSocket::CreateVarString(const std::string& string) { return var_interface_->VarFromUtf8(string.c_str(), static_cast(string.size())); } PP_Var TestWebSocket::CreateVarBinary(const std::vector& binary) { PP_Var var = arraybuffer_interface_->Create(static_cast(binary.size())); uint8_t* var_data = static_cast(arraybuffer_interface_->Map(var)); std::copy(binary.begin(), binary.end(), var_data); return var; } void TestWebSocket::ReleaseVar(const PP_Var& var) { var_interface_->Release(var); } bool TestWebSocket::AreEqualWithString(const PP_Var& var, const std::string& string) { if (var.type != PP_VARTYPE_STRING) return false; uint32_t utf8_length; const char* utf8 = var_interface_->VarToUtf8(var, &utf8_length); if (utf8_length != string.size()) return false; if (string.compare(utf8)) return false; return true; } bool TestWebSocket::AreEqualWithBinary(const PP_Var& var, const std::vector& binary) { uint32_t buffer_size = 0; PP_Bool success = arraybuffer_interface_->ByteLength(var, &buffer_size); if (!success || buffer_size != binary.size()) return false; if (!std::equal(binary.begin(), binary.end(), static_cast(arraybuffer_interface_->Map(var)))) return false; return true; } PP_Resource TestWebSocket::Connect(const std::string& url, int32_t* result, const std::string& protocol) { PP_Var protocols[] = { PP_MakeUndefined() }; PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); if (!ws) return 0; PP_Var url_var = CreateVarString(url); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); uint32_t protocol_count = 0U; if (protocol.size()) { protocols[0] = CreateVarString(protocol); protocol_count = 1U; } callback.WaitForResult(websocket_interface_->Connect( ws, url_var, protocols, protocol_count, callback.GetCallback().pp_completion_callback())); ReleaseVar(url_var); if (protocol.size()) ReleaseVar(protocols[0]); *result = callback.result(); return ws; } void TestWebSocket::Send(int32_t /* result */, PP_Resource ws, const std::string& message) { PP_Var message_var = CreateVarString(message); websocket_interface_->SendMessage(ws, message_var); ReleaseVar(message_var); } std::string TestWebSocket::TestIsWebSocket() { // Test that a NULL resource isn't a websocket. pp::Resource null_resource; PP_Bool result = websocket_interface_->IsWebSocket(null_resource.pp_resource()); ASSERT_FALSE(result); PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); result = websocket_interface_->IsWebSocket(ws); ASSERT_TRUE(result); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestUninitializedPropertiesAccess() { PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); uint64_t bufferedAmount = websocket_interface_->GetBufferedAmount(ws); ASSERT_EQ(0U, bufferedAmount); uint16_t close_code = websocket_interface_->GetCloseCode(ws); ASSERT_EQ(0U, close_code); PP_Var close_reason = websocket_interface_->GetCloseReason(ws); ASSERT_TRUE(AreEqualWithString(close_reason, std::string())); ReleaseVar(close_reason); PP_Bool close_was_clean = websocket_interface_->GetCloseWasClean(ws); ASSERT_EQ(PP_FALSE, close_was_clean); PP_Var extensions = websocket_interface_->GetExtensions(ws); ASSERT_TRUE(AreEqualWithString(extensions, std::string())); ReleaseVar(extensions); PP_Var protocol = websocket_interface_->GetProtocol(ws); ASSERT_TRUE(AreEqualWithString(protocol, std::string())); ReleaseVar(protocol); PP_WebSocketReadyState ready_state = websocket_interface_->GetReadyState(ws); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_INVALID, ready_state); PP_Var url = websocket_interface_->GetURL(ws); ASSERT_TRUE(AreEqualWithString(url, std::string())); ReleaseVar(url); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestInvalidConnect() { PP_Var protocols[] = { PP_MakeUndefined() }; PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); callback.WaitForResult(websocket_interface_->Connect( ws, PP_MakeUndefined(), protocols, 1U, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result()); callback.WaitForResult(websocket_interface_->Connect( ws, PP_MakeUndefined(), protocols, 1U, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_INPROGRESS, callback.result()); core_interface_->ReleaseResource(ws); for (int i = 0; kInvalidURLs[i]; ++i) { int32_t result; ws = Connect(kInvalidURLs[i], &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); core_interface_->ReleaseResource(ws); } PASS(); } std::string TestWebSocket::TestProtocols() { PP_Var url = CreateVarString(GetFullURL(kEchoServerURL).c_str()); PP_Var bad_protocols[] = { CreateVarString("x-test"), CreateVarString("x-test") }; PP_Var good_protocols[] = { CreateVarString("x-test"), CreateVarString("x-yatest") }; PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); callback.WaitForResult(websocket_interface_->Connect( ws, url, bad_protocols, 2U, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result()); core_interface_->ReleaseResource(ws); ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); int32_t result = websocket_interface_->Connect( ws, url, good_protocols, 2U, PP_BlockUntilComplete()); ASSERT_EQ(PP_ERROR_BLOCKS_MAIN_THREAD, result); core_interface_->ReleaseResource(ws); ReleaseVar(url); for (int i = 0; i < 2; ++i) { ReleaseVar(bad_protocols[i]); ReleaseVar(good_protocols[i]); } core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestGetURL() { for (int i = 0; kInvalidURLs[i]; ++i) { int32_t result; PP_Resource ws = Connect(kInvalidURLs[i], &result, std::string()); ASSERT_TRUE(ws); PP_Var url = websocket_interface_->GetURL(ws); ASSERT_TRUE(AreEqualWithString(url, kInvalidURLs[i])); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); ReleaseVar(url); core_interface_->ReleaseResource(ws); } PASS(); } std::string TestWebSocket::TestValidConnect() { int32_t result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var extensions = websocket_interface_->GetExtensions(ws); ASSERT_TRUE(AreEqualWithString(extensions, std::string())); core_interface_->ReleaseResource(ws); ReleaseVar(extensions); PASS(); } std::string TestWebSocket::TestInvalidClose() { PP_Var reason = CreateVarString("close for test"); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); TestCompletionCallback async_callback(instance_->pp_instance(), PP_REQUIRED); // Close before connect. PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_FAILED, callback.result()); core_interface_->ReleaseResource(ws); // Close with bad arguments. int32_t result; ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); callback.WaitForResult(websocket_interface_->Close( ws, 1U, reason, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_NOACCESS, callback.result()); core_interface_->ReleaseResource(ws); // Close with PP_VARTYPE_NULL. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeNull(), callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result()); core_interface_->ReleaseResource(ws); // Close with PP_VARTYPE_NULL and ongoing receive message. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var receive_message_var; result = websocket_interface_->ReceiveMessage( ws, &receive_message_var, async_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeNull(), callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result()); const char* send_message = "hi"; PP_Var send_message_var = CreateVarString(send_message); result = websocket_interface_->SendMessage(ws, send_message_var); ReleaseVar(send_message_var); ASSERT_EQ(PP_OK, result); async_callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_OK, async_callback.result()); ASSERT_TRUE(AreEqualWithString(receive_message_var, send_message)); ReleaseVar(receive_message_var); core_interface_->ReleaseResource(ws); // Close twice. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, async_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); // Call another Close() before previous one is in progress. result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_ERROR_INPROGRESS, result); async_callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_OK, async_callback.result()); // Call another Close() after previous one is completed. // This Close() must do nothing and reports no error. callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_OK, callback.result()); core_interface_->ReleaseResource(ws); ReleaseVar(reason); PASS(); } // TODO(tyoshino): Consider splitting this test into smaller ones. // http://crbug.com/397035 std::string TestWebSocket::TestValidClose() { PP_Var reason = CreateVarString("close for test"); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); TestCompletionCallback another_callback( instance_->pp_instance(), callback_type()); // Close. int32_t result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, callback.GetCallback().pp_completion_callback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); core_interface_->ReleaseResource(ws); // Close without code and reason. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED, reason, callback.GetCallback().pp_completion_callback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); core_interface_->ReleaseResource(ws); // Close with PP_VARTYPE_UNDEFINED. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); callback.WaitForResult(websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(), callback.GetCallback().pp_completion_callback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); core_interface_->ReleaseResource(ws); // Close in CONNECTING state. // The ongoing Connect() fails with PP_ERROR_ABORTED, then the Close() // completes successfully. ws = websocket_interface_->Create(instance_->pp_instance()); PP_Var url = CreateVarString(GetFullURL(kEchoServerURL).c_str()); PP_Var protocols[] = { PP_MakeUndefined() }; result = websocket_interface_->Connect( ws, url, protocols, 0U, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, another_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); another_callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_OK, another_callback.result()); core_interface_->ReleaseResource(ws); ReleaseVar(url); // Close while already closing. // The first Close will succeed, and the second one will synchronously fail // with PP_ERROR_INPROGRESS. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, another_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_ERROR_INPROGRESS, result); callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_OK, callback.result()); core_interface_->ReleaseResource(ws); // Close with ongoing ReceiveMessage. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var receive_message_var; result = websocket_interface_->ReceiveMessage( ws, &receive_message_var, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, another_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); another_callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_OK, another_callback.result()); core_interface_->ReleaseResource(ws); // Close with PP_VARTYPE_UNDEFINED for reason and ongoing ReceiveMessage. ws = Connect(GetFullURL(kEchoServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->ReceiveMessage( ws, &receive_message_var, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(), another_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); another_callback.WaitForResult(PP_OK_COMPLETIONPENDING); ASSERT_EQ(PP_OK, another_callback.result()); core_interface_->ReleaseResource(ws); // Server initiated closing handshake. ws = Connect( GetFullURL(kCloseWithCodeAndReasonServerURL), &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); // Text messsage "1000 bye" requests the server to initiate closing handshake // with code being 1000 and reason being "bye". PP_Var close_request_var = CreateVarString("1000 bye"); result = websocket_interface_->SendMessage(ws, close_request_var); ReleaseVar(close_request_var); callback.WaitForResult(websocket_interface_->ReceiveMessage( ws, &receive_message_var, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_ERROR_FAILED, callback.result()); core_interface_->ReleaseResource(ws); ReleaseVar(reason); PASS(); } std::string TestWebSocket::TestGetProtocol() { const char* expected_protocols[] = { "x-chat", "hoehoe", NULL }; for (int i = 0; expected_protocols[i]; ++i) { std::string url(GetFullURL(kProtocolTestServerURL)); url += expected_protocols[i]; int32_t result; PP_Resource ws = Connect(url.c_str(), &result, expected_protocols[i]); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var protocol = websocket_interface_->GetProtocol(ws); ASSERT_TRUE(AreEqualWithString(protocol, expected_protocols[i])); ReleaseVar(protocol); core_interface_->ReleaseResource(ws); } PASS(); } std::string TestWebSocket::TestTextSendReceive() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &connect_result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Send 'hello pepper' text message. const char* message = "hello pepper"; PP_Var message_var = CreateVarString(message); int32_t result = websocket_interface_->SendMessage(ws, message_var); ReleaseVar(message_var); ASSERT_EQ(PP_OK, result); // Receive echoed 'hello pepper'. TestCompletionCallback callback(instance_->pp_instance(), callback_type()); PP_Var received_message; callback.WaitForResult(websocket_interface_->ReceiveMessage( ws, &received_message, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_OK, callback.result()); ASSERT_TRUE(AreEqualWithString(received_message, message)); ReleaseVar(received_message); core_interface_->ReleaseResource(ws); PASS(); } // Run as a BACKGROUND test. std::string TestWebSocket::TestTextSendReceiveTwice() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &connect_result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); pp::MessageLoop message_loop = pp::MessageLoop::GetCurrent(); pp::CompletionCallbackFactory factory(this); message_loop.PostWork(factory.NewCallback(&TestWebSocket::Send, ws, std::string("hello"))); // When the server receives 'Goodbye', it closes the session. message_loop.PostWork(factory.NewCallback(&TestWebSocket::Send, ws, std::string("Goodbye"))); message_loop.PostQuit(false); message_loop.Run(); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); PP_Var received_message; int32_t result = websocket_interface_->ReceiveMessage( ws, &received_message, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK, result); // Since we don't run the message loop, the callback will stay // "pending and scheduled to run" state. // Waiting for the connection close which will be done by the server. while (true) { PP_WebSocketReadyState ready_state = websocket_interface_->GetReadyState(ws); if (ready_state != PP_WEBSOCKETREADYSTATE_CONNECTING && ready_state != PP_WEBSOCKETREADYSTATE_OPEN) { break; } PlatformSleep(100); // 100ms } // Cleanup the message loop message_loop.PostQuit(false); message_loop.Run(); ASSERT_EQ(PP_OK, callback.result()); ASSERT_TRUE(AreEqualWithString(received_message, "hello")); ReleaseVar(received_message); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestBinarySendReceive() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &connect_result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Send binary message. std::vector binary(256); for (uint32_t i = 0; i < binary.size(); ++i) binary[i] = i; PP_Var message_var = CreateVarBinary(binary); int32_t result = websocket_interface_->SendMessage(ws, message_var); ReleaseVar(message_var); ASSERT_EQ(PP_OK, result); // Receive echoed binary. TestCompletionCallback callback(instance_->pp_instance(), callback_type()); PP_Var received_message; callback.WaitForResult(websocket_interface_->ReceiveMessage( ws, &received_message, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_OK, callback.result()); ASSERT_TRUE(AreEqualWithBinary(received_message, binary)); ReleaseVar(received_message); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestStressedSendReceive() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &connect_result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Prepare PP_Var objects to send. const char* text = "hello pepper"; PP_Var text_var = CreateVarString(text); std::vector binary(256); for (uint32_t i = 0; i < binary.size(); ++i) binary[i] = i; PP_Var binary_var = CreateVarBinary(binary); // Prepare very large binary data over 64KiB. Object serializer in // ppapi_proxy has a limitation of 64KiB as maximum return PP_Var data size // to SRPC. In case received data over 64KiB exists, a specific code handles // this large data via asynchronous callback from main thread. This data // intends to test the code. std::vector large_binary(65 * 1024); for (uint32_t i = 0; i < large_binary.size(); ++i) large_binary[i] = i & 0xff; PP_Var large_binary_var = CreateVarBinary(large_binary); // Send many messages. int32_t result; for (int i = 0; i < 256; ++i) { result = websocket_interface_->SendMessage(ws, text_var); ASSERT_EQ(PP_OK, result); result = websocket_interface_->SendMessage(ws, binary_var); ASSERT_EQ(PP_OK, result); } result = websocket_interface_->SendMessage(ws, large_binary_var); ASSERT_EQ(PP_OK, result); ReleaseVar(text_var); ReleaseVar(binary_var); ReleaseVar(large_binary_var); // Receive echoed data. TestCompletionCallback callback(instance_->pp_instance(), callback_type()); for (int i = 0; i <= 512; ++i) { PP_Var received_message; callback.WaitForResult(websocket_interface_->ReceiveMessage( ws, &received_message, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_OK, callback.result()); if (i == 512) { ASSERT_TRUE(AreEqualWithBinary(received_message, large_binary)); } else if (i & 1) { ASSERT_TRUE(AreEqualWithBinary(received_message, binary)); } else { ASSERT_TRUE(AreEqualWithString(received_message, text)); } ReleaseVar(received_message); } core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestBufferedAmount() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(GetFullURL(kEchoServerURL), &connect_result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Prepare a large message that is not aligned with the internal buffer // sizes. std::string message(8193, 'x'); PP_Var message_var = CreateVarString(message); uint64_t buffered_amount = 0; int32_t result; for (int i = 0; i < 100; i++) { result = websocket_interface_->SendMessage(ws, message_var); ASSERT_EQ(PP_OK, result); buffered_amount = websocket_interface_->GetBufferedAmount(ws); // Buffered amount size 262144 is too big for the internal buffer size. if (buffered_amount > 262144) break; } // Close connection. std::string reason_str = "close while busy"; PP_Var reason = CreateVarString(reason_str.c_str()); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason, callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING, websocket_interface_->GetReadyState(ws)); callback.WaitForResult(result); ASSERT_EQ(PP_OK, callback.result()); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, websocket_interface_->GetReadyState(ws)); uint64_t base_buffered_amount = websocket_interface_->GetBufferedAmount(ws); // After connection closure, all sending requests fail and just increase // the bufferedAmount property. PP_Var empty_string = CreateVarString(std::string()); result = websocket_interface_->SendMessage(ws, empty_string); ASSERT_EQ(PP_ERROR_FAILED, result); buffered_amount = websocket_interface_->GetBufferedAmount(ws); ASSERT_EQ(base_buffered_amount + kMessageFrameOverhead, buffered_amount); base_buffered_amount = buffered_amount; result = websocket_interface_->SendMessage(ws, reason); ASSERT_EQ(PP_ERROR_FAILED, result); buffered_amount = websocket_interface_->GetBufferedAmount(ws); uint64_t reason_frame_size = kMessageFrameOverhead + reason_str.length(); ASSERT_EQ(base_buffered_amount + reason_frame_size, buffered_amount); ReleaseVar(message_var); ReleaseVar(reason); ReleaseVar(empty_string); core_interface_->ReleaseResource(ws); PASS(); } // Test abort behaviors where a WebSocket PP_Resource is released while each // function is in-flight on the WebSocket PP_Resource. std::string TestWebSocket::TestAbortCallsWithCallback() { // Following tests make sure the behavior for functions which require a // callback. The callback must get a PP_ERROR_ABORTED. // Test the behavior for Connect(). PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); std::string url = GetFullURL(kEchoServerURL); PP_Var url_var = CreateVarString(url); TestCompletionCallback connect_callback( instance_->pp_instance(), callback_type()); int32_t result = websocket_interface_->Connect( ws, url_var, NULL, 0, connect_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); core_interface_->ReleaseResource(ws); connect_callback.WaitForResult(result); ASSERT_EQ(PP_ERROR_ABORTED, connect_callback.result()); // Test the behavior for Close(). ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var reason_var = CreateVarString("abort"); TestCompletionCallback close_callback( instance_->pp_instance(), callback_type()); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason_var, close_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); core_interface_->ReleaseResource(ws); close_callback.WaitForResult(result); ASSERT_EQ(PP_ERROR_ABORTED, close_callback.result()); ReleaseVar(reason_var); // Test the behavior for ReceiveMessage(). // Make sure the simplest case to wait for data which never arrives, here. ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var receive_var; TestCompletionCallback receive_callback( instance_->pp_instance(), callback_type()); result = websocket_interface_->ReceiveMessage( ws, &receive_var, receive_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); core_interface_->ReleaseResource(ws); receive_callback.WaitForResult(result); ASSERT_EQ(PP_ERROR_ABORTED, receive_callback.result()); // Release the resource in the aborting receive completion callback which is // introduced by calling Close(). ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->ReceiveMessage( ws, &receive_var, receive_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); ReleaseResourceDelegate receive_delegate(core_interface_, ws); receive_callback.SetDelegate(&receive_delegate); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(), close_callback.GetCallback().pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); receive_callback.WaitForResult(result); CHECK_CALLBACK_BEHAVIOR(receive_callback); ASSERT_EQ(PP_ERROR_ABORTED, receive_callback.result()); close_callback.WaitForResult(result); CHECK_CALLBACK_BEHAVIOR(close_callback); ASSERT_EQ(PP_ERROR_ABORTED, close_callback.result()); ReleaseVar(url_var); PASS(); } std::string TestWebSocket::TestAbortSendMessageCall() { // Test the behavior for SendMessage(). // This function doesn't require a callback, but operation will be done // asynchronously in WebKit and browser process. std::vector large_binary(65 * 1024); PP_Var large_var = CreateVarBinary(large_binary); int32_t result; std::string url = GetFullURL(kEchoServerURL); PP_Resource ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->SendMessage(ws, large_var); ASSERT_EQ(PP_OK, result); core_interface_->ReleaseResource(ws); ReleaseVar(large_var); PASS(); } std::string TestWebSocket::TestAbortCloseCall() { // Release the resource in the close completion callback. int32_t result; std::string url = GetFullURL(kEchoServerURL); PP_Resource ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); TestCompletionCallback close_callback( instance_->pp_instance(), callback_type()); result = websocket_interface_->Close( ws, PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, PP_MakeUndefined(), close_callback.GetCallback().pp_completion_callback()); ReleaseResourceDelegate close_delegate(core_interface_, ws); close_callback.SetDelegate(&close_delegate); close_callback.WaitForResult(result); CHECK_CALLBACK_BEHAVIOR(close_callback); ASSERT_EQ(PP_OK, close_callback.result()); PASS(); } std::string TestWebSocket::TestAbortReceiveMessageCall() { // Test the behavior where receive process might be in-flight. std::vector large_binary(65 * 1024); PP_Var large_var = CreateVarBinary(large_binary); const char* text = "yukarin"; PP_Var text_var = CreateVarString(text); std::string url = GetFullURL(kEchoServerURL); int32_t result; PP_Resource ws; // Each trial sends |trial_count| + 1 messages and receives just |trial| // number of message(s) before releasing the WebSocket. The WebSocket is // released while the next message is going to be received. const int trial_count = 8; for (int trial = 1; trial <= trial_count; trial++) { ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); for (int i = 0; i <= trial_count; ++i) { result = websocket_interface_->SendMessage(ws, text_var); ASSERT_EQ(PP_OK, result); } TestCompletionCallback callback(instance_->pp_instance(), callback_type()); PP_Var var; for (int i = 0; i < trial; ++i) { callback.WaitForResult(websocket_interface_->ReceiveMessage( ws, &var, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_OK, callback.result()); ASSERT_TRUE(AreEqualWithString(var, text)); ReleaseVar(var); } result = websocket_interface_->ReceiveMessage( ws, &var, callback.GetCallback().pp_completion_callback()); core_interface_->ReleaseResource(ws); if (result != PP_OK) { callback.WaitForResult(result); ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); } } // Same test, but the last receiving message is large message over 64KiB. for (int trial = 1; trial <= trial_count; trial++) { ws = Connect(url, &result, std::string()); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); for (int i = 0; i <= trial_count; ++i) { if (i == trial) result = websocket_interface_->SendMessage(ws, large_var); else result = websocket_interface_->SendMessage(ws, text_var); ASSERT_EQ(PP_OK, result); } TestCompletionCallback callback(instance_->pp_instance(), callback_type()); PP_Var var; for (int i = 0; i < trial; ++i) { callback.WaitForResult(websocket_interface_->ReceiveMessage( ws, &var, callback.GetCallback().pp_completion_callback())); ASSERT_EQ(PP_OK, callback.result()); ASSERT_TRUE(AreEqualWithString(var, text)); ReleaseVar(var); } result = websocket_interface_->ReceiveMessage( ws, &var, callback.GetCallback().pp_completion_callback()); core_interface_->ReleaseResource(ws); if (result != PP_OK) { callback.WaitForResult(result); ASSERT_EQ(PP_ERROR_ABORTED, callback.result()); } } ReleaseVar(large_var); ReleaseVar(text_var); PASS(); } std::string TestWebSocket::TestClosedFromServerWhileSending() { // Connect to test echo server. const pp::Var protocols[] = { pp::Var() }; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(GetFullURL(kEchoServerURL)), protocols, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); result = websocket.Send(pp::Var("hello")); ASSERT_EQ(PP_OK, result); result = websocket.Send(pp::Var("Goodbye")); // We send many messages so that PepperWebSocketHost::SendText is called // after PepperWebSocketHost::didClose is called. // Note: We must not wait for CLOSED event here because // WebSocketResource::SendMessage doesn't call PepperWebSocketHost::SendText // when its internal state is CLOSING or CLOSED. We want to test if the // pepper WebSocket works well when WebSocketResource is OPEN and // PepperWebSocketHost is CLOSED. for (size_t i = 0; i < 10000; ++i) { result = websocket.Send(pp::Var("")); ASSERT_EQ(PP_OK, result); } PASS(); } std::string TestWebSocket::TestCcInterfaces() { // C++ bindings is simple straightforward, then just verifies interfaces work // as a interface bridge fine. pp::WebSocket ws(instance_); // Check uninitialized properties access. ASSERT_EQ(0, ws.GetBufferedAmount()); ASSERT_EQ(0, ws.GetCloseCode()); ASSERT_TRUE(AreEqualWithString(ws.GetCloseReason().pp_var(), std::string())); ASSERT_FALSE(ws.GetCloseWasClean()); ASSERT_TRUE(AreEqualWithString(ws.GetExtensions().pp_var(), std::string())); ASSERT_TRUE(AreEqualWithString(ws.GetProtocol().pp_var(), std::string())); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_INVALID, ws.GetReadyState()); ASSERT_TRUE(AreEqualWithString(ws.GetURL().pp_var(), std::string())); // Check communication interfaces (connect, send, receive, and close). TestCompletionCallback connect_callback( instance_->pp_instance(), callback_type()); connect_callback.WaitForResult(ws.Connect( pp::Var(GetFullURL(kCloseServerURL)), NULL, 0U, connect_callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(connect_callback); ASSERT_EQ(PP_OK, connect_callback.result()); std::string text_message("hello C++"); int32_t result = ws.SendMessage(pp::Var(text_message)); ASSERT_EQ(PP_OK, result); std::vector binary(256); for (uint32_t i = 0; i < binary.size(); ++i) binary[i] = i; result = ws.SendMessage( pp::Var(pp::PASS_REF, CreateVarBinary(binary))); ASSERT_EQ(PP_OK, result); pp::Var text_receive_var; TestCompletionCallback text_receive_callback( instance_->pp_instance(), callback_type()); text_receive_callback.WaitForResult( ws.ReceiveMessage(&text_receive_var, text_receive_callback.GetCallback())); ASSERT_EQ(PP_OK, text_receive_callback.result()); ASSERT_TRUE( AreEqualWithString(text_receive_var.pp_var(), text_message.c_str())); pp::Var binary_receive_var; TestCompletionCallback binary_receive_callback( instance_->pp_instance(), callback_type()); binary_receive_callback.WaitForResult( ws.ReceiveMessage(&binary_receive_var, binary_receive_callback.GetCallback())); ASSERT_EQ(PP_OK, binary_receive_callback.result()); ASSERT_TRUE(AreEqualWithBinary(binary_receive_var.pp_var(), binary)); TestCompletionCallback close_callback( instance_->pp_instance(), callback_type()); std::string reason("bye"); close_callback.WaitForResult(ws.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason), close_callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(close_callback); ASSERT_EQ(PP_OK, close_callback.result()); // Check initialized properties access. ASSERT_EQ(0, ws.GetBufferedAmount()); ASSERT_EQ(PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, ws.GetCloseCode()); ASSERT_TRUE( AreEqualWithString(ws.GetCloseReason().pp_var(), reason.c_str())); ASSERT_EQ(true, ws.GetCloseWasClean()); ASSERT_TRUE(AreEqualWithString(ws.GetProtocol().pp_var(), std::string())); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, ws.GetReadyState()); ASSERT_TRUE(AreEqualWithString( ws.GetURL().pp_var(), GetFullURL(kCloseServerURL).c_str())); PASS(); } std::string TestWebSocket::TestUtilityInvalidConnect() { const pp::Var protocols[] = { pp::Var() }; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(), protocols, 1U); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); ASSERT_EQ(0U, websocket.GetSeenEvents().size()); result = websocket.Connect(pp::Var(), protocols, 1U); ASSERT_EQ(PP_ERROR_INPROGRESS, result); ASSERT_EQ(0U, websocket.GetSeenEvents().size()); for (int i = 0; kInvalidURLs[i]; ++i) { TestWebSocketAPI ws(instance_); result = ws.Connect(pp::Var(std::string(kInvalidURLs[i])), protocols, 0U); if (result == PP_OK_COMPLETIONPENDING) { ws.WaitForClosed(); const std::vector& events = ws.GetSeenEvents(); ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type); ASSERT_EQ(2U, ws.GetSeenEvents().size()); } else { ASSERT_EQ(PP_ERROR_BADARGUMENT, result); ASSERT_EQ(0U, ws.GetSeenEvents().size()); } } PASS(); } std::string TestWebSocket::TestUtilityProtocols() { const pp::Var bad_protocols[] = { pp::Var(std::string("x-test")), pp::Var(std::string("x-test")) }; const pp::Var good_protocols[] = { pp::Var(std::string("x-test")), pp::Var(std::string("x-yatest")) }; { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect( pp::Var(GetFullURL(kEchoServerURL)), bad_protocols, 2U); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); ASSERT_EQ(0U, websocket.GetSeenEvents().size()); } { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect( pp::Var(GetFullURL(kEchoServerURL)), good_protocols, 2U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); const std::vector& events = websocket.GetSeenEvents(); // Protocol arguments are valid, but this test run without a WebSocket // server. As a result, OnError() and OnClose() are invoked because of // a connection establishment failure. ASSERT_EQ(2U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type); ASSERT_FALSE(events[1].was_clean); } PASS(); } std::string TestWebSocket::TestUtilityGetURL() { const pp::Var protocols[] = { pp::Var() }; for (int i = 0; kInvalidURLs[i]; ++i) { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect( pp::Var(std::string(kInvalidURLs[i])), protocols, 0U); if (result == PP_OK_COMPLETIONPENDING) { websocket.WaitForClosed(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type); ASSERT_EQ(2U, events.size()); } else { ASSERT_EQ(PP_ERROR_BADARGUMENT, result); ASSERT_EQ(0U, websocket.GetSeenEvents().size()); } pp::Var url = websocket.GetURL(); ASSERT_TRUE(AreEqualWithString(url.pp_var(), kInvalidURLs[i])); } PASS(); } std::string TestWebSocket::TestUtilityValidConnect() { const pp::Var protocols[] = { pp::Var() }; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect( pp::Var(GetFullURL(kEchoServerURL)), protocols, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(1U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); ASSERT_TRUE( AreEqualWithString(websocket.GetExtensions().pp_var(), std::string())); PASS(); } std::string TestWebSocket::TestUtilityInvalidClose() { const pp::Var reason = pp::Var(std::string("close for test")); // Close before connect. { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, reason); ASSERT_EQ(PP_ERROR_FAILED, result); ASSERT_EQ(0U, websocket.GetSeenEvents().size()); } // Close with bad arguments. { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(GetFullURL(kEchoServerURL)), NULL, 0); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); result = websocket.Close(1U, reason); ASSERT_EQ(PP_ERROR_NOACCESS, result); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(1U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); } PASS(); } std::string TestWebSocket::TestUtilityValidClose() { std::string reason("close for test"); pp::Var url = pp::Var(GetFullURL(kCloseServerURL)); // Close. { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(url, NULL, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); result = websocket.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason)); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForClosed(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(2U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[1].event_type); ASSERT_TRUE(events[1].was_clean); ASSERT_EQ(PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, events[1].close_code); ASSERT_TRUE(AreEqualWithString(events[1].var.pp_var(), reason.c_str())); } // Close in connecting. // The ongoing connect failed with PP_ERROR_ABORTED, then the close is done // successfully. { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(url, NULL, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason)); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForClosed(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_TRUE(events.size() == 2 || events.size() == 3); int index = 0; if (events.size() == 3) ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[index++].event_type); ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[index++].event_type); ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[index].event_type); ASSERT_FALSE(events[index].was_clean); } // Close in closing. // The first close will be done successfully, then the second one failed with // with PP_ERROR_INPROGRESS immediately. { TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(url, NULL, 0U); result = websocket.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason)); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason)); ASSERT_EQ(PP_ERROR_INPROGRESS, result); websocket.WaitForClosed(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_TRUE(events.size() == 2 || events.size() == 3); int index = 0; if (events.size() == 3) ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[index++].event_type); ASSERT_EQ(WebSocketEvent::EVENT_ERROR, events[index++].event_type); ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[index].event_type); ASSERT_FALSE(events[index].was_clean); } PASS(); } std::string TestWebSocket::TestUtilityGetProtocol() { const std::string protocol("x-chat"); const pp::Var protocols[] = { pp::Var(protocol) }; std::string url(GetFullURL(kProtocolTestServerURL)); url += protocol; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(url), protocols, 1U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForReceived(); ASSERT_TRUE(AreEqualWithString( websocket.GetProtocol().pp_var(), protocol.c_str())); const std::vector& events = websocket.GetSeenEvents(); // The server to which this test connect returns the decided protocol as a // text frame message. So the WebSocketEvent records EVENT_MESSAGE event // after EVENT_OPEN event. ASSERT_EQ(2U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[1].event_type); ASSERT_TRUE(AreEqualWithString(events[1].var.pp_var(), protocol.c_str())); PASS(); } std::string TestWebSocket::TestUtilityTextSendReceive() { const pp::Var protocols[] = { pp::Var() }; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(GetFullURL(kEchoServerURL)), protocols, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); // Send 'hello pepper'. std::string message1("hello pepper"); result = websocket.Send(pp::Var(std::string(message1))); ASSERT_EQ(PP_OK, result); // Receive echoed 'hello pepper'. websocket.WaitForReceived(); // Send 'goodbye pepper'. std::string message2("goodbye pepper"); result = websocket.Send(pp::Var(std::string(message2))); // Receive echoed 'goodbye pepper'. websocket.WaitForReceived(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(3U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[1].event_type); ASSERT_TRUE(AreEqualWithString(events[1].var.pp_var(), message1.c_str())); ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[2].event_type); ASSERT_TRUE(AreEqualWithString(events[2].var.pp_var(), message2.c_str())); PASS(); } std::string TestWebSocket::TestUtilityBinarySendReceive() { const pp::Var protocols[] = { pp::Var() }; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(GetFullURL(kEchoServerURL)), protocols, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); // Send binary message. uint32_t len = 256; std::vector binary(len); for (uint32_t i = 0; i < len; ++i) binary[i] = i; pp::VarArrayBuffer message(len); uint8_t* var_data = static_cast(message.Map()); std::copy(binary.begin(), binary.end(), var_data); result = websocket.Send(message); ASSERT_EQ(PP_OK, result); // Receive echoed binary message. websocket.WaitForReceived(); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(2U, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[1].event_type); ASSERT_TRUE(AreEqualWithBinary(events[1].var.pp_var(), binary)); PASS(); } std::string TestWebSocket::TestUtilityBufferedAmount() { // Connect to test echo server. const pp::Var protocols[] = { pp::Var() }; TestWebSocketAPI websocket(instance_); int32_t result = websocket.Connect(pp::Var(GetFullURL(kEchoServerURL)), protocols, 0U); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); websocket.WaitForConnected(); // Prepare a large message that is not aligned with the internal buffer // sizes. std::string message(8193, 'x'); uint64_t buffered_amount = 0; uint32_t sent; for (sent = 0; sent < 100; sent++) { result = websocket.Send(pp::Var(message)); ASSERT_EQ(PP_OK, result); buffered_amount = websocket.GetBufferedAmount(); // Buffered amount size 262144 is too big for the internal buffer size. if (buffered_amount > 262144) break; } // Close connection. std::string reason = "close while busy"; result = websocket.Close( PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var(reason)); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING, websocket.GetReadyState()); websocket.WaitForClosed(); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, websocket.GetReadyState()); uint64_t base_buffered_amount = websocket.GetBufferedAmount(); size_t events_on_closed = websocket.GetSeenEvents().size(); // After connection closure, all sending requests fail and just increase // the bufferedAmount property. result = websocket.Send(pp::Var(std::string())); ASSERT_EQ(PP_ERROR_FAILED, result); buffered_amount = websocket.GetBufferedAmount(); ASSERT_EQ(base_buffered_amount + kMessageFrameOverhead, buffered_amount); base_buffered_amount = buffered_amount; result = websocket.Send(pp::Var(reason)); ASSERT_EQ(PP_ERROR_FAILED, result); buffered_amount = websocket.GetBufferedAmount(); uint64_t reason_frame_size = kMessageFrameOverhead + reason.length(); ASSERT_EQ(base_buffered_amount + reason_frame_size, buffered_amount); const std::vector& events = websocket.GetSeenEvents(); ASSERT_EQ(events_on_closed, events.size()); ASSERT_EQ(WebSocketEvent::EVENT_OPEN, events[0].event_type); size_t last_event = events_on_closed - 1; for (uint32_t i = 1; i < last_event; ++i) { ASSERT_EQ(WebSocketEvent::EVENT_MESSAGE, events[i].event_type); ASSERT_TRUE(AreEqualWithString(events[i].var.pp_var(), message)); } ASSERT_EQ(WebSocketEvent::EVENT_CLOSE, events[last_event].event_type); ASSERT_TRUE(events[last_event].was_clean); PASS(); }