Skip to content

Commit

Permalink
Make EmbeddedTestServer usable in the absence of pre-existing threads.
Browse files Browse the repository at this point in the history
This changes EmbeddedTestServer to work in cases where the thread it
gets initialized/destroyed on doesn't have a message loop. Also, the
constructor now allows to not pass an I/O thread task runner, in which
case the test server will create a dedicated message loop for servicing
requests.

BUG=None
TEST=unit tests.

Review URL: https://chromiumcodereview.appspot.com/23205005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221028 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
mnissler@chromium.org committed Sep 3, 2013
1 parent fe1f42e commit d6368b8
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 26 deletions.
16 changes: 12 additions & 4 deletions content/public/test/browser_test_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/stack_trace.h"
#include "base/message_loop/message_loop.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
Expand Down Expand Up @@ -69,9 +70,7 @@ void RunTaskOnRendererThread(const base::Closure& task,
extern int BrowserMain(const MainFunctionParams&);

BrowserTestBase::BrowserTestBase()
: embedded_test_server_(
new net::test_server::EmbeddedTestServer(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))),
: embedded_test_server_io_thread_("EmbeddedTestServer io thread"),
allow_test_contexts_(true),
allow_osmesa_(true) {
#if defined(OS_MACOSX)
Expand All @@ -82,6 +81,16 @@ BrowserTestBase::BrowserTestBase()
#if defined(OS_POSIX)
handle_sigterm_ = true;
#endif

// Create a separate thread for the test server to run on. It's tempting to
// use actual browser threads, but that doesn't work for cases where the test
// server needs to be started before the browser, for example when the server
// URL should be passed in command-line parameters.
base::Thread::Options thread_options;
thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
CHECK(embedded_test_server_io_thread_.StartWithOptions(thread_options));
embedded_test_server_.reset(new net::test_server::EmbeddedTestServer(
embedded_test_server_io_thread_.message_loop_proxy()));
}

BrowserTestBase::~BrowserTestBase() {
Expand Down Expand Up @@ -179,7 +188,6 @@ void BrowserTestBase::ProxyRunTestOnMainThreadLoop() {
}
#endif // defined(OS_POSIX)
RunTestOnMainThreadLoop();
embedded_test_server_.reset();
}

