1 // Copyright 2014 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.
5 #include "base/message_loop/message_loop.h"
6 #include "ppapi/c/pp_errors.h"
7 #include "ppapi/c/ppb_file_io.h"
8 #include "ppapi/c/ppb_file_ref.h"
9 #include "ppapi/c/ppb_file_system.h"
10 #include "ppapi/proxy/file_system_resource.h"
11 #include "ppapi/proxy/locking_resource_releaser.h"
12 #include "ppapi/proxy/plugin_message_filter.h"
13 #include "ppapi/proxy/ppapi_message_utils.h"
14 #include "ppapi/proxy/ppapi_messages.h"
15 #include "ppapi/proxy/ppapi_proxy_test.h"
16 #include "ppapi/shared_impl/proxy_lock.h"
17 #include "ppapi/shared_impl/scoped_pp_var.h"
18 #include "ppapi/shared_impl/var.h"
19 #include "ppapi/thunk/enter.h"
20 #include "ppapi/thunk/ppb_file_system_api.h"
21 #include "ppapi/thunk/thunk.h"
23 using ppapi::proxy::ResourceMessageTestSink
;
24 using ppapi::thunk::EnterResource
;
25 using ppapi::thunk::PPB_FileSystem_API
;
32 const int64_t kExpectedFileSystemSize
= 100;
33 const int64_t kQuotaRequestAmount1
= 10;
34 const int64_t kQuotaRequestAmount2
= 20;
36 class MockCompletionCallback
{
38 MockCompletionCallback() : called_(false) {}
40 bool called() { return called_
; }
41 int32_t result() { return result_
; }
43 static void Callback(void* user_data
, int32_t result
) {
44 MockCompletionCallback
* that
=
45 reinterpret_cast<MockCompletionCallback
*>(user_data
);
47 that
->result_
= result
;
55 class MockRequestQuotaCallback
{
57 MockRequestQuotaCallback() : called_(false) {}
59 bool called() { return called_
; }
60 int64_t result() { return result_
; }
62 void Reset() { called_
= false; }
64 void Callback(int64_t result
) {
65 ASSERT_FALSE(called_
);
75 class FileSystemResourceTest
: public PluginProxyTest
{
77 FileSystemResourceTest() {}
79 void SendReply(const ResourceMessageCallParams
& params
,
81 const IPC::Message
& nested_message
) {
82 ResourceMessageReplyParams
reply_params(params
.pp_resource(),
84 reply_params
.set_result(result
);
85 PluginMessageFilter::DispatchResourceReplyForTest(
86 reply_params
, nested_message
);
89 void SendOpenReply(const ResourceMessageCallParams
& params
, int32_t result
) {
90 SendReply(params
, result
, PpapiPluginMsg_FileSystem_OpenReply());
93 // Opens the given file system.
94 void OpenFileSystem(PP_Resource file_system
) {
95 MockCompletionCallback cb
;
96 int32_t result
= thunk::GetPPB_FileSystem_1_0_Thunk()->Open(
98 kExpectedFileSystemSize
,
99 PP_MakeCompletionCallback(&MockCompletionCallback::Callback
, &cb
));
100 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
102 // Should have sent two new "open" messages to the browser and renderer.
103 ResourceMessageTestSink::ResourceCallVector open_messages
=
104 sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID
);
105 ASSERT_EQ(2U, open_messages
.size());
106 sink().ClearMessages();
108 // The resource is expecting two replies.
109 SendOpenReply(open_messages
[0].first
, PP_OK
);
110 SendOpenReply(open_messages
[1].first
, PP_OK
);
112 ASSERT_TRUE(cb
.called());
113 ASSERT_EQ(PP_OK
, cb
.result());
116 // Opens the given file in the given file system. Since there is no host,
117 // the file handle will be invalid.
118 void OpenFile(PP_Resource file_io
,
119 PP_Resource file_ref
,
120 PP_Resource file_system
) {
121 MockCompletionCallback cb
;
122 int32_t result
= thunk::GetPPB_FileIO_1_1_Thunk()->Open(
125 PP_FILEOPENFLAG_WRITE
,
126 PP_MakeCompletionCallback(&MockCompletionCallback::Callback
, &cb
));
127 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
129 // Should have sent an "open" message.
130 ResourceMessageCallParams params
;
132 ASSERT_TRUE(sink().GetFirstResourceCallMatching(
133 PpapiHostMsg_FileIO_Open::ID
, ¶ms
, &msg
));
134 sink().ClearMessages();
136 // Send a success reply.
137 ResourceMessageReplyParams
reply_params(params
.pp_resource(),
139 reply_params
.set_result(PP_OK
);
140 PluginMessageFilter::DispatchResourceReplyForTest(
142 PpapiPluginMsg_FileIO_OpenReply(file_system
,
143 0 /* max_written_offset */));
149 // Test that Open fails if either host returns failure. The other tests exercise
150 // the case where both hosts return PP_OK.
151 TEST_F(FileSystemResourceTest
, OpenFailure
) {
152 const PPB_FileSystem_1_0
* fs_iface
= thunk::GetPPB_FileSystem_1_0_Thunk();
153 // Fail if the first reply doesn't return PP_OK.
155 LockingResourceReleaser
file_system(
156 fs_iface
->Create(pp_instance(), PP_FILESYSTEMTYPE_LOCALTEMPORARY
));
158 MockCompletionCallback cb
;
159 int32_t result
= thunk::GetPPB_FileSystem_1_0_Thunk()->Open(
161 kExpectedFileSystemSize
,
162 PP_MakeCompletionCallback(&MockCompletionCallback::Callback
, &cb
));
163 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
165 ResourceMessageTestSink::ResourceCallVector open_messages
=
166 sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID
);
167 ASSERT_EQ(2U, open_messages
.size());
168 sink().ClearMessages();
170 SendOpenReply(open_messages
[0].first
, PP_ERROR_FAILED
);
171 SendOpenReply(open_messages
[1].first
, PP_OK
);
173 ASSERT_TRUE(cb
.called());
174 ASSERT_EQ(PP_ERROR_FAILED
, cb
.result());
176 // Fail if the second reply doesn't return PP_OK.
178 LockingResourceReleaser
file_system(
179 fs_iface
->Create(pp_instance(), PP_FILESYSTEMTYPE_LOCALTEMPORARY
));
181 MockCompletionCallback cb
;
182 int32_t result
= thunk::GetPPB_FileSystem_1_0_Thunk()->Open(
184 kExpectedFileSystemSize
,
185 PP_MakeCompletionCallback(&MockCompletionCallback::Callback
, &cb
));
186 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
188 ResourceMessageTestSink::ResourceCallVector open_messages
=
189 sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID
);
190 ASSERT_EQ(2U, open_messages
.size());
191 sink().ClearMessages();
193 SendOpenReply(open_messages
[0].first
, PP_OK
);
194 SendOpenReply(open_messages
[1].first
, PP_ERROR_FAILED
);
196 ASSERT_TRUE(cb
.called());
197 ASSERT_EQ(PP_ERROR_FAILED
, cb
.result());
201 TEST_F(FileSystemResourceTest
, RequestQuota
) {
202 const PPB_FileSystem_1_0
* fs_iface
= thunk::GetPPB_FileSystem_1_0_Thunk();
203 const PPB_FileRef_1_1
* fref_iface
= thunk::GetPPB_FileRef_1_1_Thunk();
204 const PPB_FileIO_1_1
* fio_iface
= thunk::GetPPB_FileIO_1_1_Thunk();
206 LockingResourceReleaser
file_system(
207 fs_iface
->Create(pp_instance(), PP_FILESYSTEMTYPE_LOCALTEMPORARY
));
209 OpenFileSystem(file_system
.get());
211 // Create and open two files in the file system. FileIOResource calls
212 // FileSystemResource::OpenQuotaFile on success.
213 LockingResourceReleaser
file_ref1(
214 fref_iface
->Create(file_system
.get(), "/file1"));
215 LockingResourceReleaser
file_io1(fio_iface
->Create(pp_instance()));
216 OpenFile(file_io1
.get(), file_ref1
.get(), file_system
.get());
217 LockingResourceReleaser
file_ref2(
218 fref_iface
->Create(file_system
.get(), "/file2"));
219 LockingResourceReleaser
file_io2(fio_iface
->Create(pp_instance()));
220 OpenFile(file_io2
.get(), file_ref2
.get(), file_system
.get());
222 EnterResource
<PPB_FileSystem_API
> enter(file_system
.get(), true);
223 ASSERT_FALSE(enter
.failed());
224 PPB_FileSystem_API
* file_system_api
= enter
.object();
226 MockRequestQuotaCallback cb1
;
227 int64_t result
= file_system_api
->RequestQuota(
228 kQuotaRequestAmount1
,
229 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb1
)));
230 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
232 // Should have sent a "reserve quota" message, with the amount of the request
233 // and a map of all currently open files to their max written offsets.
234 ResourceMessageCallParams params
;
236 ASSERT_TRUE(sink().GetFirstResourceCallMatching(
237 PpapiHostMsg_FileSystem_ReserveQuota::ID
, ¶ms
, &msg
));
238 sink().ClearMessages();
241 FileGrowthMap file_growths
;
242 ASSERT_TRUE(UnpackMessage
<PpapiHostMsg_FileSystem_ReserveQuota
>(
243 msg
, &amount
, &file_growths
));
244 ASSERT_EQ(kQuotaRequestAmount1
, amount
);
245 ASSERT_EQ(2U, file_growths
.size());
246 ASSERT_EQ(0, file_growths
[file_io1
.get()].max_written_offset
);
247 ASSERT_EQ(0, file_growths
[file_io2
.get()].max_written_offset
);
249 // Make another request while the "reserve quota" message is pending.
250 MockRequestQuotaCallback cb2
;
251 result
= file_system_api
->RequestQuota(
252 kQuotaRequestAmount2
,
253 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb2
)));
254 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
255 // No new "reserve quota" message should be sent while one is pending.
256 ASSERT_FALSE(sink().GetFirstResourceCallMatching(
257 PpapiHostMsg_FileSystem_ReserveQuota::ID
, ¶ms
, &msg
));
259 ProxyAutoUnlock unlock_to_prevent_deadlock
;
260 // Reply with quota reservation amount sufficient to cover both requests.
261 // Both callbacks should be called with the requests granted.
264 PpapiPluginMsg_FileSystem_ReserveQuotaReply(
265 kQuotaRequestAmount1
+ kQuotaRequestAmount2
,
266 FileGrowthMapToFileSizeMapForTesting(file_growths
)));
268 ASSERT_TRUE(cb1
.called());
269 ASSERT_EQ(kQuotaRequestAmount1
, cb1
.result());
270 ASSERT_TRUE(cb2
.called());
271 ASSERT_EQ(kQuotaRequestAmount2
, cb2
.result());
275 // All requests should fail when insufficient quota is returned to satisfy
276 // the first request.
277 result
= file_system_api
->RequestQuota(
278 kQuotaRequestAmount1
,
279 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb1
)));
280 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
281 result
= file_system_api
->RequestQuota(
282 kQuotaRequestAmount2
,
283 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb2
)));
284 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
286 ASSERT_TRUE(sink().GetFirstResourceCallMatching(
287 PpapiHostMsg_FileSystem_ReserveQuota::ID
, ¶ms
, &msg
));
288 sink().ClearMessages();
290 ProxyAutoUnlock unlock_to_prevent_deadlock
;
291 // Reply with quota reservation amount insufficient to cover the first
295 PpapiPluginMsg_FileSystem_ReserveQuotaReply(
296 kQuotaRequestAmount1
- 1,
297 FileGrowthMapToFileSizeMapForTesting(file_growths
)));
299 ASSERT_TRUE(cb1
.called());
300 ASSERT_EQ(0, cb1
.result());
301 ASSERT_TRUE(cb2
.called());
302 ASSERT_EQ(0, cb2
.result());
306 // A new request should be made if the quota reservation is enough to satisfy
307 // at least one request.
308 result
= file_system_api
->RequestQuota(
309 kQuotaRequestAmount1
,
310 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb1
)));
311 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
312 result
= file_system_api
->RequestQuota(
313 kQuotaRequestAmount2
,
314 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb2
)));
315 ASSERT_EQ(PP_OK_COMPLETIONPENDING
, result
);
317 ASSERT_TRUE(sink().GetFirstResourceCallMatching(
318 PpapiHostMsg_FileSystem_ReserveQuota::ID
, ¶ms
, &msg
));
319 sink().ClearMessages();
321 ProxyAutoUnlock unlock_to_prevent_deadlock
;
322 // Reply with quota reservation amount sufficient only to cover the first
326 PpapiPluginMsg_FileSystem_ReserveQuotaReply(
327 kQuotaRequestAmount1
,
328 FileGrowthMapToFileSizeMapForTesting(file_growths
)));
330 ASSERT_TRUE(cb1
.called());
331 ASSERT_EQ(kQuotaRequestAmount1
, cb1
.result());
332 ASSERT_FALSE(cb2
.called());
334 // Another request message should have been sent.
335 ASSERT_TRUE(sink().GetFirstResourceCallMatching(
336 PpapiHostMsg_FileSystem_ReserveQuota::ID
, ¶ms
, &msg
));
337 sink().ClearMessages();
339 ProxyAutoUnlock unlock_to_prevent_deadlock
;
340 // Reply with quota reservation amount sufficient to cover the second
341 // request and some extra.
344 PpapiPluginMsg_FileSystem_ReserveQuotaReply(
345 kQuotaRequestAmount1
+ kQuotaRequestAmount2
,
346 FileGrowthMapToFileSizeMapForTesting(file_growths
)));
349 ASSERT_TRUE(cb2
.called());
350 ASSERT_EQ(kQuotaRequestAmount2
, cb2
.result());
354 // There is kQuotaRequestAmount1 of quota left, and a request for it should
355 // succeed immediately.
356 result
= file_system_api
->RequestQuota(
357 kQuotaRequestAmount1
,
358 base::Bind(&MockRequestQuotaCallback::Callback
, base::Unretained(&cb1
)));
359 ASSERT_EQ(kQuotaRequestAmount1
, result
);