| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Note: This file tests both binding.h (mojo::Binding) and strong_binding.h |
| // (mojo::StrongBinding). |
| |
| #include "mojo/public/cpp/bindings/binding.h" |
| |
| #include <stdint.h> |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "mojo/core/embedder/embedder.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/public/cpp/bindings/tests/bindings_test_base.h" |
| #include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" |
| #include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" |
| #include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace mojo { |
| namespace { |
| |
| class ServiceImpl : public sample::Service { |
| public: |
| explicit ServiceImpl(bool* was_deleted = nullptr) |
| : was_deleted_(was_deleted) {} |
| ~ServiceImpl() override { |
| if (was_deleted_) |
| *was_deleted_ = true; |
| } |
| |
| private: |
| // sample::Service implementation |
| void Frobinate(sample::FooPtr foo, |
| BazOptions options, |
| sample::PortPtr port, |
| FrobinateCallback callback) override { |
| std::move(callback).Run(1); |
| } |
| void GetPort(InterfaceRequest<sample::Port> port) override {} |
| |
| bool* const was_deleted_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ServiceImpl); |
| }; |
| |
| template <typename... Args> |
| void DoSetFlagAndRunClosure(bool* flag, |
| const base::Closure& closure, |
| Args... args) { |
| *flag = true; |
| if (!closure.is_null()) |
| closure.Run(); |
| } |
| |
| template <typename... Args> |
| base::Callback<void(Args...)> SetFlagAndRunClosure( |
| bool* flag, |
| const base::Closure& callback = base::Closure()) { |
| return base::Bind(&DoSetFlagAndRunClosure<Args...>, flag, callback); |
| } |
| |
| // BindingTest ----------------------------------------------------------------- |
| |
| using BindingTest = BindingsTestBase; |
| |
| TEST_P(BindingTest, Close) { |
| bool called = false; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| base::RunLoop run_loop; |
| ptr.set_connection_error_handler( |
| SetFlagAndRunClosure(&called, run_loop.QuitClosure())); |
| ServiceImpl impl; |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| |
| binding.Close(); |
| EXPECT_FALSE(called); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| } |
| |
| // Tests that destroying a mojo::Binding closes the bound message pipe handle. |
| TEST_P(BindingTest, DestroyClosesMessagePipe) { |
| bool encountered_error = false; |
| ServiceImpl impl; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| base::RunLoop run_loop; |
| ptr.set_connection_error_handler( |
| SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); |
| bool called = false; |
| base::RunLoop run_loop2; |
| { |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, |
| run_loop2.QuitClosure())); |
| run_loop2.Run(); |
| EXPECT_TRUE(called); |
| EXPECT_FALSE(encountered_error); |
| } |
| // Now that the Binding is out of scope we should detect an error on the other |
| // end of the pipe. |
| run_loop.Run(); |
| EXPECT_TRUE(encountered_error); |
| |
| // And calls should fail. |
| called = false; |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, |
| run_loop2.QuitClosure())); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(called); |
| } |
| |
| // Tests that the binding's connection error handler gets called when the other |
| // end is closed. |
| TEST_P(BindingTest, ConnectionError) { |
| bool called = false; |
| { |
| ServiceImpl impl; |
| sample::ServicePtr ptr; |
| Binding<sample::Service> binding(&impl, MakeRequest(&ptr)); |
| base::RunLoop run_loop; |
| binding.set_connection_error_handler( |
| SetFlagAndRunClosure(&called, run_loop.QuitClosure())); |
| ptr.reset(); |
| EXPECT_FALSE(called); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| // We want to make sure that it isn't called again during destruction. |
| called = false; |
| } |
| EXPECT_FALSE(called); |
| } |
| |
| // Tests that calling Close doesn't result in the connection error handler being |
| // called. |
| TEST_P(BindingTest, CloseDoesntCallConnectionErrorHandler) { |
| ServiceImpl impl; |
| sample::ServicePtr ptr; |
| Binding<sample::Service> binding(&impl, MakeRequest(&ptr)); |
| bool called = false; |
| binding.set_connection_error_handler(SetFlagAndRunClosure(&called)); |
| binding.Close(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(called); |
| |
| // We can also close the other end, and the error handler still won't be |
| // called. |
| ptr.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(called); |
| } |
| |
| class ServiceImplWithBinding : public ServiceImpl { |
| public: |
| ServiceImplWithBinding(bool* was_deleted, |
| const base::Closure& closure, |
| InterfaceRequest<sample::Service> request) |
| : ServiceImpl(was_deleted), |
| binding_(this, std::move(request)), |
| closure_(closure) { |
| binding_.set_connection_error_handler( |
| base::Bind(&ServiceImplWithBinding::OnConnectionError, |
| base::Unretained(this))); |
| } |
| |
| private: |
| ~ServiceImplWithBinding() override{ |
| closure_.Run(); |
| } |
| |
| void OnConnectionError() { delete this; } |
| |
| Binding<sample::Service> binding_; |
| base::Closure closure_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ServiceImplWithBinding); |
| }; |
| |
| // Tests that the binding may be deleted in the connection error handler. |
| TEST_P(BindingTest, SelfDeleteOnConnectionError) { |
| bool was_deleted = false; |
| sample::ServicePtr ptr; |
| // This should delete itself on connection error. |
| base::RunLoop run_loop; |
| new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(), |
| MakeRequest(&ptr)); |
| ptr.reset(); |
| EXPECT_FALSE(was_deleted); |
| run_loop.Run(); |
| EXPECT_TRUE(was_deleted); |
| } |
| |
| // Tests that explicitly calling Unbind followed by rebinding works. |
| TEST_P(BindingTest, Unbind) { |
| ServiceImpl impl; |
| sample::ServicePtr ptr; |
| Binding<sample::Service> binding(&impl, MakeRequest(&ptr)); |
| |
| bool called = false; |
| base::RunLoop run_loop; |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| |
| called = false; |
| auto request = binding.Unbind(); |
| EXPECT_FALSE(binding.is_bound()); |
| // All calls should fail when not bound... |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, |
| run_loop.QuitClosure())); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(called); |
| |
| called = false; |
| binding.Bind(std::move(request)); |
| EXPECT_TRUE(binding.is_bound()); |
| // ...and should succeed again when the rebound. |
| base::RunLoop run_loop2; |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, |
| run_loop2.QuitClosure())); |
| run_loop2.Run(); |
| EXPECT_TRUE(called); |
| } |
| |
| class IntegerAccessorImpl : public sample::IntegerAccessor { |
| public: |
| IntegerAccessorImpl() {} |
| ~IntegerAccessorImpl() override {} |
| |
| private: |
| // sample::IntegerAccessor implementation. |
| void GetInteger(GetIntegerCallback callback) override { |
| std::move(callback).Run(1, sample::Enum::VALUE); |
| } |
| void SetInteger(int64_t data, sample::Enum type) override {} |
| |
| DISALLOW_COPY_AND_ASSIGN(IntegerAccessorImpl); |
| }; |
| |
| TEST_P(BindingTest, PauseResume) { |
| bool called = false; |
| base::RunLoop run_loop; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| ServiceImpl impl; |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| binding.PauseIncomingMethodCallProcessing(); |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, |
| run_loop.QuitClosure())); |
| EXPECT_FALSE(called); |
| base::RunLoop().RunUntilIdle(); |
| // Frobinate() should not be called as the binding is paused. |
| EXPECT_FALSE(called); |
| |
| // Resume the binding, which should trigger processing. |
| binding.ResumeIncomingMethodCallProcessing(); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| } |
| |
| // Verifies the connection error handler is not run while a binding is paused. |
| TEST_P(BindingTest, ErrorHandleNotRunWhilePaused) { |
| bool called = false; |
| base::RunLoop run_loop; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| ServiceImpl impl; |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| binding.set_connection_error_handler( |
| SetFlagAndRunClosure(&called, run_loop.QuitClosure())); |
| binding.PauseIncomingMethodCallProcessing(); |
| |
| ptr.reset(); |
| base::RunLoop().RunUntilIdle(); |
| // The connection error handle should not be called as the binding is paused. |
| EXPECT_FALSE(called); |
| |
| // Resume the binding, which should trigger the error handler. |
| binding.ResumeIncomingMethodCallProcessing(); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| } |
| |
| class PingServiceImpl : public test::PingService { |
| public: |
| PingServiceImpl() {} |
| ~PingServiceImpl() override {} |
| |
| // test::PingService: |
| void Ping(PingCallback callback) override { |
| if (!ping_handler_.is_null()) |
| ping_handler_.Run(); |
| std::move(callback).Run(); |
| } |
| |
| void set_ping_handler(const base::Closure& handler) { |
| ping_handler_ = handler; |
| } |
| |
| private: |
| base::Closure ping_handler_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PingServiceImpl); |
| }; |
| |
| class CallbackFilter : public MessageReceiver { |
| public: |
| explicit CallbackFilter(const base::Closure& callback) |
| : callback_(callback) {} |
| ~CallbackFilter() override {} |
| |
| static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) { |
| return std::make_unique<CallbackFilter>(callback); |
| } |
| |
| // MessageReceiver: |
| bool Accept(Message* message) override { |
| callback_.Run(); |
| return true; |
| } |
| |
| private: |
| const base::Closure callback_; |
| }; |
| |
| // Verifies that message filters are notified in the order they were added and |
| // are always notified before a message is dispatched. |
| TEST_P(BindingTest, MessageFilter) { |
| test::PingServicePtr ptr; |
| PingServiceImpl impl; |
| mojo::Binding<test::PingService> binding(&impl, MakeRequest(&ptr)); |
| |
| int status = 0; |
| auto handler_helper = [] (int* status, int expected_status, int new_status) { |
| EXPECT_EQ(expected_status, *status); |
| *status = new_status; |
| }; |
| auto create_handler = [&] (int expected_status, int new_status) { |
| return base::Bind(handler_helper, &status, expected_status, new_status); |
| }; |
| |
| binding.AddFilter(CallbackFilter::Wrap(create_handler(0, 1))); |
| binding.AddFilter(CallbackFilter::Wrap(create_handler(1, 2))); |
| impl.set_ping_handler(create_handler(2, 3)); |
| |
| for (int i = 0; i < 10; ++i) { |
| status = 0; |
| base::RunLoop loop; |
| ptr->Ping(loop.QuitClosure()); |
| loop.Run(); |
| EXPECT_EQ(3, status); |
| } |
| } |
| |
| void Fail() { |
| FAIL() << "Unexpected connection error"; |
| } |
| |
| TEST_P(BindingTest, FlushForTesting) { |
| bool called = false; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| ServiceImpl impl; |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| binding.set_connection_error_handler(base::Bind(&Fail)); |
| |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called)); |
| EXPECT_FALSE(called); |
| // Because the flush is sent from the binding, it only guarantees that the |
| // request has been received, not the response. The second flush waits for the |
| // response to be received. |
| binding.FlushForTesting(); |
| binding.FlushForTesting(); |
| EXPECT_TRUE(called); |
| } |
| |
| TEST_P(BindingTest, FlushForTestingWithClosedPeer) { |
| bool called = false; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| ServiceImpl impl; |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| binding.set_connection_error_handler(SetFlagAndRunClosure(&called)); |
| ptr.reset(); |
| |
| EXPECT_FALSE(called); |
| binding.FlushForTesting(); |
| EXPECT_TRUE(called); |
| binding.FlushForTesting(); |
| } |
| |
| TEST_P(BindingTest, ConnectionErrorWithReason) { |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| ServiceImpl impl; |
| Binding<sample::Service> binding(&impl, std::move(request)); |
| |
| base::RunLoop run_loop; |
| binding.set_connection_error_with_reason_handler(base::Bind( |
| [](const base::Closure& quit_closure, uint32_t custom_reason, |
| const std::string& description) { |
| EXPECT_EQ(1234u, custom_reason); |
| EXPECT_EQ("hello", description); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| ptr.ResetWithReason(1234u, "hello"); |
| |
| run_loop.Run(); |
| } |
| |
| template <typename T> |
| struct WeakPtrImplRefTraits { |
| using PointerType = base::WeakPtr<T>; |
| |
| static bool IsNull(const base::WeakPtr<T>& ptr) { return !ptr; } |
| static T* GetRawPointer(base::WeakPtr<T>* ptr) { return ptr->get(); } |
| }; |
| |
| template <typename T> |
| using WeakBinding = Binding<T, WeakPtrImplRefTraits<T>>; |
| |
| TEST_P(BindingTest, CustomImplPointerType) { |
| PingServiceImpl impl; |
| base::WeakPtrFactory<test::PingService> weak_factory(&impl); |
| |
| test::PingServicePtr proxy; |
| WeakBinding<test::PingService> binding(weak_factory.GetWeakPtr(), |
| MakeRequest(&proxy)); |
| |
| { |
| // Ensure the binding is functioning. |
| base::RunLoop run_loop; |
| proxy->Ping(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| { |
| // Attempt to dispatch another message after the WeakPtr is invalidated. |
| base::Closure assert_not_reached = base::Bind([] { NOTREACHED(); }); |
| impl.set_ping_handler(assert_not_reached); |
| proxy->Ping(assert_not_reached); |
| |
| // The binding will close its end of the pipe which will trigger a |
| // connection error on |proxy|. |
| base::RunLoop run_loop; |
| proxy.set_connection_error_handler(run_loop.QuitClosure()); |
| weak_factory.InvalidateWeakPtrs(); |
| run_loop.Run(); |
| } |
| } |
| |
| TEST_P(BindingTest, ReportBadMessage) { |
| bool called = false; |
| test::PingServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| base::RunLoop run_loop; |
| ptr.set_connection_error_handler( |
| SetFlagAndRunClosure(&called, run_loop.QuitClosure())); |
| PingServiceImpl impl; |
| Binding<test::PingService> binding(&impl, std::move(request)); |
| impl.set_ping_handler(base::Bind( |
| [](Binding<test::PingService>* binding) { |
| binding->ReportBadMessage("received bad message"); |
| }, |
| &binding)); |
| |
| std::string received_error; |
| core::SetDefaultProcessErrorCallback( |
| base::Bind([](std::string* out_error, |
| const std::string& error) { *out_error = error; }, |
| &received_error)); |
| |
| ptr->Ping(base::Bind([] {})); |
| EXPECT_FALSE(called); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| EXPECT_EQ("received bad message", received_error); |
| |
| core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback()); |
| } |
| |
| TEST_P(BindingTest, GetBadMessageCallback) { |
| test::PingServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| base::RunLoop run_loop; |
| PingServiceImpl impl; |
| ReportBadMessageCallback bad_message_callback; |
| |
| std::string received_error; |
| core::SetDefaultProcessErrorCallback( |
| base::Bind([](std::string* out_error, |
| const std::string& error) { *out_error = error; }, |
| &received_error)); |
| |
| { |
| Binding<test::PingService> binding(&impl, std::move(request)); |
| impl.set_ping_handler(base::Bind( |
| [](Binding<test::PingService>* binding, |
| ReportBadMessageCallback* out_callback) { |
| *out_callback = binding->GetBadMessageCallback(); |
| }, |
| &binding, &bad_message_callback)); |
| ptr->Ping(run_loop.QuitClosure()); |
| run_loop.Run(); |
| EXPECT_TRUE(received_error.empty()); |
| EXPECT_TRUE(bad_message_callback); |
| } |
| |
| std::move(bad_message_callback).Run("delayed bad message"); |
| EXPECT_EQ("delayed bad message", received_error); |
| |
| core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback()); |
| } |
| |
| // StrongBindingTest ----------------------------------------------------------- |
| |
| using StrongBindingTest = BindingsTestBase; |
| |
| // Tests that destroying a mojo::StrongBinding closes the bound message pipe |
| // handle but does *not* destroy the implementation object. |
| TEST_P(StrongBindingTest, DestroyClosesMessagePipe) { |
| base::RunLoop run_loop; |
| bool encountered_error = false; |
| bool was_deleted = false; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| ptr.set_connection_error_handler( |
| SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); |
| bool called = false; |
| base::RunLoop run_loop2; |
| |
| auto binding = MakeStrongBinding(std::make_unique<ServiceImpl>(&was_deleted), |
| std::move(request)); |
| ptr->Frobinate( |
| nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called, run_loop2.QuitClosure())); |
| run_loop2.Run(); |
| EXPECT_TRUE(called); |
| EXPECT_FALSE(encountered_error); |
| binding->Close(); |
| |
| // Now that the StrongBinding is closed we should detect an error on the other |
| // end of the pipe. |
| run_loop.Run(); |
| EXPECT_TRUE(encountered_error); |
| |
| // Destroying the StrongBinding also destroys the impl. |
| ASSERT_TRUE(was_deleted); |
| } |
| |
| // Tests the typical case, where the implementation object owns the |
| // StrongBinding (and should be destroyed on connection error). |
| TEST_P(StrongBindingTest, ConnectionErrorDestroysImpl) { |
| sample::ServicePtr ptr; |
| bool was_deleted = false; |
| // Will delete itself. |
| base::RunLoop run_loop; |
| new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(), |
| MakeRequest(&ptr)); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(was_deleted); |
| |
| ptr.reset(); |
| EXPECT_FALSE(was_deleted); |
| run_loop.Run(); |
| EXPECT_TRUE(was_deleted); |
| } |
| |
| TEST_P(StrongBindingTest, FlushForTesting) { |
| bool called = false; |
| bool was_deleted = false; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| auto binding = MakeStrongBinding(std::make_unique<ServiceImpl>(&was_deleted), |
| std::move(request)); |
| binding->set_connection_error_handler(base::Bind(&Fail)); |
| |
| ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, |
| SetFlagAndRunClosure<int32_t>(&called)); |
| EXPECT_FALSE(called); |
| // Because the flush is sent from the binding, it only guarantees that the |
| // request has been received, not the response. The second flush waits for the |
| // response to be received. |
| ASSERT_TRUE(binding); |
| binding->FlushForTesting(); |
| ASSERT_TRUE(binding); |
| binding->FlushForTesting(); |
| EXPECT_TRUE(called); |
| EXPECT_FALSE(was_deleted); |
| ptr.reset(); |
| ASSERT_TRUE(binding); |
| binding->set_connection_error_handler(base::Closure()); |
| binding->FlushForTesting(); |
| EXPECT_TRUE(was_deleted); |
| } |
| |
| TEST_P(StrongBindingTest, FlushForTestingWithClosedPeer) { |
| bool called = false; |
| bool was_deleted = false; |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| auto binding = MakeStrongBinding(std::make_unique<ServiceImpl>(&was_deleted), |
| std::move(request)); |
| binding->set_connection_error_handler(SetFlagAndRunClosure(&called)); |
| ptr.reset(); |
| |
| EXPECT_FALSE(called); |
| EXPECT_FALSE(was_deleted); |
| ASSERT_TRUE(binding); |
| binding->FlushForTesting(); |
| EXPECT_TRUE(called); |
| EXPECT_TRUE(was_deleted); |
| ASSERT_FALSE(binding); |
| } |
| |
| TEST_P(StrongBindingTest, ConnectionErrorWithReason) { |
| sample::ServicePtr ptr; |
| auto request = MakeRequest(&ptr); |
| auto binding = |
| MakeStrongBinding(std::make_unique<ServiceImpl>(), std::move(request)); |
| base::RunLoop run_loop; |
| binding->set_connection_error_with_reason_handler(base::Bind( |
| [](const base::Closure& quit_closure, uint32_t custom_reason, |
| const std::string& description) { |
| EXPECT_EQ(5678u, custom_reason); |
| EXPECT_EQ("hello", description); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| ptr.ResetWithReason(5678u, "hello"); |
| |
| run_loop.Run(); |
| } |
| |
| INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(BindingTest); |
| INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(StrongBindingTest); |
| |
| } // namespace |
| } // mojo |