void BrowserTestBase::CreateTestServer(const base::FilePath& test_server_base) {
Expand Down
2 changes: 2 additions & 0 deletions content/public/test/browser_test_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/threading/thread.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"

Expand Down Expand Up @@ -124,6 +125,7 @@ class BrowserTestBase : public testing::Test {
scoped_ptr<net::SpawnedTestServer> test_server_;

// Embedded test server, cheap to create, started on demand.
base::Thread embedded_test_server_io_thread_;
scoped_ptr<net::test_server::EmbeddedTestServer> embedded_test_server_;

// When false, the ui::Compositor will be forced to use real GL contexts for
Expand Down
47 changes: 28 additions & 19 deletions net/test/embedded_test_server/embedded_test_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
#include "net/test/embedded_test_server/embedded_test_server.h"

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
Expand Down Expand Up @@ -118,33 +119,19 @@ EmbeddedTestServer::~EmbeddedTestServer() {
bool EmbeddedTestServer::InitializeAndWaitUntilReady() {
DCHECK(thread_checker_.CalledOnValidThread());

base::RunLoop run_loop;
if (!io_thread_->PostTaskAndReply(
FROM_HERE,
base::Bind(&EmbeddedTestServer::InitializeOnIOThread,
base::Unretained(this)),
run_loop.QuitClosure())) {
if (!PostTaskToIOThreadAndWait(base::Bind(
&EmbeddedTestServer::InitializeOnIOThread, base::Unretained(this)))) {
return false;
}
run_loop.Run();

return Started() && base_url_.is_valid();
}

bool EmbeddedTestServer::ShutdownAndWaitUntilComplete() {
DCHECK(thread_checker_.CalledOnValidThread());

base::RunLoop run_loop;
if (!io_thread_->PostTaskAndReply(
FROM_HERE,
base::Bind(&EmbeddedTestServer::ShutdownOnIOThread,
base::Unretained(this)),
run_loop.QuitClosure())) {
return false;
}
run_loop.Run();

return true;
return PostTaskToIOThreadAndWait(base::Bind(
&EmbeddedTestServer::ShutdownOnIOThread, base::Unretained(this)));
}

void EmbeddedTestServer::InitializeOnIOThread() {
Expand Down Expand Up @@ -274,5 +261,27 @@ HttpConnection* EmbeddedTestServer::FindConnection(
return it->second;
}

bool EmbeddedTestServer::PostTaskToIOThreadAndWait(
const base::Closure& closure) {
// Note that PostTaskAndReply below requires base::MessageLoopProxy::current()
// to return a loop for posting the reply task. However, in order to make
// EmbeddedTestServer universally usable, it needs to cope with the situation
// where it's running on a thread on which a message loop is not (yet)
// available or as has been destroyed already.
//
// To handle this situation, create temporary message loop to support the
// PostTaskAndReply operation if the current thread as no message loop.
scoped_ptr<base::MessageLoop> temporary_loop;
if (!base::MessageLoop::current())
temporary_loop.reset(new base::MessageLoop());

base::RunLoop run_loop;
if (!io_thread_->PostTaskAndReply(FROM_HERE, closure, run_loop.QuitClosure()))
return false;
run_loop.Run();

return true;
}

} // namespace test_server
} // namespace net
7 changes: 6 additions & 1 deletion net/test/embedded_test_server/embedded_test_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <vector>

#include "base/basictypes.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
Expand Down Expand Up @@ -53,7 +54,7 @@ class HttpListenSocket : public TCPListenSocket {
//
// void SetUp() {
// base::Thread::Options thread_options;
// thread_options.message_loop_type = MessageLoop::TYPE_IO;
// thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
// ASSERT_TRUE(io_thread_.StartWithOptions(thread_options));
//
// test_server_.reset(
Expand Down Expand Up @@ -145,6 +146,10 @@ class EmbeddedTestServer : public StreamListenSocket::Delegate {

HttpConnection* FindConnection(StreamListenSocket* socket);

// Posts a task to the |io_thread_| and waits for a reply.
bool PostTaskToIOThreadAndWait(
const base::Closure& closure) WARN_UNUSED_RESULT;

scoped_refptr<base::SingleThreadTaskRunner> io_thread_;

scoped_ptr<HttpListenSocket> listen_socket_;
Expand Down
89 changes: 87 additions & 2 deletions net/test/embedded_test_server/embedded_test_server_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ std::string GetContentTypeFromFetcher(const URLFetcher& fetcher) {

} // namespace

class EmbeddedTestServerTest : public testing::Test,
public URLFetcherDelegate {
class EmbeddedTestServerTest: public testing::Test,
public URLFetcherDelegate {
public:
EmbeddedTestServerTest()
: num_responses_received_(0),
Expand Down Expand Up @@ -240,5 +240,90 @@ TEST_F(EmbeddedTestServerTest, ConcurrentFetches) {
EXPECT_EQ("text/plain", GetContentTypeFromFetcher(*fetcher3));
}

// Below test exercises EmbeddedTestServer's ability to cope with the situation
// where there is no MessageLoop available on the thread at EmbeddedTestServer
// initialization and/or destruction.

typedef std::tr1::tuple<bool, bool> ThreadingTestParams;

class EmbeddedTestServerThreadingTest
: public testing::TestWithParam<ThreadingTestParams> {};

class EmbeddedTestServerThreadingTestDelegate
: public base::PlatformThread::Delegate,
public URLFetcherDelegate {
public:
EmbeddedTestServerThreadingTestDelegate(
bool message_loop_present_on_initialize,
bool message_loop_present_on_shutdown)
: message_loop_present_on_initialize_(message_loop_present_on_initialize),
message_loop_present_on_shutdown_(message_loop_present_on_shutdown) {}

// base::PlatformThread::Delegate:
virtual void ThreadMain() OVERRIDE {
scoped_refptr<base::SingleThreadTaskRunner> io_thread_runner;
base::Thread io_thread("io_thread");
base::Thread::Options thread_options;
thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
ASSERT_TRUE(io_thread.StartWithOptions(thread_options));
io_thread_runner = io_thread.message_loop_proxy();

scoped_ptr<base::MessageLoop> loop;
if (message_loop_present_on_initialize_)
loop.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));

// Create the test server instance.
EmbeddedTestServer server(io_thread_runner);
base::FilePath src_dir;
ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
ASSERT_TRUE(server.InitializeAndWaitUntilReady());

// Make a request and wait for the reply.
if (!loop)
loop.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));

scoped_ptr<URLFetcher> fetcher(URLFetcher::Create(
server.GetURL("/test?q=foo"), URLFetcher::GET, this));
fetcher->SetRequestContext(
new TestURLRequestContextGetter(loop->message_loop_proxy()));
fetcher->Start();
loop->Run();
fetcher.reset();

// Shut down.
if (message_loop_present_on_shutdown_)
loop.reset();

ASSERT_TRUE(server.ShutdownAndWaitUntilComplete());
}

// URLFetcherDelegate override.
virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE {
base::MessageLoop::current()->Quit();
}

private:
bool message_loop_present_on_initialize_;
bool message_loop_present_on_shutdown_;

DISALLOW_COPY_AND_ASSIGN(EmbeddedTestServerThreadingTestDelegate);
};

TEST_P(EmbeddedTestServerThreadingTest, RunTest) {
// The actual test runs on a separate thread so it can screw with the presence
// of a MessageLoop - the test suite already sets up a MessageLoop for the
// main test thread.
base::PlatformThreadHandle thread_handle;
EmbeddedTestServerThreadingTestDelegate delegate(
std::tr1::get<0>(GetParam()),
std::tr1::get<1>(GetParam()));
ASSERT_TRUE(base::PlatformThread::Create(0, &delegate, &thread_handle));
base::PlatformThread::Join(thread_handle);
}

INSTANTIATE_TEST_CASE_P(EmbeddedTestServerThreadingTestInstantiation,
EmbeddedTestServerThreadingTest,
testing::Combine(testing::Bool(), testing::Bool()));

} // namespace test_server
} // namespace net

0 comments on commit d6368b8

Please sign in to comment.