// Copyright 2014 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. #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "extensions/browser/mojo/stash_backend.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { class StashServiceTest : public testing::Test, public mojo::ErrorHandler { public: enum Event { EVENT_NONE, EVENT_STASH_RETRIEVED, }; StashServiceTest() {} void SetUp() override { expecting_error_ = false; expected_event_ = EVENT_NONE; stash_backend_.reset(new StashBackend); stash_backend_->BindToRequest(mojo::GetProxy(&stash_service_)); stash_service_.set_error_handler(this); } void OnConnectionError() override { FAIL() << "Unexpected connection error"; } mojo::Array RetrieveStash() { mojo::Array stash; stash_service_->RetrieveStash(base::Bind( &StashServiceTest::StashRetrieved, base::Unretained(this), &stash)); WaitForEvent(EVENT_STASH_RETRIEVED); return stash.Pass(); } void StashRetrieved(mojo::Array* output, mojo::Array stash) { *output = stash.Pass(); EventReceived(EVENT_STASH_RETRIEVED); } void WaitForEvent(Event event) { expected_event_ = event; base::RunLoop run_loop; stop_run_loop_ = run_loop.QuitClosure(); run_loop.Run(); } void EventReceived(Event event) { if (event == expected_event_ && !stop_run_loop_.is_null()) stop_run_loop_.Run(); } protected: base::MessageLoop message_loop_; base::Closure stop_run_loop_; scoped_ptr stash_backend_; Event expected_event_; bool expecting_error_; mojo::InterfacePtr stash_service_; private: DISALLOW_COPY_AND_ASSIGN(StashServiceTest); }; // Test that adding stashed objects in multiple calls can all be retrieved by a // Retrieve call. TEST_F(StashServiceTest, AddTwiceAndRetrieve) { mojo::Array stashed_objects; StashedObjectPtr stashed_object(StashedObject::New()); stashed_object->id = "test type"; stashed_object->data.push_back(1); stashed_object->stashed_handles = mojo::Array(0); stashed_objects.push_back(stashed_object.Pass()); stash_service_->AddToStash(stashed_objects.Pass()); stashed_object = StashedObject::New(); stashed_object->id = "test type2"; stashed_object->data.push_back(2); stashed_object->data.push_back(3); stashed_object->stashed_handles = mojo::Array(0); stashed_objects.push_back(stashed_object.Pass()); stash_service_->AddToStash(stashed_objects.Pass()); stashed_objects = RetrieveStash(); ASSERT_EQ(2u, stashed_objects.size()); EXPECT_EQ("test type", stashed_objects[0]->id); EXPECT_EQ(0u, stashed_objects[0]->stashed_handles.size()); EXPECT_EQ(1u, stashed_objects[0]->data.size()); EXPECT_EQ(1, stashed_objects[0]->data[0]); EXPECT_EQ("test type2", stashed_objects[1]->id); EXPECT_EQ(0u, stashed_objects[1]->stashed_handles.size()); EXPECT_EQ(2u, stashed_objects[1]->data.size()); EXPECT_EQ(2, stashed_objects[1]->data[0]); EXPECT_EQ(3, stashed_objects[1]->data[1]); } // Test that handles survive a round-trip through the stash. TEST_F(StashServiceTest, StashAndRetrieveHandles) { mojo::Array stashed_objects; StashedObjectPtr stashed_object(StashedObject::New()); stashed_object->id = "test type"; stashed_object->data.push_back(1); mojo::ScopedDataPipeConsumerHandle consumer; mojo::ScopedDataPipeProducerHandle producer; MojoCreateDataPipeOptions options = { sizeof(options), MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, 1, 1, }; mojo::CreateDataPipe(&options, &producer, &consumer); uint32_t num_bytes = 1; MojoResult result = mojo::WriteDataRaw( producer.get(), "1", &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE); ASSERT_EQ(MOJO_RESULT_OK, result); ASSERT_EQ(1u, num_bytes); stashed_object->stashed_handles.push_back( mojo::ScopedHandle::From(producer.Pass())); stashed_object->stashed_handles.push_back( mojo::ScopedHandle::From(consumer.Pass())); stashed_objects.push_back(stashed_object.Pass()); stash_service_->AddToStash(stashed_objects.Pass()); stashed_objects = RetrieveStash(); ASSERT_EQ(1u, stashed_objects.size()); EXPECT_EQ("test type", stashed_objects[0]->id); ASSERT_EQ(2u, stashed_objects[0]->stashed_handles.size()); consumer = mojo::ScopedDataPipeConsumerHandle::From( stashed_objects[0]->stashed_handles[1].Pass()); result = mojo::Wait( consumer.get(), MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE); ASSERT_EQ(MOJO_RESULT_OK, result); char data = '\0'; result = mojo::ReadDataRaw( consumer.get(), &data, &num_bytes, MOJO_READ_DATA_FLAG_ALL_OR_NONE); ASSERT_EQ(MOJO_RESULT_OK, result); ASSERT_EQ(1u, num_bytes); EXPECT_EQ('1', data); } TEST_F(StashServiceTest, RetrieveWithoutStashing) { mojo::Array stashed_objects = RetrieveStash(); ASSERT_TRUE(!stashed_objects.is_null()); EXPECT_EQ(0u, stashed_objects.size()); } // Test that a stash service discards stashed objects when the backend no longer // exists. TEST_F(StashServiceTest, ServiceWithDeletedBackend) { stash_backend_.reset(); stash_service_.set_error_handler(this); mojo::Array stashed_objects; StashedObjectPtr stashed_object(StashedObject::New()); stashed_object->id = "test type"; stashed_object->data.push_back(1); mojo::MessagePipe message_pipe; stashed_object->stashed_handles.push_back( mojo::ScopedHandle::From(message_pipe.handle0.Pass())); stashed_objects.push_back(stashed_object.Pass()); stash_service_->AddToStash(stashed_objects.Pass()); stashed_objects = RetrieveStash(); ASSERT_EQ(0u, stashed_objects.size()); // Check that the stashed handle has been closed. MojoResult result = mojo::Wait(message_pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE); EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); } } // namespace extensions