| // 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/message_loop/message_loop.h" |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/ppb_file_io.h" |
| #include "ppapi/c/ppb_file_ref.h" |
| #include "ppapi/c/ppb_file_system.h" |
| #include "ppapi/proxy/file_system_resource.h" |
| #include "ppapi/proxy/locking_resource_releaser.h" |
| #include "ppapi/proxy/plugin_message_filter.h" |
| #include "ppapi/proxy/ppapi_message_utils.h" |
| #include "ppapi/proxy/ppapi_messages.h" |
| #include "ppapi/proxy/ppapi_proxy_test.h" |
| #include "ppapi/shared_impl/proxy_lock.h" |
| #include "ppapi/shared_impl/scoped_pp_var.h" |
| #include "ppapi/shared_impl/var.h" |
| #include "ppapi/thunk/enter.h" |
| #include "ppapi/thunk/ppb_file_system_api.h" |
| #include "ppapi/thunk/thunk.h" |
| |
| using ppapi::proxy::ResourceMessageTestSink; |
| using ppapi::thunk::EnterResource; |
| using ppapi::thunk::PPB_FileSystem_API; |
| |
| namespace ppapi { |
| namespace proxy { |
| |
| namespace { |
| |
| const int64_t kExpectedFileSystemSize = 100; |
| const int64_t kQuotaRequestAmount1 = 10; |
| const int64_t kQuotaRequestAmount2 = 20; |
| |
| class MockCompletionCallback { |
| public: |
| MockCompletionCallback() : called_(false) {} |
| |
| bool called() { return called_; } |
| int32_t result() { return result_; } |
| |
| static void Callback(void* user_data, int32_t result) { |
| MockCompletionCallback* that = |
| reinterpret_cast<MockCompletionCallback*>(user_data); |
| that->called_ = true; |
| that->result_ = result; |
| } |
| |
| private: |
| bool called_; |
| int32_t result_; |
| }; |
| |
| class MockRequestQuotaCallback { |
| public: |
| MockRequestQuotaCallback() : called_(false) {} |
| |
| bool called() { return called_; } |
| int64_t result() { return result_; } |
| |
| void Reset() { called_ = false; } |
| |
| void Callback(int64_t result) { |
| ASSERT_FALSE(called_); |
| called_ = true; |
| result_ = result; |
| } |
| |
| private: |
| bool called_; |
| int64_t result_; |
| }; |
| |
| class FileSystemResourceTest : public PluginProxyTest { |
| public: |
| const PPB_FileSystem_1_0* file_system_iface; |
| const PPB_FileRef_1_1* file_ref_iface; |
| const PPB_FileIO_1_1* file_io_iface; |
| |
| FileSystemResourceTest() |
| : file_system_iface(thunk::GetPPB_FileSystem_1_0_Thunk()), |
| file_ref_iface(thunk::GetPPB_FileRef_1_1_Thunk()), |
| file_io_iface(thunk::GetPPB_FileIO_1_1_Thunk()) { |
| } |
| |
| void SendReply(const ResourceMessageCallParams& params, |
| int32_t result, |
| const IPC::Message& nested_message) { |
| ResourceMessageReplyParams reply_params(params.pp_resource(), |
| params.sequence()); |
| reply_params.set_result(result); |
| PluginMessageFilter::DispatchResourceReplyForTest( |
| reply_params, nested_message); |
| } |
| |
| void SendOpenReply(const ResourceMessageCallParams& params, int32_t result) { |
| SendReply(params, result, PpapiPluginMsg_FileSystem_OpenReply()); |
| } |
| |
| // Opens the given file system. |
| void OpenFileSystem(PP_Resource file_system) { |
| MockCompletionCallback cb; |
| int32_t result = file_system_iface->Open( |
| file_system, |
| kExpectedFileSystemSize, |
| PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| // Should have sent two new "open" messages to the browser and renderer. |
| ResourceMessageTestSink::ResourceCallVector open_messages = |
| sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID); |
| ASSERT_EQ(2U, open_messages.size()); |
| sink().ClearMessages(); |
| |
| // The resource is expecting two replies. |
| SendOpenReply(open_messages[0].first, PP_OK); |
| SendOpenReply(open_messages[1].first, PP_OK); |
| |
| ASSERT_TRUE(cb.called()); |
| ASSERT_EQ(PP_OK, cb.result()); |
| } |
| |
| // Opens the given file in the given file system. Since there is no host, |
| // the file handle will be invalid. |
| void OpenFile(PP_Resource file_io, |
| PP_Resource file_ref, |
| PP_Resource file_system) { |
| MockCompletionCallback cb; |
| int32_t result = file_io_iface->Open( |
| file_io, |
| file_ref, |
| PP_FILEOPENFLAG_WRITE, |
| PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| // Should have sent an "open" message. |
| ResourceMessageCallParams params; |
| IPC::Message msg; |
| ASSERT_TRUE(sink().GetFirstResourceCallMatching( |
| PpapiHostMsg_FileIO_Open::ID, ¶ms, &msg)); |
| sink().ClearMessages(); |
| |
| // Send a success reply. |
| ResourceMessageReplyParams reply_params(params.pp_resource(), |
| params.sequence()); |
| reply_params.set_result(PP_OK); |
| PluginMessageFilter::DispatchResourceReplyForTest( |
| reply_params, |
| PpapiPluginMsg_FileIO_OpenReply(file_system, |
| 0 /* max_written_offset */)); |
| } |
| }; |
| |
| } // namespace |
| |
| // Test that Open fails if either host returns failure. The other tests exercise |
| // the case where both hosts return PP_OK. |
| TEST_F(FileSystemResourceTest, OpenFailure) { |
| // Fail if the first reply doesn't return PP_OK. |
| { |
| LockingResourceReleaser file_system( |
| file_system_iface->Create(pp_instance(), |
| PP_FILESYSTEMTYPE_LOCALTEMPORARY)); |
| |
| MockCompletionCallback cb; |
| int32_t result = file_system_iface->Open( |
| file_system.get(), |
| kExpectedFileSystemSize, |
| PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| ResourceMessageTestSink::ResourceCallVector open_messages = |
| sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID); |
| ASSERT_EQ(2U, open_messages.size()); |
| sink().ClearMessages(); |
| |
| SendOpenReply(open_messages[0].first, PP_ERROR_FAILED); |
| SendOpenReply(open_messages[1].first, PP_OK); |
| |
| ASSERT_TRUE(cb.called()); |
| ASSERT_EQ(PP_ERROR_FAILED, cb.result()); |
| } |
| // Fail if the second reply doesn't return PP_OK. |
| { |
| LockingResourceReleaser file_system( |
| file_system_iface->Create(pp_instance(), |
| PP_FILESYSTEMTYPE_LOCALTEMPORARY)); |
| |
| MockCompletionCallback cb; |
| int32_t result = file_system_iface->Open( |
| file_system.get(), |
| kExpectedFileSystemSize, |
| PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| ResourceMessageTestSink::ResourceCallVector open_messages = |
| sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID); |
| ASSERT_EQ(2U, open_messages.size()); |
| sink().ClearMessages(); |
| |
| SendOpenReply(open_messages[0].first, PP_OK); |
| SendOpenReply(open_messages[1].first, PP_ERROR_FAILED); |
| |
| ASSERT_TRUE(cb.called()); |
| ASSERT_EQ(PP_ERROR_FAILED, cb.result()); |
| } |
| } |
| |
| TEST_F(FileSystemResourceTest, RequestQuota) { |
| LockingResourceReleaser file_system( |
| file_system_iface->Create(pp_instance(), |
| PP_FILESYSTEMTYPE_LOCALTEMPORARY)); |
| |
| OpenFileSystem(file_system.get()); |
| |
| // Create and open two files in the file system. FileIOResource calls |
| // FileSystemResource::OpenQuotaFile on success. |
| LockingResourceReleaser file_ref1( |
| file_ref_iface->Create(file_system.get(), "/file1")); |
| LockingResourceReleaser file_io1(file_io_iface->Create(pp_instance())); |
| OpenFile(file_io1.get(), file_ref1.get(), file_system.get()); |
| LockingResourceReleaser file_ref2( |
| file_ref_iface->Create(file_system.get(), "/file2")); |
| LockingResourceReleaser file_io2(file_io_iface->Create(pp_instance())); |
| OpenFile(file_io2.get(), file_ref2.get(), file_system.get()); |
| |
| EnterResource<PPB_FileSystem_API> enter(file_system.get(), true); |
| ASSERT_FALSE(enter.failed()); |
| PPB_FileSystem_API* file_system_api = enter.object(); |
| |
| MockRequestQuotaCallback cb1; |
| int64_t result = file_system_api->RequestQuota( |
| kQuotaRequestAmount1, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| // Should have sent a "reserve quota" message, with the amount of the request |
| // and a map of all currently open files to their max written offsets. |
| ResourceMessageCallParams params; |
| IPC::Message msg; |
| ASSERT_TRUE(sink().GetFirstResourceCallMatching( |
| PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); |
| sink().ClearMessages(); |
| |
| int64_t amount = 0; |
| FileGrowthMap file_growths; |
| ASSERT_TRUE(UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>( |
| msg, &amount, &file_growths)); |
| ASSERT_EQ(kQuotaRequestAmount1, amount); |
| ASSERT_EQ(2U, file_growths.size()); |
| ASSERT_EQ(0, file_growths[file_io1.get()].max_written_offset); |
| ASSERT_EQ(0, file_growths[file_io2.get()].max_written_offset); |
| |
| // Make another request while the "reserve quota" message is pending. |
| MockRequestQuotaCallback cb2; |
| result = file_system_api->RequestQuota( |
| kQuotaRequestAmount2, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2))); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| // No new "reserve quota" message should be sent while one is pending. |
| ASSERT_FALSE(sink().GetFirstResourceCallMatching( |
| PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); |
| { |
| ProxyAutoUnlock unlock_to_prevent_deadlock; |
| // Reply with quota reservation amount sufficient to cover both requests. |
| // Both callbacks should be called with the requests granted. |
| SendReply(params, |
| PP_OK, |
| PpapiPluginMsg_FileSystem_ReserveQuotaReply( |
| kQuotaRequestAmount1 + kQuotaRequestAmount2, |
| FileGrowthMapToFileSizeMapForTesting(file_growths))); |
| } |
| ASSERT_TRUE(cb1.called()); |
| ASSERT_EQ(kQuotaRequestAmount1, cb1.result()); |
| ASSERT_TRUE(cb2.called()); |
| ASSERT_EQ(kQuotaRequestAmount2, cb2.result()); |
| cb1.Reset(); |
| cb2.Reset(); |
| |
| // All requests should fail when insufficient quota is returned to satisfy |
| // the first request. |
| result = file_system_api->RequestQuota( |
| kQuotaRequestAmount1, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| result = file_system_api->RequestQuota( |
| kQuotaRequestAmount2, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2))); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| ASSERT_TRUE(sink().GetFirstResourceCallMatching( |
| PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); |
| sink().ClearMessages(); |
| { |
| ProxyAutoUnlock unlock_to_prevent_deadlock; |
| // Reply with quota reservation amount insufficient to cover the first |
| // request. |
| SendReply(params, |
| PP_OK, |
| PpapiPluginMsg_FileSystem_ReserveQuotaReply( |
| kQuotaRequestAmount1 - 1, |
| FileGrowthMapToFileSizeMapForTesting(file_growths))); |
| } |
| ASSERT_TRUE(cb1.called()); |
| ASSERT_EQ(0, cb1.result()); |
| ASSERT_TRUE(cb2.called()); |
| ASSERT_EQ(0, cb2.result()); |
| cb1.Reset(); |
| cb2.Reset(); |
| |
| // A new request should be made if the quota reservation is enough to satisfy |
| // at least one request. |
| result = file_system_api->RequestQuota( |
| kQuotaRequestAmount1, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| result = file_system_api->RequestQuota( |
| kQuotaRequestAmount2, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2))); |
| ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); |
| |
| ASSERT_TRUE(sink().GetFirstResourceCallMatching( |
| PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); |
| sink().ClearMessages(); |
| { |
| ProxyAutoUnlock unlock_to_prevent_deadlock; |
| // Reply with quota reservation amount sufficient only to cover the first |
| // request. |
| SendReply(params, |
| PP_OK, |
| PpapiPluginMsg_FileSystem_ReserveQuotaReply( |
| kQuotaRequestAmount1, |
| FileGrowthMapToFileSizeMapForTesting(file_growths))); |
| } |
| ASSERT_TRUE(cb1.called()); |
| ASSERT_EQ(kQuotaRequestAmount1, cb1.result()); |
| ASSERT_FALSE(cb2.called()); |
| |
| // Another request message should have been sent. |
| ASSERT_TRUE(sink().GetFirstResourceCallMatching( |
| PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); |
| sink().ClearMessages(); |
| { |
| ProxyAutoUnlock unlock_to_prevent_deadlock; |
| // Reply with quota reservation amount sufficient to cover the second |
| // request and some extra. |
| SendReply(params, |
| PP_OK, |
| PpapiPluginMsg_FileSystem_ReserveQuotaReply( |
| kQuotaRequestAmount1 + kQuotaRequestAmount2, |
| FileGrowthMapToFileSizeMapForTesting(file_growths))); |
| } |
| |
| ASSERT_TRUE(cb2.called()); |
| ASSERT_EQ(kQuotaRequestAmount2, cb2.result()); |
| cb1.Reset(); |
| cb2.Reset(); |
| |
| // There is kQuotaRequestAmount1 of quota left, and a request for it should |
| // succeed immediately. |
| result = file_system_api->RequestQuota( |
| kQuotaRequestAmount1, |
| base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); |
| ASSERT_EQ(kQuotaRequestAmount1, result); |
| } |
| |
| } // namespace proxy |
| } // namespace ppapi |