| // Copyright (c) 2017 The Chromium Embedded Framework 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 <list> |
| #include <map> |
| #include <set> |
| |
| #include "include/base/cef_bind.h" |
| #include "include/base/cef_ref_counted.h" |
| #include "include/base/cef_scoped_ptr.h" |
| #include "include/cef_command_line.h" |
| #include "include/cef_server.h" |
| #include "include/cef_task.h" |
| #include "include/cef_urlrequest.h" |
| #include "include/wrapper/cef_closure_task.h" |
| #include "tests/ceftests/routing_test_handler.h" |
| #include "tests/ceftests/test_util.h" |
| #include "tests/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| const char kTestServerAddress[] = "127.0.0.1"; |
| const uint16 kTestServerPort = 8099; |
| const int kTestTimeout = 5000; |
| |
| std::string GetTestServerOrigin(bool is_websocket) { |
| std::stringstream ss; |
| ss << (is_websocket ? "ws://" : "http://") << kTestServerAddress << ":" |
| << kTestServerPort << "/"; |
| return ss.str(); |
| } |
| |
| // Handles the test server. Used for both HTTP and WebSocket tests. |
| class TestServerHandler : public CefServerHandler { |
| public: |
| // HTTP test handler. |
| // The methods of this class are always executed on the server thread. |
| class HttpRequestHandler { |
| public: |
| virtual ~HttpRequestHandler() {} |
| virtual bool HandleRequest(CefRefPtr<CefServer> server, |
| int connection_id, |
| const CefString& client_address, |
| CefRefPtr<CefRequest> request) = 0; |
| virtual bool VerifyResults() = 0; |
| virtual std::string ToString() = 0; |
| }; |
| |
| // WebSocket test handler. |
| // The methods of this class are always executed on the server thread. |
| class WsRequestHandler { |
| public: |
| virtual ~WsRequestHandler() {} |
| virtual bool HandleRequest(CefRefPtr<CefServer> server, |
| int connection_id, |
| const CefString& client_address, |
| CefRefPtr<CefRequest> request, |
| CefRefPtr<CefCallback> callback) = 0; |
| virtual bool HandleConnected(CefRefPtr<CefServer> server, |
| int connection_id) = 0; |
| virtual bool HandleMessage(CefRefPtr<CefServer> server, |
| int connection_id, |
| const void* data, |
| size_t data_size) = 0; |
| virtual bool VerifyResults() = 0; |
| virtual std::string ToString() = 0; |
| }; |
| |
| // |start_callback| will be executed on the UI thread after the server is |
| // started. |
| // |destroy_callback| will be executed on the UI thread after this handler |
| // object is destroyed. |
| TestServerHandler(const base::Closure& start_callback, |
| const base::Closure& destroy_callback) |
| : initialized_(false), |
| start_callback_(start_callback), |
| destroy_callback_(destroy_callback), |
| expected_connection_ct_(0), |
| actual_connection_ct_(0), |
| expected_http_request_ct_(0), |
| actual_http_request_ct_(0), |
| expected_ws_request_ct_(0), |
| actual_ws_request_ct_(0), |
| expected_ws_connected_ct_(0), |
| actual_ws_connected_ct_(0), |
| expected_ws_message_ct_(0), |
| actual_ws_message_ct_(0) { |
| EXPECT_FALSE(destroy_callback_.is_null()); |
| } |
| |
| virtual ~TestServerHandler() { |
| EXPECT_UI_THREAD(); |
| |
| if (!http_request_handler_list_.empty()) { |
| HttpRequestHandlerList::const_iterator it = |
| http_request_handler_list_.begin(); |
| for (; it != http_request_handler_list_.end(); ++it) |
| delete *it; |
| } |
| |
| if (!ws_request_handler_list_.empty()) { |
| WsRequestHandlerList::const_iterator it = |
| ws_request_handler_list_.begin(); |
| for (; it != ws_request_handler_list_.end(); ++it) |
| delete *it; |
| } |
| |
| destroy_callback_.Run(); |
| } |
| |
| // Must be called before CreateServer(). |
| void SetExpectedConnectionCount(int expected) { |
| EXPECT_FALSE(initialized_); |
| expected_connection_ct_ = expected; |
| } |
| |
| // Must be called before CreateServer(). |
| void AddHttpRequestHandler(scoped_ptr<HttpRequestHandler> request_handler) { |
| EXPECT_FALSE(initialized_); |
| EXPECT_TRUE(request_handler); |
| http_request_handler_list_.push_back(request_handler.release()); |
| } |
| |
| // Must be called before CreateServer(). |
| void SetExpectedHttpRequestCount(int expected) { |
| EXPECT_FALSE(initialized_); |
| expected_http_request_ct_ = expected; |
| } |
| |
| // Must be called before CreateServer(). |
| void AddWsRequestHandler(scoped_ptr<WsRequestHandler> request_handler) { |
| EXPECT_FALSE(initialized_); |
| EXPECT_TRUE(request_handler); |
| ws_request_handler_list_.push_back(request_handler.release()); |
| } |
| |
| // Must be called before CreateServer(). |
| void SetExpectedWsRequestCount(int expected) { |
| EXPECT_FALSE(initialized_); |
| expected_ws_request_ct_ = expected; |
| } |
| |
| // Must be called before CreateServer(). |
| void SetExpectedWsConnectedCount(int expected) { |
| EXPECT_FALSE(initialized_); |
| expected_ws_connected_ct_ = expected; |
| } |
| |
| // Must be called before CreateServer(). |
| void SetExpectedWsMessageCount(int expected) { |
| EXPECT_FALSE(initialized_); |
| expected_ws_message_ct_ = expected; |
| } |
| |
| void CreateServer() { |
| EXPECT_FALSE(initialized_); |
| initialized_ = true; |
| CefServer::CreateServer(kTestServerAddress, kTestServerPort, 10, this); |
| } |
| |
| // Results in a call to VerifyResults() and eventual execution of the |
| // |destroy_callback|. |
| void ShutdownServer() { |
| EXPECT_TRUE(server_); |
| if (server_) |
| server_->Shutdown(); |
| } |
| |
| void OnServerCreated(CefRefPtr<CefServer> server) override { |
| EXPECT_TRUE(server); |
| EXPECT_TRUE(server->IsRunning()); |
| EXPECT_FALSE(server->HasConnection()); |
| |
| EXPECT_FALSE(got_server_created_); |
| got_server_created_.yes(); |
| |
| EXPECT_FALSE(server_); |
| server_ = server; |
| |
| EXPECT_FALSE(server_runner_); |
| server_runner_ = server_->GetTaskRunner(); |
| EXPECT_TRUE(server_runner_); |
| EXPECT_TRUE(server_runner_->BelongsToCurrentThread()); |
| |
| RunStartCallback(); |
| } |
| |
| void OnServerDestroyed(CefRefPtr<CefServer> server) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_FALSE(server->IsRunning()); |
| EXPECT_FALSE(server->HasConnection()); |
| |
| EXPECT_FALSE(got_server_destroyed_); |
| got_server_destroyed_.yes(); |
| |
| server_ = nullptr; |
| |
| VerifyResults(); |
| } |
| |
| void OnClientConnected(CefRefPtr<CefServer> server, |
| int connection_id) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_TRUE(server->HasConnection()); |
| EXPECT_TRUE(server->IsValidConnection(connection_id)); |
| |
| EXPECT_TRUE(connection_id_set_.find(connection_id) == |
| connection_id_set_.end()); |
| connection_id_set_.insert(connection_id); |
| |
| actual_connection_ct_++; |
| } |
| |
| void OnClientDisconnected(CefRefPtr<CefServer> server, |
| int connection_id) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_FALSE(server->IsValidConnection(connection_id)); |
| |
| ConnectionIdSet::iterator it = connection_id_set_.find(connection_id); |
| EXPECT_TRUE(it != connection_id_set_.end()); |
| connection_id_set_.erase(it); |
| |
| ConnectionIdSet::iterator it2 = ws_connection_id_set_.find(connection_id); |
| if (it2 != ws_connection_id_set_.end()) |
| ws_connection_id_set_.erase(it2); |
| |
| if (connection_id_set_.empty()) { |
| EXPECT_TRUE(ws_connection_id_set_.empty()); |
| EXPECT_FALSE(server->HasConnection()); |
| } |
| } |
| |
| void OnHttpRequest(CefRefPtr<CefServer> server, |
| int connection_id, |
| const CefString& client_address, |
| CefRefPtr<CefRequest> request) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_TRUE(VerifyConnection(connection_id)); |
| EXPECT_FALSE(client_address.empty()); |
| EXPECT_TRUE(VerifyRequest(request, false)); |
| |
| bool handled = false; |
| HttpRequestHandlerList::const_iterator it = |
| http_request_handler_list_.begin(); |
| for (; it != http_request_handler_list_.end(); ++it) { |
| handled = |
| (*it)->HandleRequest(server, connection_id, client_address, request); |
| if (handled) |
| break; |
| } |
| EXPECT_TRUE(handled) << "missing HttpRequestHandler for " |
| << request->GetURL().ToString(); |
| |
| actual_http_request_ct_++; |
| } |
| |
| void OnWebSocketRequest(CefRefPtr<CefServer> server, |
| int connection_id, |
| const CefString& client_address, |
| CefRefPtr<CefRequest> request, |
| CefRefPtr<CefCallback> callback) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_TRUE(VerifyConnection(connection_id)); |
| EXPECT_FALSE(client_address.empty()); |
| EXPECT_TRUE(VerifyRequest(request, true)); |
| |
| EXPECT_TRUE(ws_connection_id_set_.find(connection_id) == |
| ws_connection_id_set_.end()); |
| ws_connection_id_set_.insert(connection_id); |
| |
| bool handled = false; |
| WsRequestHandlerList::const_iterator it = ws_request_handler_list_.begin(); |
| for (; it != ws_request_handler_list_.end(); ++it) { |
| handled = (*it)->HandleRequest(server, connection_id, client_address, |
| request, callback); |
| if (handled) |
| break; |
| } |
| EXPECT_TRUE(handled) << "missing WsRequestHandler for " |
| << request->GetURL().ToString(); |
| |
| actual_ws_request_ct_++; |
| } |
| |
| void OnWebSocketConnected(CefRefPtr<CefServer> server, |
| int connection_id) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_TRUE(VerifyConnection(connection_id)); |
| |
| EXPECT_TRUE(ws_connection_id_set_.find(connection_id) != |
| ws_connection_id_set_.end()); |
| |
| bool handled = false; |
| WsRequestHandlerList::const_iterator it = ws_request_handler_list_.begin(); |
| for (; it != ws_request_handler_list_.end(); ++it) { |
| handled = (*it)->HandleConnected(server, connection_id); |
| if (handled) |
| break; |
| } |
| EXPECT_TRUE(handled) << "missing WsRequestHandler for " << connection_id; |
| |
| actual_ws_connected_ct_++; |
| } |
| |
| void OnWebSocketMessage(CefRefPtr<CefServer> server, |
| int connection_id, |
| const void* data, |
| size_t data_size) override { |
| EXPECT_TRUE(VerifyServer(server)); |
| EXPECT_TRUE(VerifyConnection(connection_id)); |
| EXPECT_TRUE(data); |
| EXPECT_GT(data_size, 0U); |
| |
| EXPECT_TRUE(ws_connection_id_set_.find(connection_id) != |
| ws_connection_id_set_.end()); |
| |
| bool handled = false; |
| WsRequestHandlerList::const_iterator it = ws_request_handler_list_.begin(); |
| for (; it != ws_request_handler_list_.end(); ++it) { |
| handled = (*it)->HandleMessage(server, connection_id, data, data_size); |
| if (handled) |
| break; |
| } |
| EXPECT_TRUE(handled) << "missing WsRequestHandler for " << connection_id; |
| |
| actual_ws_message_ct_++; |
| } |
| |
| private: |
| bool RunningOnServerThread() { |
| return server_runner_ && server_runner_->BelongsToCurrentThread(); |
| } |
| |
| bool VerifyServer(CefRefPtr<CefServer> server) { |
| V_DECLARE(); |
| V_EXPECT_TRUE(RunningOnServerThread()); |
| V_EXPECT_TRUE(server); |
| V_EXPECT_TRUE(server_); |
| V_EXPECT_TRUE(server->GetAddress().ToString() == |
| server_->GetAddress().ToString()); |
| V_RETURN(); |
| } |
| |
| bool VerifyConnection(int connection_id) { |
| return connection_id_set_.find(connection_id) != connection_id_set_.end(); |
| } |
| |
| bool VerifyRequest(CefRefPtr<CefRequest> request, bool is_websocket) { |
| V_DECLARE(); |
| |
| V_EXPECT_FALSE(request->GetMethod().empty()); |
| |
| const std::string& url = request->GetURL(); |
| V_EXPECT_FALSE(url.empty()); |
| const std::string& address = server_->GetAddress(); |
| V_EXPECT_TRUE(url.find((is_websocket ? "ws://" : "http://") + address) == 0) |
| << "url " << url << " address " << address; |
| |
| CefRefPtr<CefPostData> post_data = request->GetPostData(); |
| if (post_data) { |
| CefPostData::ElementVector elements; |
| post_data->GetElements(elements); |
| V_EXPECT_TRUE(elements.size() == 1); |
| V_EXPECT_TRUE(elements[0]->GetBytesCount() > 0U); |
| } |
| |
| V_RETURN(); |
| } |
| |
| void VerifyResults() { |
| EXPECT_TRUE(RunningOnServerThread()); |
| |
| EXPECT_TRUE(got_server_created_); |
| EXPECT_TRUE(got_server_destroyed_); |
| EXPECT_TRUE(connection_id_set_.empty()); |
| EXPECT_EQ(expected_connection_ct_, actual_connection_ct_); |
| |
| // HTTP |
| |
| EXPECT_EQ(expected_http_request_ct_, actual_http_request_ct_); |
| |
| if (!http_request_handler_list_.empty()) { |
| HttpRequestHandlerList::const_iterator it = |
| http_request_handler_list_.begin(); |
| for (; it != http_request_handler_list_.end(); ++it) { |
| EXPECT_TRUE((*it)->VerifyResults()) |
| << "HttpRequestHandler for " << (*it)->ToString(); |
| } |
| } |
| |
| // WebSocket |
| |
| EXPECT_EQ(expected_ws_request_ct_, actual_ws_request_ct_); |
| EXPECT_EQ(expected_ws_connected_ct_, actual_ws_connected_ct_); |
| EXPECT_EQ(expected_ws_message_ct_, actual_ws_message_ct_); |
| |
| if (!ws_request_handler_list_.empty()) { |
| WsRequestHandlerList::const_iterator it = |
| ws_request_handler_list_.begin(); |
| for (; it != ws_request_handler_list_.end(); ++it) { |
| EXPECT_TRUE((*it)->VerifyResults()) |
| << "WsRequestHandler for " << (*it)->ToString(); |
| } |
| } |
| } |
| |
| private: |
| void RunStartCallback() { |
| if (!CefCurrentlyOn(TID_UI)) { |
| CefPostTask(TID_UI, |
| base::Bind(&TestServerHandler::RunStartCallback, this)); |
| return; |
| } |
| |
| EXPECT_FALSE(start_callback_.is_null()); |
| start_callback_.Run(); |
| start_callback_.Reset(); |
| } |
| |
| CefRefPtr<CefServer> server_; |
| CefRefPtr<CefTaskRunner> server_runner_; |
| bool initialized_; |
| |
| // After initialization only accessed on the UI thread. |
| base::Closure start_callback_; |
| base::Closure destroy_callback_; |
| |
| // After initialization the below members are only accessed on the server |
| // thread. |
| |
| TrackCallback got_server_created_; |
| TrackCallback got_server_destroyed_; |
| |
| typedef std::set<int> ConnectionIdSet; |
| ConnectionIdSet connection_id_set_; |
| |
| int expected_connection_ct_; |
| int actual_connection_ct_; |
| |
| // HTTP |
| |
| typedef std::list<HttpRequestHandler*> HttpRequestHandlerList; |
| HttpRequestHandlerList http_request_handler_list_; |
| |
| int expected_http_request_ct_; |
| int actual_http_request_ct_; |
| |
| // WebSocket |
| |
| typedef std::list<WsRequestHandler*> WsRequestHandlerList; |
| WsRequestHandlerList ws_request_handler_list_; |
| |
| ConnectionIdSet ws_connection_id_set_; |
| |
| int expected_ws_request_ct_; |
| int actual_ws_request_ct_; |
| |
| int expected_ws_connected_ct_; |
| int actual_ws_connected_ct_; |
| |
| int expected_ws_message_ct_; |
| int actual_ws_message_ct_; |
| |
| IMPLEMENT_REFCOUNTING(TestServerHandler); |
| DISALLOW_COPY_AND_ASSIGN(TestServerHandler); |
| }; |
| |
| // HTTP TESTS |
| |
| // Test runner for 1 or more HTTP requests/responses. |
| // Works similarly to TestHandler but without the CefClient dependencies. |
| class HttpTestRunner : public base::RefCountedThreadSafe<HttpTestRunner> { |
| public: |
| // The methods of this class are always executed on the UI thread. |
| class RequestRunner { |
| public: |
| virtual ~RequestRunner() {} |
| |
| // Create the server-side handler for the request. |
| virtual scoped_ptr<TestServerHandler::HttpRequestHandler> |
| CreateHttpRequestHandler() = 0; |
| |
| // Run the request and execute |complete_callback| on completion. |
| virtual void RunRequest(const base::Closure& complete_callback) = 0; |
| |
| virtual bool VerifyResults() = 0; |
| virtual std::string ToString() = 0; |
| }; |
| |
| // If |parallel_requests| is true all requests will be run at the same time, |
| // otherwise one request will be run at a time. |
| HttpTestRunner(bool parallel_requests) |
| : parallel_requests_(parallel_requests), |
| initialized_(false), |
| next_request_id_(0) { |
| handler_ = new TestServerHandler( |
| base::Bind(&HttpTestRunner::OnServerStarted, this), |
| base::Bind(&HttpTestRunner::OnServerDestroyed, this)); |
| } |
| |
| virtual ~HttpTestRunner() { |
| if (destroy_event_) |
| destroy_event_->Signal(); |
| } |
| |
| void AddRequestRunner(scoped_ptr<RequestRunner> request_runner) { |
| EXPECT_FALSE(initialized_); |
| request_runner_map_.insert( |
| std::make_pair(++next_request_id_, request_runner.release())); |
| } |
| |
| // Blocks until the test has completed or timed out. |
| void ExecuteTest() { |
| EXPECT_FALSE(CefCurrentlyOn(TID_UI)); |
| |
| run_event_ = CefWaitableEvent::CreateWaitableEvent(false, false); |
| |
| CefPostTask(TID_UI, base::Bind(&HttpTestRunner::RunTest, this)); |
| |
| // Block until test completion. |
| run_event_->Wait(); |
| } |
| |
| // Event that will be signaled from the HttpTestRunner destructor. |
| // Used by ReleaseAndWaitForDestructor. |
| void SetDestroyEvent(CefRefPtr<CefWaitableEvent> event) { |
| destroy_event_ = event; |
| } |
| |
| private: |
| void RunTest() { |
| EXPECT_UI_THREAD(); |
| EXPECT_FALSE(initialized_); |
| initialized_ = true; |
| |
| EXPECT_FALSE(request_runner_map_.empty()); |
| RequestRunnerMap::const_iterator it = request_runner_map_.begin(); |
| for (; it != request_runner_map_.end(); ++it) { |
| handler_->AddHttpRequestHandler(it->second->CreateHttpRequestHandler()); |
| } |
| |
| handler_->SetExpectedConnectionCount( |
| static_cast<int>(request_runner_map_.size())); |
| handler_->SetExpectedHttpRequestCount( |
| static_cast<int>(request_runner_map_.size())); |
| |
| handler_->CreateServer(); |
| |
| SetTestTimeout(kTestTimeout); |
| } |
| |
| void OnServerStarted() { |
| EXPECT_UI_THREAD(); |
| if (parallel_requests_) { |
| RunAllRequests(); |
| } else { |
| RunNextRequest(); |
| } |
| } |
| |
| void OnServerDestroyed() { |
| EXPECT_UI_THREAD(); |
| EXPECT_FALSE(got_server_destroyed_); |
| got_server_destroyed_.yes(); |
| |
| // Allow the call stack to unwind. |
| CefPostTask(TID_UI, base::Bind(&HttpTestRunner::DestroyTest, this)); |
| } |
| |
| // Run all requests in parallel. |
| void RunAllRequests() { |
| RequestRunnerMap::const_iterator it = request_runner_map_.begin(); |
| for (; it != request_runner_map_.end(); ++it) { |
| it->second->RunRequest( |
| base::Bind(&HttpTestRunner::OnRequestComplete, this, it->first)); |
| } |
| } |
| |
| // Run one request at a time. |
| void RunNextRequest() { |
| RequestRunnerMap::const_iterator it = request_runner_map_.begin(); |
| it->second->RunRequest( |
| base::Bind(&HttpTestRunner::OnRequestComplete, this, it->first)); |
| } |
| |
| void OnRequestComplete(int request_id) { |
| EXPECT_UI_THREAD() |
| // Allow the call stack to unwind. |
| CefPostTask(TID_UI, base::Bind(&HttpTestRunner::OnRequestCompleteContinue, |
| this, request_id)); |
| } |
| |
| void OnRequestCompleteContinue(int request_id) { |
| RequestRunnerMap::iterator it = request_runner_map_.find(request_id); |
| EXPECT_TRUE(it != request_runner_map_.end()); |
| |
| // Verify the request results. |
| EXPECT_TRUE(it->second->VerifyResults()) |
| << "request_id " << request_id << " RequestRunner for " |
| << it->second->ToString(); |
| delete it->second; |
| request_runner_map_.erase(it); |
| |
| if (request_runner_map_.empty()) { |
| got_all_requests_.yes(); |
| |
| // Will trigger TestServerHandler::HttpRequestHandler verification and a |
| // call to OnServerDestroyed(). |
| handler_->ShutdownServer(); |
| handler_ = nullptr; |
| } else if (!parallel_requests_) { |
| RunNextRequest(); |
| } |
| } |
| |
| void DestroyTest() { |
| EXPECT_UI_THREAD(); |
| |
| EXPECT_TRUE(got_all_requests_); |
| EXPECT_TRUE(got_server_destroyed_); |
| EXPECT_TRUE(request_runner_map_.empty()); |
| |
| // Cancel the timeout, if any. |
| if (ui_thread_helper_) |
| ui_thread_helper_.reset(); |
| |
| // Signal test completion. |
| run_event_->Signal(); |
| } |
| |
| TestHandler::UIThreadHelper* GetUIThreadHelper() { |
| EXPECT_UI_THREAD(); |
| if (!ui_thread_helper_) |
| ui_thread_helper_.reset(new TestHandler::UIThreadHelper()); |
| return ui_thread_helper_.get(); |
| } |
| |
| void SetTestTimeout(int timeout_ms) { |
| EXPECT_UI_THREAD(); |
| if (CefCommandLine::GetGlobalCommandLine()->HasSwitch( |
| "disable-test-timeout")) { |
| return; |
| } |
| |
| // Use a weak reference to |this| via UIThreadHelper so that the |
| // test runner can be destroyed before the timeout expires. |
| GetUIThreadHelper()->PostDelayedTask( |
| base::Bind(&HttpTestRunner::OnTestTimeout, base::Unretained(this), |
| timeout_ms), |
| timeout_ms); |
| } |
| |
| void OnTestTimeout(int timeout_ms) { |
| EXPECT_UI_THREAD(); |
| EXPECT_TRUE(false) << "Test timed out after " << timeout_ms << "ms"; |
| DestroyTest(); |
| } |
| |
| bool parallel_requests_; |
| CefRefPtr<CefWaitableEvent> run_event_; |
| CefRefPtr<CefWaitableEvent> destroy_event_; |
| CefRefPtr<TestServerHandler> handler_; |
| bool initialized_; |
| |
| // After initialization the below members are only accessed on the UI thread. |
| |
| int next_request_id_; |
| |
| // Map of request ID to RequestRunner. |
| typedef std::map<int, RequestRunner*> RequestRunnerMap; |
| RequestRunnerMap request_runner_map_; |
| |
| TrackCallback got_all_requests_; |
| TrackCallback got_server_destroyed_; |
| |
| scoped_ptr<TestHandler::UIThreadHelper> ui_thread_helper_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HttpTestRunner); |
| }; |
| |
| // Structure representing the data that can be sent via |
| // CefServer::SendHttp*Response(). |
| struct HttpServerResponse { |
| enum Type { TYPE_200, TYPE_404, TYPE_500, TYPE_CUSTOM }; |
| |
| explicit HttpServerResponse(Type response_type) |
| : type(response_type), no_content_length(false) {} |
| |
| Type type; |
| |
| // Used with 200 and CUSTOM response type. |
| std::string content; |
| std::string content_type; |
| |
| // Used with 500 response type. |
| std::string error_message; |
| |
| // Used with CUSTOM response type. |
| int response_code; |
| CefServer::HeaderMap extra_headers; |
| bool no_content_length; |
| }; |
| |
| void SendHttpServerResponse(CefRefPtr<CefServer> server, |
| int connection_id, |
| const HttpServerResponse& response) { |
| EXPECT_TRUE(server->GetTaskRunner()->BelongsToCurrentThread()); |
| EXPECT_TRUE(server->IsValidConnection(connection_id)); |
| |
| switch (response.type) { |
| case HttpServerResponse::TYPE_200: |
| EXPECT_TRUE(!response.content_type.empty()); |
| server->SendHttp200Response(connection_id, response.content_type, |
| response.content.data(), |
| response.content.size()); |
| break; |
| case HttpServerResponse::TYPE_404: |
| server->SendHttp404Response(connection_id); |
| break; |
| case HttpServerResponse::TYPE_500: |
| server->SendHttp500Response(connection_id, response.error_message); |
| break; |
| case HttpServerResponse::TYPE_CUSTOM: |
| EXPECT_TRUE(!response.content_type.empty()); |
| server->SendHttpResponse( |
| connection_id, response.response_code, response.content_type, |
| response.no_content_length |
| ? -1 |
| : static_cast<int64>(response.content.size()), |
| response.extra_headers); |
| if (!response.content.empty()) { |
| server->SendRawData(connection_id, response.content.data(), |
| response.content.size()); |
| } |
| if (!response.content.empty() || |
| (response.content.empty() && response.no_content_length)) { |
| server->CloseConnection(connection_id); |
| } |
| break; |
| } |
| |
| // All of the above responses should close the connection. |
| EXPECT_FALSE(server->IsValidConnection(connection_id)); |
| } |
| |
| std::string GetHeaderValue(const CefServer::HeaderMap& header_map, |
| const std::string& header_name) { |
| CefServer::HeaderMap::const_iterator it = header_map.find(header_name); |
| if (it != header_map.end()) |
| return it->second; |
| return std::string(); |
| } |
| |
| void VerifyHttpServerResponse(const HttpServerResponse& expected_response, |
| CefRefPtr<CefResponse> response, |
| const std::string& data) { |
| CefServer::HeaderMap header_map; |
| response->GetHeaderMap(header_map); |
| |
| switch (expected_response.type) { |
| case HttpServerResponse::TYPE_200: |
| EXPECT_EQ(200, response->GetStatus()); |
| EXPECT_STREQ(expected_response.content_type.c_str(), |
| GetHeaderValue(header_map, "Content-Type").c_str()); |
| EXPECT_STREQ(expected_response.content.c_str(), data.c_str()); |
| break; |
| case HttpServerResponse::TYPE_404: |
| EXPECT_EQ(404, response->GetStatus()); |
| break; |
| case HttpServerResponse::TYPE_500: |
| EXPECT_EQ(500, response->GetStatus()); |
| break; |
| case HttpServerResponse::TYPE_CUSTOM: |
| EXPECT_EQ(expected_response.response_code, response->GetStatus()); |
| EXPECT_STREQ(expected_response.content_type.c_str(), |
| GetHeaderValue(header_map, "Content-Type").c_str()); |
| if (expected_response.no_content_length) { |
| EXPECT_TRUE(GetHeaderValue(header_map, "Content-Length").empty()); |
| } else { |
| EXPECT_FALSE(GetHeaderValue(header_map, "Content-Length").empty()); |
| } |
| EXPECT_STREQ(expected_response.content.c_str(), data.c_str()); |
| TestMapEqual(expected_response.extra_headers, header_map, true); |
| break; |
| } |
| } |
| |
| CefRefPtr<CefRequest> CreateTestServerRequest( |
| const std::string& path, |
| const std::string& method, |
| const std::string& data = std::string(), |
| const std::string& content_type = std::string(), |
| const CefRequest::HeaderMap& extra_headers = CefRequest::HeaderMap()) { |
| CefRefPtr<CefRequest> request = CefRequest::Create(); |
| request->SetURL(GetTestServerOrigin(false) + path); |
| request->SetMethod(method); |
| |
| CefRequest::HeaderMap header_map; |
| |
| if (!data.empty()) { |
| CefRefPtr<CefPostData> post_data = CefPostData::Create(); |
| CefRefPtr<CefPostDataElement> post_element = CefPostDataElement::Create(); |
| post_element->SetToBytes(data.size(), data.data()); |
| post_data->AddElement(post_element); |
| request->SetPostData(post_data); |
| |
| EXPECT_FALSE(content_type.empty()); |
| header_map.insert(std::make_pair("content-type", content_type)); |
| } |
| |
| if (!extra_headers.empty()) |
| header_map.insert(extra_headers.begin(), extra_headers.end()); |
| request->SetHeaderMap(header_map); |
| |
| return request; |
| } |
| |
| // RequestHandler that returns a static response for 1 or more requests. |
| class StaticHttpServerRequestHandler |
| : public TestServerHandler::HttpRequestHandler { |
| public: |
| StaticHttpServerRequestHandler(CefRefPtr<CefRequest> expected_request, |
| int expected_request_ct, |
| const HttpServerResponse& response) |
| : expected_request_(expected_request), |
| expected_request_ct_(expected_request_ct), |
| actual_request_ct_(0), |
| response_(response) {} |
| |
| bool HandleRequest(CefRefPtr<CefServer> server, |
| int connection_id, |
| const CefString& client_address, |
| CefRefPtr<CefRequest> request) override { |
| if (request->GetURL() == expected_request_->GetURL() && |
| request->GetMethod() == expected_request_->GetMethod()) { |
| TestRequestEqual(expected_request_, request, true); |
| actual_request_ct_++; |
| |
| SendHttpServerResponse(server, connection_id, response_); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool VerifyResults() override { |
| EXPECT_EQ(expected_request_ct_, actual_request_ct_); |
| return expected_request_ct_ == actual_request_ct_; |
| } |
| |
| std::string ToString() override { return expected_request_->GetURL(); } |
| |
| private: |
| CefRefPtr<CefRequest> expected_request_; |
| int expected_request_ct_; |
| int actual_request_ct_; |
| HttpServerResponse response_; |
| |
| DISALLOW_COPY_AND_ASSIGN(StaticHttpServerRequestHandler); |
| }; |
| |
| // URLRequestClient that runs a single request and executes a callback with the |
| // response. |
| class StaticHttpURLRequestClient : public CefURLRequestClient { |
| public: |
| typedef base::Callback<void(cef_errorcode_t /* error */, |
| CefRefPtr<CefResponse> /* response */, |
| const std::string& /* data */)> |
| ResponseCallback; |
| |
| // |response_callback| will be executed on the UI thread when the response |
| // is complete. |
| StaticHttpURLRequestClient(CefRefPtr<CefRequest> request, |
| const ResponseCallback& response_callback) |
| : request_(request), response_callback_(response_callback) { |
| EXPECT_TRUE(request_); |
| EXPECT_FALSE(response_callback_.is_null()); |
| } |
| |
| void RunRequest() { |
| EXPECT_UI_THREAD(); |
| CefURLRequest::Create(request_, this, nullptr); |
| } |
| |
| void OnRequestComplete(CefRefPtr<CefURLRequest> request) override { |
| EXPECT_FALSE(response_callback_.is_null()); |
| response_callback_.Run(request->GetRequestError(), request->GetResponse(), |
| data_); |
| response_callback_.Reset(); |
| } |
| |
| void OnUploadProgress(CefRefPtr<CefURLRequest> request, |
| int64 current, |
| int64 total) override {} |
| |
| void OnDownloadProgress(CefRefPtr<CefURLRequest> request, |
| int64 current, |
| int64 total) override {} |
| |
| void OnDownloadData(CefRefPtr<CefURLRequest> request, |
| const void* data, |
| size_t data_length) override { |
| data_.append(static_cast<const char*>(data), data_length); |
| } |
| |
| bool GetAuthCredentials(bool isProxy, |
| const CefString& host, |
| int port, |
| const CefString& realm, |
| const CefString& scheme, |
| CefRefPtr<CefAuthCallback> callback) override { |
| return false; |
| } |
| |
| private: |
| CefRefPtr<CefRequest> request_; |
| ResponseCallback response_callback_; |
| std::string data_; |
| |
| IMPLEMENT_REFCOUNTING(StaticHttpURLRequestClient); |
| DISALLOW_COPY_AND_ASSIGN(StaticHttpURLRequestClient); |
| }; |
| |
| // RequestRunner that will manage a single static HTTP request/response. |
| class StaticHttpRequestRunner : public HttpTestRunner::RequestRunner { |
| public: |
| StaticHttpRequestRunner(CefRefPtr<CefRequest> request, |
| const HttpServerResponse& response) |
| : request_(request), response_(response) {} |
| |
| static scoped_ptr<HttpTestRunner::RequestRunner> Create200( |
| const std::string& path, |
| bool with_content = true) { |
| CefRefPtr<CefRequest> request = CreateTestServerRequest(path, "GET"); |
| HttpServerResponse response(HttpServerResponse::TYPE_200); |
| response.content_type = "text/html"; |
| if (with_content) |
| response.content = "<html>200 response content</html>"; |
| return make_scoped_ptr<HttpTestRunner::RequestRunner>( |
| new StaticHttpRequestRunner(request, response)); |
| } |
| |
| static scoped_ptr<HttpTestRunner::RequestRunner> Create404( |
| const std::string& path) { |
| CefRefPtr<CefRequest> request = CreateTestServerRequest(path, "GET"); |
| HttpServerResponse response(HttpServerResponse::TYPE_404); |
| return make_scoped_ptr<HttpTestRunner::RequestRunner>( |
| new StaticHttpRequestRunner(request, response)); |
| } |
| |
| static scoped_ptr<HttpTestRunner::RequestRunner> Create500( |
| const std::string& path) { |
| CefRefPtr<CefRequest> request = CreateTestServerRequest(path, "GET"); |
| // Don't retry the request. |
| request->SetFlags(UR_FLAG_NO_RETRY_ON_5XX); |
| HttpServerResponse response(HttpServerResponse::TYPE_500); |
| response.error_message = "Something went wrong!"; |
| return make_scoped_ptr<HttpTestRunner::RequestRunner>( |
| new StaticHttpRequestRunner(request, response)); |
| } |
| |
| static scoped_ptr<HttpTestRunner::RequestRunner> CreateCustom( |
| const std::string& path, |
| bool with_content = true, |
| bool with_content_length = true) { |
| CefRequest::HeaderMap request_headers; |
| request_headers.insert(std::make_pair("x-request-custom1", "My Value A")); |
| request_headers.insert(std::make_pair("x-request-custom2", "My Value B")); |
| CefRefPtr<CefRequest> request = CreateTestServerRequest( |
| path, "POST", "foo=bar&choo=too", "application/x-www-form-urlencoded", |
| request_headers); |
| request->SetReferrer("http://tests/referer.html", REFERRER_POLICY_DEFAULT); |
| |
| HttpServerResponse response(HttpServerResponse::TYPE_CUSTOM); |
| response.response_code = 202; |
| if (with_content) |
| response.content = "BlahBlahBlah"; |
| if (!with_content_length) |
| response.no_content_length = true; |
| response.content_type = "application/x-blah-blah"; |
| response.extra_headers.insert( |
| std::make_pair("x-response-custom1", "My Value 1")); |
| response.extra_headers.insert( |
| std::make_pair("x-response-custom2", "My Value 2")); |
| |
| return make_scoped_ptr<HttpTestRunner::RequestRunner>( |
| new StaticHttpRequestRunner(request, response)); |
| } |
| |
| scoped_ptr<TestServerHandler::HttpRequestHandler> CreateHttpRequestHandler() |
| override { |
| EXPECT_FALSE(got_create_handler_); |
| got_create_handler_.yes(); |
| return make_scoped_ptr<TestServerHandler::HttpRequestHandler>( |
| new StaticHttpServerRequestHandler(request_, 1, response_)); |
| } |
| |
| void RunRequest(const base::Closure& complete_callback) override { |
| EXPECT_UI_THREAD(); |
| |
| EXPECT_FALSE(got_run_request_); |
| got_run_request_.yes(); |
| |
| complete_callback_ = complete_callback; |
| |
| request_client_ = new StaticHttpURLRequestClient( |
| request_, base::Bind(&StaticHttpRequestRunner::OnResponseComplete, |
| base::Unretained(this))); |
| request_client_->RunRequest(); |
| } |
| |
| bool VerifyResults() override { |
| V_DECLARE(); |
| V_EXPECT_TRUE(got_create_handler_); |
| V_EXPECT_TRUE(got_run_request_); |
| V_EXPECT_TRUE(got_response_complete_); |
| V_RETURN(); |
| } |
| |
| std::string ToString() override { return request_->GetURL(); } |
| |
| private: |
| void OnResponseComplete(cef_errorcode_t error, |
| CefRefPtr<CefResponse> response, |
| const std::string& data) { |
| EXPECT_UI_THREAD(); |
| |
| EXPECT_FALSE(got_response_complete_); |
| got_response_complete_.yes(); |
| |
| EXPECT_EQ(error, ERR_NONE) |
| << "OnResponseComplete for " << request_->GetURL().ToString(); |
| if (error == ERR_NONE) |
| VerifyHttpServerResponse(response_, response, data); |
| |
| complete_callback_.Run(); |
| complete_callback_.Reset(); |
| } |
| |
| CefRefPtr<CefRequest> request_; |
| HttpServerResponse response_; |
| |
| CefRefPtr<StaticHttpURLRequestClient> request_client_; |
| base::Closure complete_callback_; |
| |
| TrackCallback got_run_request_; |
| TrackCallback got_create_handler_; |
| TrackCallback got_response_complete_; |
| |
| DISALLOW_COPY_AND_ASSIGN(StaticHttpRequestRunner); |
| }; |
| |
| } // namespace |
| |
| // Verify handling of a single HTTP 200 request. |
| TEST(ServerTest, HttpSingle200) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP 200 request with no content. |
| TEST(ServerTest, HttpSingle200NoContent) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner( |
| StaticHttpRequestRunner::Create200("200.html", false)); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP 404 request. |
| TEST(ServerTest, HttpSingle404) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create404("404.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP 500 request. |
| TEST(ServerTest, HttpSingle500) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create500("500.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP custom request. |
| TEST(ServerTest, HttpSingleCustom) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner(StaticHttpRequestRunner::CreateCustom("202.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP custom request with no content. |
| TEST(ServerTest, HttpSingleCustomNoContent) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner( |
| StaticHttpRequestRunner::CreateCustom("202.html", false)); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP custom request with no Content-Length |
| // header. |
| TEST(ServerTest, HttpSingleCustomNoContentLength) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner( |
| StaticHttpRequestRunner::CreateCustom("202.html", true, false)); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of a single HTTP custom request with no content and no |
| // Content-Length header. |
| TEST(ServerTest, HttpSingleCustomNoContentAndNoLength) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner( |
| StaticHttpRequestRunner::CreateCustom("202.html", false, false)); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of multiple HTTP requests in parallel. |
| TEST(ServerTest, HttpMultipleParallel200) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(true); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200a.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200b.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200c.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of multiple HTTP requests in serial. |
| TEST(ServerTest, HttpMultipleSerial200) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200a.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200b.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200c.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of multiple HTTP requests in parallel. |
| TEST(ServerTest, HttpMultipleParallelMixed) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(true); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create404("404.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create500("500.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::CreateCustom("202.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| // Verify handling of multiple HTTP requests in serial. |
| TEST(ServerTest, HttpMultipleSerialMixed) { |
| CefRefPtr<HttpTestRunner> runner = new HttpTestRunner(false); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create200("200.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create404("404.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::Create500("500.html")); |
| runner->AddRequestRunner(StaticHttpRequestRunner::CreateCustom("202.html")); |
| runner->ExecuteTest(); |
| ReleaseAndWaitForDestructor(runner); |
| } |
| |
| namespace { |
| |
| // WEBSOCKET TESTS |
| |
| const char kWebSocketUrl[] = "http://tests-display/websocket.html"; |
| const char kDoneMsgPrefix[] = "done:"; |
| |
| class WebSocketTestHandler : public RoutingTestHandler { |
| public: |
| WebSocketTestHandler() {} |
| |
| void RunTest() override { |
| handler_ = new TestServerHandler( |
| base::Bind(&WebSocketTestHandler::OnServerStarted, this), |
| base::Bind(&WebSocketTestHandler::OnServerDestroyed, this)); |
| OnHandlerCreated(handler_); |
| |
| handler_->CreateServer(); |
| |
| // Time out the test after a reasonable period of time. |
| SetTestTimeout(); |
| } |
| |
| bool OnQuery(CefRefPtr<CefBrowser> browser, |
| CefRefPtr<CefFrame> frame, |
| int64 query_id, |
| const CefString& request, |
| bool persistent, |
| CefRefPtr<Callback> callback) override { |
| const std::string& request_str = request.ToString(); |
| if (request_str.find(kDoneMsgPrefix) == 0) { |
| EXPECT_FALSE(got_done_message_); |
| got_done_message_.yes(); |
| OnDoneMessage(request_str.substr(strlen(kDoneMsgPrefix))); |
| DestroyTestIfDone(); |
| return true; |
| } |
| return false; |
| } |
| |
| void DestroyTest() override { |
| EXPECT_TRUE(got_server_started_); |
| EXPECT_TRUE(got_done_message_); |
| EXPECT_TRUE(got_server_destroyed_); |
| |
| TestHandler::DestroyTest(); |
| } |
| |
| protected: |
| // Returns the HTML/JS for the client. |
| virtual std::string GetClientHtml() = 0; |
| |
| // Called after the server handler is created to set test expectations. |
| virtual void OnHandlerCreated(CefRefPtr<TestServerHandler> handler) = 0; |
| |
| // Returns the JS to execute when the test is done. |
| std::string GetDoneJS(const std::string& result) { |
| return "window.testQuery({request:'" + std::string(kDoneMsgPrefix) + |
| "' + " + result + "});"; |
| } |
| |
| // Called with the result from the done message. |
| virtual void OnDoneMessage(const std::string& result) = 0; |
| |
| void ShutdownServer() { |
| EXPECT_TRUE(handler_); |
| handler_->ShutdownServer(); |
| handler_ = nullptr; |
| } |
| |
| private: |
| void OnServerStarted() { |
| EXPECT_UI_THREAD(); |
| EXPECT_FALSE(got_server_started_); |
| got_server_started_.yes(); |
| |
| // Add the WebSocket client code. |
| AddResource(kWebSocketUrl, GetClientHtml(), "text/html"); |
| |
| // Create the browser. |
| CreateBrowser(kWebSocketUrl); |
| } |
| |
| void OnServerDestroyed() { |
| EXPECT_UI_THREAD(); |
| EXPECT_FALSE(got_server_destroyed_); |
| got_server_destroyed_.yes(); |
| DestroyTestIfDone(); |
| } |
| |
| void DestroyTestIfDone() { |
| if (got_server_destroyed_ && got_done_message_) { |
| // Allow the call stack to unwind. |
| CefPostTask(TID_UI, base::Bind(&WebSocketTestHandler::DestroyTest, this)); |
| } |
| } |
| |
| CefRefPtr<TestServerHandler> handler_; |
| |
| TrackCallback got_server_started_; |
| TrackCallback got_done_message_; |
| TrackCallback got_server_destroyed_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WebSocketTestHandler); |
| }; |
| |
| // WebSocket request handler that echoes each message sent. |
| class EchoWebSocketRequestHandler : public TestServerHandler::WsRequestHandler { |
| public: |
| explicit EchoWebSocketRequestHandler(int expected_message_ct) |
| : expected_message_ct_(expected_message_ct), actual_message_ct_(0) {} |
| |
| std::string GetWebSocketUrl() { return GetTestServerOrigin(true) + "echo"; } |
| |
| bool HandleRequest(CefRefPtr<CefServer> server, |
| int connection_id, |
| const CefString& client_address, |
| CefRefPtr<CefRequest> request, |
| CefRefPtr<CefCallback> callback) override { |
| EXPECT_STREQ(GetWebSocketUrl().c_str(), |
| request->GetURL().ToString().c_str()); |
| |
| callback->Continue(); |
| return true; |
| } |
| |
| bool HandleConnected(CefRefPtr<CefServer> server, |
| int connection_id) override { |
| return true; |
| } |
| |
| bool HandleMessage(CefRefPtr<CefServer> server, |
| int connection_id, |
| const void* data, |
| size_t data_size) override { |
| actual_message_ct_++; |
| |
| // Echo the message back to the sender. |
| server->SendWebSocketMessage(connection_id, data, data_size); |
| |
| return true; |
| } |
| |
| bool VerifyResults() override { |
| EXPECT_EQ(expected_message_ct_, actual_message_ct_); |
| return expected_message_ct_ == actual_message_ct_; |
| } |
| |
| std::string ToString() override { return "EchoRequestHandler"; } |
| |
| private: |
| int expected_message_ct_; |
| int actual_message_ct_; |
| |
| DISALLOW_COPY_AND_ASSIGN(EchoWebSocketRequestHandler); |
| }; |
| |
| class EchoWebSocketTestHandler : public WebSocketTestHandler { |
| public: |
| // Create |connection_ct| connections and send |message_ct| messages to each |
| // connection. If |in_parallel| is true the connections will be created in |
| // parallel. |
| EchoWebSocketTestHandler(int connection_ct, int message_ct, bool in_parallel) |
| : connection_ct_(connection_ct), |
| message_ct_(message_ct), |
| in_parallel_(in_parallel) {} |
| |
| std::string GetClientHtml() override { |
| std::stringstream ss; |
| ss << connection_ct_; |
| std::string cct_str = ss.str(); |
| ss.str(""); |
| ss << message_ct_; |
| std::string mct_str = ss.str(); |
| |
| // clang-format off |
| return |
| "<html><body><script>\n" |
| |
| // Input variables. |
| "var url = '" + ws_url_ +"';\n" |
| "var expected_connection_ct = " + cct_str + ";\n" |
| "var expected_message_ct = " + mct_str + ";\n" |
| "var in_parallel = " + (in_parallel_ ? "true" : "false") + ";\n" |
| "var complete_callback = function() { " + |
| GetDoneJS("complete_message_ct") + " }\n" |
| |
| // Result variables. |
| "var complete_connection_ct = 0;\n" |
| "var complete_message_ct = 0;\n" |
| |
| // Send the next message on the connection asynchronously, or close the |
| // connection if all messages have been sent. |
| "function sendNextMessage(ws, connection_id, message_id) {\n" |
| " if (message_id < expected_message_ct) {\n" |
| " setTimeout(function() {\n" |
| " ws.send('message:' + connection_id + ':' + message_id);\n" |
| " }, 1);\n" |
| " } else {\n" |
| " ws.close();\n" |
| " }\n" |
| "}\n" |
| |
| // Handle a received message. |
| "function onMessage(ws, connection_id, data) {\n" |
| " var parts = data.split(':');\n" |
| " if (parts.length == 3 && parts[0] == 'message') {\n" |
| " var cid = parseInt(parts[1]);\n" |
| " var mid = parseInt(parts[2]);\n" |
| " if (cid == connection_id) {\n" |
| " complete_message_ct++;\n" |
| " sendNextMessage(ws, connection_id, mid + 1);\n" |
| " } else {\n" |
| " console.log('Connection id mismatch; expected ' +\n" |
| " connection_id + ', actual ' + cid);\n" |
| " }\n" |
| " } else {\n" |
| " console.log('Unexpected message format: ' + data);\n" |
| " }\n" |
| "}\n" |
| |
| // Handle socket closed. If all messages have been sent on all |
| // connections then complete the test. |
| "function onClose(ws) {\n" |
| " if (++complete_connection_ct == expected_connection_ct) {\n" |
| " complete_callback();\n" |
| " } else if (!in_parallel) {\n" |
| " startConnection(complete_connection_ct);\n" |
| " }\n" |
| "}\n" |
| |
| // Start a new WebSocket connection. |
| "function startConnection(connection_id) {\n" |
| " var ws = new WebSocket(url);\n" |
| " ws.onopen = function() {\n" |
| " sendNextMessage(ws, connection_id, 0);\n" |
| " };\n" |
| " ws.onmessage = function(event) {\n" |
| " onMessage(ws, connection_id, event.data);\n" |
| " };\n" |
| " ws.onclose = function() { onClose(ws); };\n" |
| "}\n" |
| |
| // JS entry point. |
| "if (in_parallel) {\n" |
| " for (var i = 0; i < expected_connection_ct; ++i) {\n" |
| " startConnection(i);\n" |
| " }\n" |
| "} else {\n" |
| " startConnection(0);\n" |
| "}\n" |
| |
| "</script>WebSocket Test</body></html>"; |
| // clang-format on |
| } |
| |
| void OnHandlerCreated(CefRefPtr<TestServerHandler> handler) override { |
| handler->SetExpectedConnectionCount(connection_ct_); |
| handler->SetExpectedWsRequestCount(connection_ct_); |
| handler->SetExpectedWsConnectedCount(connection_ct_); |
| handler->SetExpectedWsMessageCount(connection_ct_ * message_ct_); |
| |
| EchoWebSocketRequestHandler* echo_handler = |
| new EchoWebSocketRequestHandler(connection_ct_ * message_ct_); |
| ws_url_ = echo_handler->GetWebSocketUrl(); |
| handler->AddWsRequestHandler( |
| make_scoped_ptr<TestServerHandler::WsRequestHandler>(echo_handler)); |
| } |
| |
| void OnDoneMessage(const std::string& result) override { |
| const int complete_message_ct = atoi(result.c_str()); |
| EXPECT_EQ(connection_ct_ * message_ct_, complete_message_ct); |
| ShutdownServer(); |
| } |
| |
| private: |
| int connection_ct_; |
| int message_ct_; |
| bool in_parallel_; |
| std::string ws_url_; |
| |
| IMPLEMENT_REFCOUNTING(EchoWebSocketTestHandler); |
| DISALLOW_COPY_AND_ASSIGN(EchoWebSocketTestHandler); |
| }; |
| |
| } // namespace |
| |
| // Test handling of a single connection with a single message. |
| TEST(ServerTest, WebSocketSingleConnectionSingleMessage) { |
| CefRefPtr<EchoWebSocketTestHandler> handler = |
| new EchoWebSocketTestHandler(1, 1, true); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| // Test handling of a single connection with multiple messages. |
| TEST(ServerTest, WebSocketSingleConnectionMultipleMessages) { |
| CefRefPtr<EchoWebSocketTestHandler> handler = |
| new EchoWebSocketTestHandler(1, 5, true); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| // Test handling of multiple connections and multiple messages in parallel. |
| TEST(ServerTest, WebSocketMultipleConnectionsMultipleMessagesInParallel) { |
| CefRefPtr<EchoWebSocketTestHandler> handler = |
| new EchoWebSocketTestHandler(4, 6, true); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| // Test handling of multiple connections and multiple messages in serial. |
| TEST(ServerTest, WebSocketMultipleConnectionsMultipleMessagesInSerial) { |
| CefRefPtr<EchoWebSocketTestHandler> handler = |
| new EchoWebSocketTestHandler(4, 6, false); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |