Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Unified Diff: extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc

Issue 494573002: A change for the setPause() api in chrome.sockets.tcp: Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Cosmetics and commentary. Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698