| Index: extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc
|
| diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc b/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc
|
| index d6db1620c23a8256e26d319180e75b40f42be76d..420f32e47cbbbe33e7e900c471f9eebc8f5838fd 100644
|
| --- a/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc
|
| +++ b/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc
|
| @@ -2,31 +2,236 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| +#include <sched.h>
|
| +#include <unistd.h>
|
| +
|
| #include "base/memory/ref_counted.h"
|
| +#include "base/run_loop.h"
|
| #include "base/strings/stringprintf.h"
|
| +#include "base/synchronization/lock.h"
|
| #include "extensions/browser/api/dns/host_resolver_wrapper.h"
|
| #include "extensions/browser/api/dns/mock_host_resolver_creator.h"
|
| #include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h"
|
| #include "extensions/browser/api_test_utils.h"
|
| +#include "extensions/browser/event_router.h"
|
| +#include "extensions/browser/event_router_factory.h"
|
| +#include "extensions/browser/extension_function_dispatcher.h"
|
| +#include "extensions/browser/extension_prefs.h"
|
| +#include "extensions/common/api/sockets/sockets_manifest_data.h"
|
| #include "extensions/common/extension.h"
|
| +#include "extensions/common/extension_builder.h"
|
| +#include "extensions/common/manifest_constants.h"
|
| #include "extensions/common/test_util.h"
|
| +#include "extensions/common/value_builder.h"
|
| #include "extensions/shell/test/shell_apitest.h"
|
| #include "extensions/shell/test/shell_test.h"
|
| #include "extensions/test/extension_test_message_listener.h"
|
| #include "extensions/test/result_catcher.h"
|
| +#include "net/base/io_buffer.h"
|
| +#include "net/base/net_errors.h"
|
| #include "net/dns/mock_host_resolver.h"
|
| +#include "net/socket/tcp_server_socket.h"
|
| #include "net/test/spawned_test_server/spawned_test_server.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
|
|
| namespace extensions {
|
| +using ::testing::_;
|
|
|
| const std::string kHostname = "127.0.0.1";
|
|
|
| +// Runs a single-connection TCP echo server on a separate thread.
|
| +class LocalServer : public base::RefCountedThreadSafe<LocalServer> {
|
| + public:
|
| + LocalServer()
|
| + : echo_socket_(nullptr, net::NetLog::Source()),
|
| + thread_("localserver"),
|
| + active_(false),
|
| + issued_write_count_(0),
|
| + finished_write_count_(0),
|
| + port_(-1),
|
| + sock_(nullptr),
|
| + state_cvar_(&state_lock_) {}
|
| +
|
| + void Start() {
|
| + // Should run in an IO thread (or at least one with a MessageLoopForIO)
|
| + base::Thread::Options thread_opts(base::MessageLoop::TYPE_IO, 0);
|
| + thread_.StartWithOptions(thread_opts);
|
| + thread_.WaitUntilThreadStarted();
|
| + thread_.message_loop()->PostTask(
|
| + FROM_HERE, base::Bind(&LocalServer::IOStart, base::Unretained(this)));
|
| + }
|
| +
|
| + void Close() {
|
| + thread_.message_loop()->PostTask(FROM_HERE,
|
| + base::Bind(&LocalServer::DoClose, this));
|
| + }
|
| +
|
| + // This is -1 until IOStart() has run on this LocalServer's IO thread
|
| + int port() {
|
| + int ret = -1;
|
| + do {
|
| + {
|
| + base::AutoLock l(state_lock_);
|
| + ret = port_;
|
| + }
|
| + if (port_ < 0) {
|
| + LOG(INFO) << "port(): waiting for thread to finish doing whatever it's "
|
| + "doing.";
|
| + usleep(25000);
|
| + }
|
| + } while (ret < 0);
|
| + return ret;
|
| + }
|
| +
|
| + // Whether Accept() completed, making sock_ valid.
|
| + bool accepted() {
|
| + base::AutoLock l(state_lock_);
|
| + return active_;
|
| + }
|
| +
|
| + // Returns the total number of issued writes. If this call returns N, then
|
| + // you can verify that the underlying write finished with
|
| + // wait_for_n_finished_writes(N).
|
| + int Write(const char* message) { return Write(std::string(message)); }
|
| +
|
| + int Write(const std::string& message) {
|
| + thread_.message_loop()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&LocalServer::DoWrite, base::Unretained(this), message));
|
| + return ++issued_write_count_;
|
| + }
|
| +
|
| + int issued_write_count() {
|
| + base::AutoLock l(state_lock_);
|
| + return issued_write_count_;
|
| + }
|
| +
|
| + int finished_write_count() {
|
| + base::AutoLock l(state_lock_);
|
| + return finished_write_count_;
|
| + }
|
| +
|
| + void WaitForNFinishedWrites(int nr) {
|
| + base::AutoLock l(state_lock_);
|
| + while (finished_write_count_ < nr) {
|
| + state_cvar_.Wait();
|
| + }
|
| + }
|
| +
|
| + private:
|
| + net::TCPServerSocket echo_socket_;
|
| + base::Thread thread_; // Our IO thread.
|
| + bool active_;
|
| + int issued_write_count_; // How many calls to Write() have been made?
|
| + int finished_write_count_; // How many calls to FinishWrite() have been made?
|
| + int port_;
|
| + scoped_ptr<net::StreamSocket> sock_; // not for use on the primary thread!
|
| + base::Lock state_lock_;
|
| + base::ConditionVariable state_cvar_;
|
| +
|
| + friend class base::RefCountedThreadSafe<LocalServer>;
|
| + virtual ~LocalServer() {
|
| + bool active;
|
| + {
|
| + base::AutoLock l(state_lock_);
|
| + active = active_;
|
| + }
|
| + DCHECK(!active);
|
| + }
|
| +
|
| + void FinishWrite(int result) {
|
| + base::AutoLock l(state_lock_);
|
| + ASSERT_GT(result, 0);
|
| + LOG(INFO) << "FinishWrite(result=" << result << ")";
|
| + finished_write_count_++;
|
| + state_cvar_.Broadcast();
|
| + }
|
| +
|
| + void DoWrite(const std::string& message) {
|
| + {
|
| + base::AutoLock l(state_lock_);
|
| + DCHECK(active_);
|
| + issued_write_count_++;
|
| + }
|
| + scoped_refptr<net::StringIOBuffer> buf(new net::StringIOBuffer(message));
|
| + LOG(INFO) << "Write(" << message << ")";
|
| + int result = sock_->Write(
|
| + buf.get(), buf->size(),
|
| + base::Bind(&LocalServer::FinishWrite, base::Unretained(this)));
|
| + if (result != net::ERR_IO_PENDING) {
|
| + FinishWrite(result);
|
| + }
|
| + }
|
| +
|
| + void IOStart() {
|
| + base::AutoLock l(state_lock_);
|
| + net::IPAddressNumber localhost;
|
| + net::IPEndPoint local;
|
| + net::ParseIPLiteralToNumber("127.0.0.1", &localhost);
|
| + // setup echo_socket_.
|
| + echo_socket_.Listen(net::IPEndPoint(localhost, 0), 5);
|
| + echo_socket_.GetLocalAddress(&local);
|
| + port_ = local.port();
|
| + echo_socket_.Accept(
|
| + &sock_, base::Bind(&LocalServer::AcceptedSock, base::Unretained(this)));
|
| + }
|
| +
|
| + void AcceptedSock(int result) {
|
| + base::AutoLock l(state_lock_);
|
| + ASSERT_EQ(net::OK, result);
|
| + active_ = true;
|
| + }
|
| +
|
| + void DoClose() {
|
| + base::AutoLock l(state_lock_);
|
| + // Repeatedly calling this is harmless.
|
| + sock_->Disconnect();
|
| + active_ = false;
|
| + }
|
| +};
|
| +
|
| +class MockEventRouter : public EventRouter {
|
| + public:
|
| + MockEventRouter(content::BrowserContext* ctx, ExtensionPrefs* prefs)
|
| + : EventRouter(ctx, prefs), do_dispatch_(true), call_count_(0) {}
|
| +
|
| + MOCK_METHOD3(DoDispatchEventToExtension,
|
| + void(const std::string&, const std::string&, Event*));
|
| +
|
| + void set_dispatch(bool v) { do_dispatch_ = v; }
|
| + int call_count() { return call_count_; }
|
| +
|
| + // Trampoline method to work around GMOCK/scoped_ptr problems.
|
| + void DispatchEventToExtension(const std::string& str,
|
| + scoped_ptr<Event> evt) override {
|
| + LOG(INFO) << "MockEventRouter: Got event: " << evt->event_name;
|
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
| + call_count_++;
|
| + DoDispatchEventToExtension(str, evt->event_name, evt.get());
|
| + if (do_dispatch_) {
|
| + EventRouter::DispatchEventToExtension(str,
|
| + scoped_ptr<Event>(evt.release()));
|
| + }
|
| + }
|
| +
|
| + private:
|
| + bool do_dispatch_;
|
| + int call_count_;
|
| +};
|
| +
|
| class SocketsTcpApiTest : public ShellApiTest {
|
| public:
|
| SocketsTcpApiTest()
|
| : resolver_event_(true, false),
|
| resolver_creator_(new MockHostResolverCreator()) {}
|
|
|
| + void EnableMockRouter() {
|
| + // Setup a Mock EventRouter to catch output from the
|
| + // TCPSocketEventDispatcher
|
| + EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
|
| + browser_context(), &BuildMockRouter);
|
| + }
|
| +
|
| void SetUpOnMainThread() override {
|
| ShellApiTest::SetUpOnMainThread();
|
|
|
| @@ -35,14 +240,71 @@ class SocketsTcpApiTest : public ShellApiTest {
|
| }
|
|
|
| void TearDownOnMainThread() override {
|
| + LOG(INFO) << "TearDown starting";
|
| HostResolverWrapper::GetInstance()->SetHostResolverForTesting(NULL);
|
| resolver_creator_->DeleteMockHostResolver();
|
|
|
| + LOG(INFO) << "super TearDown starting";
|
| ShellApiTest::TearDownOnMainThread();
|
| + LOG(INFO) << "TearDown done";
|
| + }
|
| +
|
| + protected:
|
| + // Makes the extension if we haven't built it yet.
|
| + scoped_refptr<Extension> extension() {
|
| + if (!extension_.get()) {
|
| + DictionaryBuilder app;
|
| + app.Set("background",
|
| + DictionaryBuilder().Set("scripts",
|
| + ListBuilder().Append("background.J's")));
|
| + manifest_builder_.Set("name", "Test")
|
| + .Set("version", "1.0")
|
| + .Set(manifest_keys::kApp, app);
|
| + extension_ = ExtensionBuilder().SetManifest(manifest_builder_).Build();
|
| + }
|
| + return extension_;
|
| + }
|
| +
|
| + static scoped_ptr<KeyedService> BuildMockRouter(
|
| + content::BrowserContext* ctx) {
|
| + return scoped_ptr<KeyedService>(
|
| + new MockEventRouter(ctx, ExtensionPrefs::Get(ctx)));
|
| + }
|
| +
|
| + MockEventRouter& mock_router() {
|
| + return *static_cast<MockEventRouter*>(EventRouter::Get(browser_context()));
|
| + }
|
| +
|
| + template <class F>
|
| + scoped_ptr<base::Value> CallFunction(std::string args) {
|
| + scoped_refptr<F> function = new F();
|
| + function->set_extension(extension());
|
| + return scoped_ptr<base::Value>(
|
| + api_test_utils::RunFunctionAndReturnSingleResult(function.get(), args,
|
| + browser_context()));
|
| + }
|
| +
|
| + // Adds parts to the currently-building manifest. Call |extension()| to
|
| + // create the extension with the manifest.
|
| + void SetManifestPermissions(const std::string& perms) {
|
| + base::string16 error;
|
| + scoped_ptr<base::DictionaryValue> permissionsDict =
|
| + api_test_utils::ParseDictionary(perms);
|
| + // Validate the permissions.
|
| + DCHECK(extension_.get() == nullptr);
|
| + if (SocketsManifestData::FromValue(*permissionsDict.get(), &error).get()) {
|
| + DictionaryBuilder dbuilder(*permissionsDict.get());
|
| + manifest_builder_.Set(manifest_keys::kSockets, dbuilder);
|
| + } else {
|
| + LOG(ERROR) << "Failed to setup socket manifest permissions: " << error
|
| + << " for value: " << perms;
|
| + }
|
| }
|
|
|
| private:
|
| base::WaitableEvent resolver_event_;
|
| + DictionaryBuilder manifest_builder_;
|
| + scoped_refptr<Extension> extension_;
|
|
|
| // The MockHostResolver asserts that it's used on the same thread on which
|
| // it's created, which is actually a stronger rule than its real counterpart.
|
| @@ -50,24 +312,16 @@ class SocketsTcpApiTest : public ShellApiTest {
|
| scoped_refptr<MockHostResolverCreator> resolver_creator_;
|
| };
|
|
|
| -IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketsTcpCreateGood) {
|
| - scoped_refptr<api::SocketsTcpCreateFunction> socket_create_function(
|
| - new api::SocketsTcpCreateFunction());
|
| - scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
|
| -
|
| - socket_create_function->set_extension(empty_extension.get());
|
| - socket_create_function->set_has_callback(true);
|
| -
|
| +IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketsTcpCreateIsGood) {
|
| scoped_ptr<base::Value> result(
|
| - api_test_utils::RunFunctionAndReturnSingleResult(
|
| - socket_create_function.get(), "[]", browser_context()));
|
| + CallFunction<api::SocketsTcpCreateFunction>("[]"));
|
|
|
| ASSERT_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
|
| base::DictionaryValue* value =
|
| static_cast<base::DictionaryValue*>(result.get());
|
| int socketId = -1;
|
| EXPECT_TRUE(value->GetInteger("socketId", &socketId));
|
| - ASSERT_TRUE(socketId > 0);
|
| + ASSERT_GT(socketId, 0);
|
| }
|
|
|
| IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketTcpExtension) {
|
| @@ -78,7 +332,7 @@ IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketTcpExtension) {
|
|
|
| net::HostPortPair host_port_pair = test_server->host_port_pair();
|
| int port = host_port_pair.port();
|
| - ASSERT_TRUE(port > 0);
|
| + ASSERT_GT(port, 0);
|
|
|
| // Test that connect() is properly resolving hostnames.
|
| host_port_pair.set_host("lOcAlHoSt");
|
| @@ -120,4 +374,74 @@ IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketTcpExtensionTLS) {
|
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
|
| }
|
|
|
| +IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketsTcpPauseIsGood) {
|
| + EnableMockRouter();
|
| +
|
| + scoped_refptr<LocalServer> server(new LocalServer);
|
| + server->Start();
|
| + int port = server->port();
|
| +
|
| + LOG(INFO) << "Port " << port;
|
| + SetManifestPermissions(base::StringPrintf(
|
| + "{ \"tcp\": { \"connect\": \"localhost:%d\" } }", port));
|
| +
|
| + scoped_ptr<base::Value> create(
|
| + CallFunction<api::SocketsTcpCreateFunction>("[{\"persistent\":true}]"));
|
| + int sockfd;
|
| + base::DictionaryValue* create_val =
|
| + static_cast<base::DictionaryValue*>(create.get());
|
| + EXPECT_TRUE(create_val->GetInteger("socketId", &sockfd));
|
| + ASSERT_GT(sockfd, 0);
|
| +
|
| + scoped_ptr<base::Value> conn_result =
|
| + CallFunction<api::SocketsTcpConnectFunction>(
|
| + base::StringPrintf("[%d, \"localhost\", %d]", sockfd, port));
|
| + LOG(INFO) << "Finished running connect()";
|
| +
|
| + // Require that we receive two OnReceive events, with a "MARK" event in the
|
| + // middle. So we can make sure that we don't receive an OnReceive while
|
| + // paused.
|
| + {
|
| + ::testing::InSequence seq;
|
| + EXPECT_CALL(mock_router(),
|
| + DoDispatchEventToExtension(_, "sockets.tcp.onReceive", _))
|
| + .Times(1);
|
| + EXPECT_CALL(mock_router(), DoDispatchEventToExtension(_, "MARK", _))
|
| + .Times(1);
|
| + EXPECT_CALL(mock_router(),
|
| + DoDispatchEventToExtension(_, "sockets.tcp.onReceive", _))
|
| + .Times(1)
|
| + .RetiresOnSaturation();
|
| + }
|
| +
|
| + // Write the first message, and wait for it to have been issued.
|
| + server->Write("first message\n");
|
| + server->WaitForNFinishedWrites(1);
|
| + // Make sure that the onReceive comes before the MARK gets sent over.
|
| + while (mock_router().call_count() < 1) {
|
| + LOG(INFO) << "RunLoop call {";
|
| + base::RunLoop().RunUntilIdle();
|
| + LOG(INFO) << "} RunLoop call finished.";
|
| + }
|
| +
|
| + CallFunction<api::SocketsTcpSetPausedFunction>(
|
| + base::StringPrintf("[%d, true]", sockfd));
|
| +
|
| + server->Write("second message\n");
|
| + server->WaitForNFinishedWrites(2);
|
| + // This will result in a message before the Write() above, as it's paused.
|
| + mock_router().DoDispatchEventToExtension("", "MARK", nullptr);
|
| +
|
| + // Let the Write() get delivered.
|
| + CallFunction<api::SocketsTcpSetPausedFunction>(
|
| + base::StringPrintf("[%d, false]", sockfd));
|
| +
|
| + while (mock_router().call_count() < 2) {
|
| + // Let event queues drain.
|
| + base::RunLoop().RunUntilIdle();
|
| + }
|
| +
|
| + server->Close();
|
| +}
|
| +
|
| } // namespace extensions
|
|
|