ppapi_proxy_test.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ppapi/proxy/ppapi_proxy_test.h"
6
7#include <sstream>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/message_loop_proxy.h"
12#include "base/observer_list.h"
13#include "base/process_util.h"
14#include "base/run_loop.h"
15#include "ipc/ipc_sync_channel.h"
16#include "ppapi/c/pp_errors.h"
17#include "ppapi/c/private/ppb_proxy_private.h"
18#include "ppapi/proxy/ppapi_messages.h"
19#include "ppapi/proxy/ppb_message_loop_proxy.h"
20#include "ppapi/shared_impl/proxy_lock.h"
21
22namespace ppapi {
23namespace proxy {
24
25namespace {
26// HostDispatcher requires a PPB_Proxy_Private, so we always provide a fallback
27// do-nothing implementation.
28void PluginCrashed(PP_Module module) {
29  NOTREACHED();
30};
31
32PP_Instance GetInstanceForResource(PP_Resource resource) {
33  // If a test relies on this, we need to implement it.
34  NOTREACHED();
35  return 0;
36}
37
38void SetReserveInstanceIDCallback(PP_Module module,
39                                  PP_Bool (*is_seen)(PP_Module, PP_Instance)) {
40  // This function gets called in HostDispatcher's constructor.  We simply don't
41  // worry about Instance uniqueness in tests, so we can ignore the call.
42}
43
44int32_t GetURLLoaderBufferedBytes(PP_Resource url_loader) {
45  NOTREACHED();
46  return 0;
47}
48
49void AddRefModule(PP_Module module) {}
50void ReleaseModule(PP_Module module) {}
51PP_Bool IsInModuleDestructor(PP_Module module) { return PP_FALSE; }
52
53PPB_Proxy_Private ppb_proxy_private = {
54  &PluginCrashed,
55  &GetInstanceForResource,
56  &SetReserveInstanceIDCallback,
57  &GetURLLoaderBufferedBytes,
58  &AddRefModule,
59  &ReleaseModule,
60  &IsInModuleDestructor
61};
62
63// We allow multiple harnesses at a time to respond to 'GetInterface' calls.
64// We assume that only 1 harness's GetInterface function will ever support a
65// given interface name. In practice, there will either be only 1 GetInterface
66// handler (for PluginProxyTest or HostProxyTest), or there will be only 2
67// GetInterface handlers (for TwoWayTest).  In the latter case, one handler is
68// for the PluginProxyTestHarness and should only respond for PPP interfaces,
69// and the other handler is for the HostProxyTestHarness which should only
70// ever respond for PPB interfaces.
71ObserverList<ProxyTestHarnessBase> get_interface_handlers_;
72
73const void* MockGetInterface(const char* name) {
74  ObserverList<ProxyTestHarnessBase>::Iterator it =
75      get_interface_handlers_;
76  while (ProxyTestHarnessBase* observer = it.GetNext()) {
77    const void* interface = observer->GetInterface(name);
78    if (interface)
79      return interface;
80  }
81  if (strcmp(name, PPB_PROXY_PRIVATE_INTERFACE) == 0)
82    return &ppb_proxy_private;
83  return NULL;
84}
85
86void SetUpRemoteHarness(ProxyTestHarnessBase* harness,
87                        const IPC::ChannelHandle& handle,
88                        base::MessageLoopProxy* ipc_message_loop_proxy,
89                        base::WaitableEvent* shutdown_event,
90                        base::WaitableEvent* harness_set_up) {
91  harness->SetUpHarnessWithChannel(handle, ipc_message_loop_proxy,
92                                   shutdown_event, false);
93  harness_set_up->Signal();
94}
95
96void TearDownRemoteHarness(ProxyTestHarnessBase* harness,
97                           base::WaitableEvent* harness_torn_down) {
98  harness->TearDownHarness();
99  harness_torn_down->Signal();
100}
101
102void RunTaskOnRemoteHarness(const base::Closure& task,
103                            base::WaitableEvent* task_complete) {
104 task.Run();
105 task_complete->Signal();
106}
107
108}  // namespace
109
110// ProxyTestHarnessBase --------------------------------------------------------
111
112ProxyTestHarnessBase::ProxyTestHarnessBase() : pp_module_(0x98765),
113                                               pp_instance_(0x12345) {
114  get_interface_handlers_.AddObserver(this);
115}
116
117ProxyTestHarnessBase::~ProxyTestHarnessBase() {
118  get_interface_handlers_.RemoveObserver(this);
119}
120
121const void* ProxyTestHarnessBase::GetInterface(const char* name) {
122  return registered_interfaces_[name];
123}
124
125void ProxyTestHarnessBase::RegisterTestInterface(const char* name,
126                                                 const void* test_interface) {
127  registered_interfaces_[name] = test_interface;
128}
129
130bool ProxyTestHarnessBase::SupportsInterface(const char* name) {
131  sink().ClearMessages();
132
133  // IPC doesn't actually write to this when we send a message manually
134  // not actually using IPC.
135  bool unused_result = false;
136  PpapiMsg_SupportsInterface msg(name, &unused_result);
137  GetDispatcher()->OnMessageReceived(msg);
138
139  const IPC::Message* reply_msg =
140      sink().GetUniqueMessageMatching(IPC_REPLY_ID);
141  EXPECT_TRUE(reply_msg);
142  if (!reply_msg)
143    return false;
144
145  TupleTypes<PpapiMsg_SupportsInterface::ReplyParam>::ValueTuple reply_data;
146  EXPECT_TRUE(PpapiMsg_SupportsInterface::ReadReplyParam(
147      reply_msg, &reply_data));
148
149  sink().ClearMessages();
150  return reply_data.a;
151}
152
153// PluginProxyTestHarness ------------------------------------------------------
154
155PluginProxyTestHarness::PluginProxyTestHarness(
156    GlobalsConfiguration globals_config)
157    : globals_config_(globals_config) {
158}
159
160PluginProxyTestHarness::~PluginProxyTestHarness() {
161}
162
163PpapiGlobals* PluginProxyTestHarness::GetGlobals() {
164  return plugin_globals_.get();
165}
166
167Dispatcher* PluginProxyTestHarness::GetDispatcher() {
168  return plugin_dispatcher_.get();
169}
170
171void PluginProxyTestHarness::SetUpHarness() {
172  // These must be first since the dispatcher set-up uses them.
173  CreatePluginGlobals();
174
175  resource_tracker().DidCreateInstance(pp_instance());
176
177  plugin_dispatcher_.reset(new PluginDispatcher(
178      &MockGetInterface,
179      PpapiPermissions(),
180      false));
181  plugin_dispatcher_->InitWithTestSink(&sink());
182  // The plugin proxy delegate is needed for
183  // |PluginProxyDelegate::GetBrowserSender| which is used
184  // in |ResourceCreationProxy::GetConnection| to get the channel to the
185  // browser. In this case we just use the |plugin_dispatcher_| as the channel
186  // for test purposes.
187  plugin_delegate_mock_.set_browser_sender(plugin_dispatcher_.get());
188  PluginGlobals::Get()->set_plugin_proxy_delegate(&plugin_delegate_mock_);
189  plugin_dispatcher_->DidCreateInstance(pp_instance());
190}
191
192void PluginProxyTestHarness::SetUpHarnessWithChannel(
193    const IPC::ChannelHandle& channel_handle,
194    base::MessageLoopProxy* ipc_message_loop,
195    base::WaitableEvent* shutdown_event,
196    bool is_client) {
197  // These must be first since the dispatcher set-up uses them.
198  CreatePluginGlobals();
199
200  resource_tracker().DidCreateInstance(pp_instance());
201  plugin_delegate_mock_.Init(ipc_message_loop, shutdown_event);
202
203  plugin_dispatcher_.reset(new PluginDispatcher(
204      &MockGetInterface,
205      PpapiPermissions(),
206      false));
207  plugin_dispatcher_->InitPluginWithChannel(&plugin_delegate_mock_,
208                                            base::kNullProcessId,
209                                            channel_handle,
210                                            is_client);
211  plugin_delegate_mock_.set_browser_sender(plugin_dispatcher_.get());
212  PluginGlobals::Get()->set_plugin_proxy_delegate(&plugin_delegate_mock_);
213  plugin_dispatcher_->DidCreateInstance(pp_instance());
214}
215
216void PluginProxyTestHarness::TearDownHarness() {
217  plugin_dispatcher_->DidDestroyInstance(pp_instance());
218  plugin_dispatcher_.reset();
219
220  resource_tracker().DidDeleteInstance(pp_instance());
221  plugin_globals_.reset();
222}
223
224void PluginProxyTestHarness::CreatePluginGlobals() {
225  if (globals_config_ == PER_THREAD_GLOBALS) {
226    plugin_globals_.reset(new PluginGlobals(PpapiGlobals::PerThreadForTest()));
227    PpapiGlobals::SetPpapiGlobalsOnThreadForTest(GetGlobals());
228  } else {
229    plugin_globals_.reset(new PluginGlobals());
230  }
231}
232
233base::MessageLoopProxy*
234PluginProxyTestHarness::PluginDelegateMock::GetIPCMessageLoop() {
235  return ipc_message_loop_;
236}
237
238base::WaitableEvent*
239PluginProxyTestHarness::PluginDelegateMock::GetShutdownEvent() {
240  return shutdown_event_;
241}
242
243IPC::PlatformFileForTransit
244PluginProxyTestHarness::PluginDelegateMock::ShareHandleWithRemote(
245    base::PlatformFile handle,
246    base::ProcessId /* remote_pid */,
247    bool should_close_source) {
248  return IPC::GetFileHandleForProcess(handle,
249                                      base::Process::Current().handle(),
250                                      should_close_source);
251}
252
253std::set<PP_Instance>*
254PluginProxyTestHarness::PluginDelegateMock::GetGloballySeenInstanceIDSet() {
255  return &instance_id_set_;
256}
257
258uint32 PluginProxyTestHarness::PluginDelegateMock::Register(
259    PluginDispatcher* plugin_dispatcher) {
260  return 0;
261}
262
263void PluginProxyTestHarness::PluginDelegateMock::Unregister(
264    uint32 plugin_dispatcher_id) {
265}
266
267IPC::Sender* PluginProxyTestHarness::PluginDelegateMock::GetBrowserSender() {
268  return browser_sender_;
269}
270
271std::string PluginProxyTestHarness::PluginDelegateMock::GetUILanguage() {
272  return std::string("en-US");
273}
274
275void PluginProxyTestHarness::PluginDelegateMock::PreCacheFont(
276    const void* logfontw) {
277}
278
279void PluginProxyTestHarness::PluginDelegateMock::SetActiveURL(
280    const std::string& url) {
281}
282
283// PluginProxyTest -------------------------------------------------------------
284
285PluginProxyTest::PluginProxyTest() : PluginProxyTestHarness(SINGLETON_GLOBALS) {
286}
287
288PluginProxyTest::~PluginProxyTest() {
289}
290
291void PluginProxyTest::SetUp() {
292  SetUpHarness();
293}
294
295void PluginProxyTest::TearDown() {
296  TearDownHarness();
297}
298
299// PluginProxyMultiThreadTest --------------------------------------------------
300
301PluginProxyMultiThreadTest::PluginProxyMultiThreadTest() {
302}
303
304PluginProxyMultiThreadTest::~PluginProxyMultiThreadTest() {
305}
306
307void PluginProxyMultiThreadTest::RunTest() {
308  main_thread_message_loop_proxy_ =
309      PpapiGlobals::Get()->GetMainThreadMessageLoop();
310  ASSERT_EQ(main_thread_message_loop_proxy_.get(),
311            base::MessageLoopProxy::current());
312  nested_main_thread_message_loop_.reset(new base::RunLoop());
313
314  secondary_thread_.reset(new base::DelegateSimpleThread(
315      this, "PluginProxyMultiThreadTest"));
316
317  {
318    ProxyAutoLock auto_lock;
319
320    // MessageLoopResource assumes that the proxy lock has been acquired.
321    secondary_thread_message_loop_ = new MessageLoopResource(pp_instance());
322
323    ASSERT_EQ(PP_OK,
324        secondary_thread_message_loop_->PostWork(
325            PP_MakeCompletionCallback(
326                &PluginProxyMultiThreadTest::InternalSetUpTestOnSecondaryThread,
327                this),
328            0));
329  }
330
331  SetUpTestOnMainThread();
332
333  secondary_thread_->Start();
334  nested_main_thread_message_loop_->Run();
335  secondary_thread_->Join();
336
337  {
338    ProxyAutoLock auto_lock;
339
340    // The destruction requires a valid PpapiGlobals instance, so we should
341    // explicitly release it.
342    secondary_thread_message_loop_ = NULL;
343  }
344
345  secondary_thread_.reset(NULL);
346  nested_main_thread_message_loop_.reset(NULL);
347  main_thread_message_loop_proxy_ = NULL;
348}
349
350void PluginProxyMultiThreadTest::CheckOnThread(ThreadType thread_type) {
351  ProxyAutoLock auto_lock;
352  if (thread_type == MAIN_THREAD) {
353    ASSERT_TRUE(MessageLoopResource::GetCurrent()->is_main_thread_loop());
354  } else {
355    ASSERT_EQ(secondary_thread_message_loop_.get(),
356              MessageLoopResource::GetCurrent());
357  }
358}
359
360void PluginProxyMultiThreadTest::PostQuitForMainThread() {
361  main_thread_message_loop_proxy_->PostTask(
362      FROM_HERE,
363      base::Bind(&PluginProxyMultiThreadTest::QuitNestedLoop,
364                 base::Unretained(this)));
365}
366
367void PluginProxyMultiThreadTest::PostQuitForSecondaryThread() {
368  ProxyAutoLock auto_lock;
369  secondary_thread_message_loop_->PostQuit(PP_TRUE);
370}
371
372void PluginProxyMultiThreadTest::Run() {
373  ProxyAutoLock auto_lock;
374  ASSERT_EQ(PP_OK, secondary_thread_message_loop_->AttachToCurrentThread());
375  ASSERT_EQ(PP_OK, secondary_thread_message_loop_->Run());
376}
377
378void PluginProxyMultiThreadTest::QuitNestedLoop() {
379  nested_main_thread_message_loop_->Quit();
380}
381
382// static
383void PluginProxyMultiThreadTest::InternalSetUpTestOnSecondaryThread(
384    void* user_data,
385    int32_t result) {
386  EXPECT_EQ(PP_OK, result);
387  PluginProxyMultiThreadTest* thiz =
388      static_cast<PluginProxyMultiThreadTest*>(user_data);
389  thiz->CheckOnThread(SECONDARY_THREAD);
390  thiz->SetUpTestOnSecondaryThread();
391}
392
393// HostProxyTestHarness --------------------------------------------------------
394
395class HostProxyTestHarness::MockSyncMessageStatusReceiver
396    : public HostDispatcher::SyncMessageStatusReceiver {
397 public:
398  virtual void BeginBlockOnSyncMessage() OVERRIDE {}
399  virtual void EndBlockOnSyncMessage() OVERRIDE {}
400};
401
402HostProxyTestHarness::HostProxyTestHarness(GlobalsConfiguration globals_config)
403    : globals_config_(globals_config),
404      status_receiver_(new MockSyncMessageStatusReceiver) {
405}
406
407HostProxyTestHarness::~HostProxyTestHarness() {
408}
409
410PpapiGlobals* HostProxyTestHarness::GetGlobals() {
411  return host_globals_.get();
412}
413
414Dispatcher* HostProxyTestHarness::GetDispatcher() {
415  return host_dispatcher_.get();
416}
417
418void HostProxyTestHarness::SetUpHarness() {
419  // These must be first since the dispatcher set-up uses them.
420  CreateHostGlobals();
421
422  host_dispatcher_.reset(new HostDispatcher(
423      pp_module(),
424      &MockGetInterface,
425      status_receiver_.release(),
426      PpapiPermissions::AllPermissions()));
427  host_dispatcher_->InitWithTestSink(&sink());
428  HostDispatcher::SetForInstance(pp_instance(), host_dispatcher_.get());
429}
430
431void HostProxyTestHarness::SetUpHarnessWithChannel(
432    const IPC::ChannelHandle& channel_handle,
433    base::MessageLoopProxy* ipc_message_loop,
434    base::WaitableEvent* shutdown_event,
435    bool is_client) {
436  // These must be first since the dispatcher set-up uses them.
437  CreateHostGlobals();
438
439  delegate_mock_.Init(ipc_message_loop, shutdown_event);
440
441  host_dispatcher_.reset(new HostDispatcher(
442      pp_module(),
443      &MockGetInterface,
444      status_receiver_.release(),
445      PpapiPermissions::AllPermissions()));
446  ppapi::Preferences preferences;
447  host_dispatcher_->InitHostWithChannel(&delegate_mock_,
448                                        base::kNullProcessId, channel_handle,
449                                        is_client, preferences);
450  HostDispatcher::SetForInstance(pp_instance(), host_dispatcher_.get());
451}
452
453void HostProxyTestHarness::TearDownHarness() {
454  HostDispatcher::RemoveForInstance(pp_instance());
455  host_dispatcher_.reset();
456  host_globals_.reset();
457}
458
459void HostProxyTestHarness::CreateHostGlobals() {
460  if (globals_config_ == PER_THREAD_GLOBALS) {
461    host_globals_.reset(new TestGlobals(PpapiGlobals::PerThreadForTest()));
462    PpapiGlobals::SetPpapiGlobalsOnThreadForTest(GetGlobals());
463  } else {
464    host_globals_.reset(new TestGlobals());
465  }
466}
467
468base::MessageLoopProxy*
469HostProxyTestHarness::DelegateMock::GetIPCMessageLoop() {
470  return ipc_message_loop_;
471}
472
473base::WaitableEvent* HostProxyTestHarness::DelegateMock::GetShutdownEvent() {
474  return shutdown_event_;
475}
476
477IPC::PlatformFileForTransit
478HostProxyTestHarness::DelegateMock::ShareHandleWithRemote(
479    base::PlatformFile handle,
480    base::ProcessId /* remote_pid */,
481    bool should_close_source) {
482  return IPC::GetFileHandleForProcess(handle,
483                                      base::Process::Current().handle(),
484                                      should_close_source);
485}
486
487
488// HostProxyTest ---------------------------------------------------------------
489
490HostProxyTest::HostProxyTest() : HostProxyTestHarness(SINGLETON_GLOBALS) {
491}
492
493HostProxyTest::~HostProxyTest() {
494}
495
496void HostProxyTest::SetUp() {
497  SetUpHarness();
498}
499
500void HostProxyTest::TearDown() {
501  TearDownHarness();
502}
503
504// TwoWayTest ---------------------------------------------------------------
505
506TwoWayTest::TwoWayTest(TwoWayTest::TwoWayTestMode test_mode)
507    : test_mode_(test_mode),
508      host_(ProxyTestHarnessBase::PER_THREAD_GLOBALS),
509      plugin_(ProxyTestHarnessBase::PER_THREAD_GLOBALS),
510      io_thread_("TwoWayTest_IOThread"),
511      plugin_thread_("TwoWayTest_PluginThread"),
512      remote_harness_(NULL),
513      local_harness_(NULL),
514      channel_created_(true, false),
515      shutdown_event_(true, false) {
516  if (test_mode == TEST_PPP_INTERFACE) {
517    remote_harness_ = &plugin_;
518    local_harness_ = &host_;
519  } else {
520    remote_harness_ = &host_;
521    local_harness_ = &plugin_;
522  }
523}
524
525TwoWayTest::~TwoWayTest() {
526  shutdown_event_.Signal();
527}
528
529void TwoWayTest::SetUp() {
530  base::Thread::Options options;
531  options.message_loop_type = MessageLoop::TYPE_IO;
532  io_thread_.StartWithOptions(options);
533  plugin_thread_.Start();
534
535  // Construct the IPC handle name using the process ID so we can safely run
536  // multiple |TwoWayTest|s concurrently.
537  std::ostringstream handle_name;
538  handle_name << "TwoWayTestChannel" << base::GetCurrentProcId();
539  IPC::ChannelHandle handle(handle_name.str());
540  base::WaitableEvent remote_harness_set_up(true, false);
541  plugin_thread_.message_loop_proxy()->PostTask(
542      FROM_HERE,
543      base::Bind(&SetUpRemoteHarness,
544                 remote_harness_,
545                 handle,
546                 io_thread_.message_loop_proxy(),
547                 &shutdown_event_,
548                 &remote_harness_set_up));
549  remote_harness_set_up.Wait();
550  local_harness_->SetUpHarnessWithChannel(handle,
551                                          io_thread_.message_loop_proxy(),
552                                          &shutdown_event_,
553                                          true);  // is_client
554}
555
556void TwoWayTest::TearDown() {
557  base::WaitableEvent remote_harness_torn_down(true, false);
558  plugin_thread_.message_loop_proxy()->PostTask(
559      FROM_HERE,
560      base::Bind(&TearDownRemoteHarness,
561                 remote_harness_,
562                 &remote_harness_torn_down));
563  remote_harness_torn_down.Wait();
564
565  local_harness_->TearDownHarness();
566
567  io_thread_.Stop();
568}
569
570void TwoWayTest::PostTaskOnRemoteHarness(const base::Closure& task) {
571  base::WaitableEvent task_complete(true, false);
572  plugin_thread_.message_loop_proxy()->PostTask(FROM_HERE,
573      base::Bind(&RunTaskOnRemoteHarness,
574                 task,
575                 &task_complete));
576  task_complete.Wait();
577}
578
579
580}  // namespace proxy
581}  // namespace ppapi
582