OLD | NEW |
| (Empty) |
1 // Copyright 2015 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 "files/public/cpp/input_stream_file.h" | |
6 | |
7 #include <string.h> | |
8 | |
9 #include <string> | |
10 #include <utility> | |
11 | |
12 #include "files/public/interfaces/files.mojom.h" | |
13 #include "files/public/interfaces/types.mojom.h" | |
14 #include "mojo/public/cpp/application/application_test_base.h" | |
15 #include "mojo/public/cpp/bindings/interface_request.h" | |
16 #include "mojo/public/cpp/environment/logging.h" | |
17 #include "mojo/public/cpp/system/macros.h" | |
18 #include "mojo/public/cpp/utility/run_loop.h" | |
19 | |
20 namespace files_impl { | |
21 namespace { | |
22 | |
23 using InputStreamFileTest = mojo::test::ApplicationTestBase; | |
24 | |
25 void QuitMessageLoop() { | |
26 mojo::RunLoop::current()->Quit(); | |
27 } | |
28 | |
29 void RunMessageLoop() { | |
30 mojo::RunLoop::current()->Run(); | |
31 } | |
32 | |
33 void RunMessageLoopUntilIdle() { | |
34 mojo::RunLoop::current()->RunUntilIdle(); | |
35 } | |
36 | |
37 void PostTaskToMessageLoop(const mojo::Closure& task) { | |
38 mojo::RunLoop::current()->PostDelayedTask(task, 0); | |
39 } | |
40 | |
41 // Converts a string to a |mojo::Array<uint8_t>|. | |
42 mojo::Array<uint8_t> StringToArray(const std::string& s) { | |
43 auto rv = mojo::Array<uint8_t>::New(s.size()); | |
44 if (s.size()) | |
45 memcpy(&rv[0], &s[0], s.size()); | |
46 return rv; | |
47 } | |
48 | |
49 // Converts a |mojo::Array<uint8_t>| to a string. If the array is null, returns | |
50 // the string "ARRAY_IS_NULL". | |
51 std::string ArrayToString(const mojo::Array<uint8_t>& a) { | |
52 if (a.is_null()) | |
53 return std::string("ARRAY_IS_NULL"); | |
54 return a.size() ? std::string(reinterpret_cast<const char*>(&a[0]), a.size()) | |
55 : std::string(); | |
56 } | |
57 | |
58 class TestClient : public InputStreamFile::Client { | |
59 public: | |
60 TestClient() { Reset(); } | |
61 ~TestClient() override {} | |
62 | |
63 // Note: This doesn't reset |callback_|. | |
64 void Reset() { | |
65 data_ = StringToArray("OOPS"); | |
66 complete_synchronously_ = true; | |
67 got_request_data_ = false; | |
68 got_on_closed_ = false; | |
69 } | |
70 | |
71 // Completes a pending callback. Note: |RequestData()| may be called again | |
72 // "inside" this (i.e., "inside" the callback). | |
73 void RunRequestDataCallback(mojo::Array<uint8_t> data) { | |
74 MOJO_CHECK(!callback_.is_null()); | |
75 RequestDataCallback callback; | |
76 std::swap(callback, callback_); | |
77 callback.Run(mojo::files::Error::OK, data.Pass()); | |
78 } | |
79 | |
80 void set_data(mojo::Array<uint8_t> data) { | |
81 MOJO_CHECK(!data.is_null()); | |
82 data_ = data.Pass(); | |
83 } | |
84 void set_complete_synchronously(bool complete_synchronously) { | |
85 complete_synchronously_ = complete_synchronously; | |
86 } | |
87 | |
88 bool got_request_data() const { return got_request_data_; } | |
89 bool got_on_closed() const { return got_on_closed_; } | |
90 | |
91 private: | |
92 // |InputStreamFile::Client|: | |
93 bool RequestData(size_t max_num_bytes, | |
94 mojo::files::Error* error, | |
95 mojo::Array<uint8_t>* data, | |
96 const RequestDataCallback& callback) override { | |
97 MOJO_CHECK(max_num_bytes); | |
98 MOJO_CHECK(error); | |
99 MOJO_CHECK(data); | |
100 MOJO_CHECK(!callback.is_null()); | |
101 | |
102 // This shouldn't be called while a callback is pending. | |
103 MOJO_CHECK(callback_.is_null()); | |
104 | |
105 got_request_data_ = true; | |
106 QuitMessageLoop(); | |
107 | |
108 if (!complete_synchronously_) { | |
109 callback_ = callback; | |
110 return false; | |
111 } | |
112 | |
113 *error = mojo::files::Error::OK; | |
114 *data = data_.Clone(); | |
115 return true; | |
116 } | |
117 void OnClosed() override { | |
118 got_on_closed_ = true; | |
119 QuitMessageLoop(); | |
120 } | |
121 | |
122 mojo::Array<uint8_t> data_; | |
123 bool complete_synchronously_; | |
124 | |
125 bool got_request_data_; | |
126 bool got_on_closed_; | |
127 | |
128 RequestDataCallback callback_; | |
129 | |
130 MOJO_DISALLOW_COPY_AND_ASSIGN(TestClient); | |
131 }; | |
132 | |
133 void TestReadSync(mojo::files::File* file, | |
134 TestClient* client, | |
135 const std::string& s) { | |
136 bool read_cb_called = false; | |
137 mojo::files::Error error = mojo::files::Error::INTERNAL; | |
138 mojo::Array<uint8_t> data; | |
139 file->Read(100u, 0, mojo::files::Whence::FROM_CURRENT, | |
140 [&read_cb_called, &error, &data](mojo::files::Error e, | |
141 mojo::Array<uint8_t> d) { | |
142 read_cb_called = true; | |
143 error = e; | |
144 data = d.Pass(); | |
145 QuitMessageLoop(); | |
146 }); | |
147 if (client) { | |
148 // If there's a client, since we're running everything on one thread, the | |
149 // impl (which will call the client, which will quit the message loop) will | |
150 // get called before the callback. | |
151 client->Reset(); | |
152 client->set_data(StringToArray(s)); | |
153 RunMessageLoop(); | |
154 EXPECT_TRUE(client->got_request_data()); | |
155 EXPECT_FALSE(client->got_on_closed()); | |
156 EXPECT_FALSE(read_cb_called); | |
157 // Spin the message loop again to get the callback. | |
158 client->Reset(); | |
159 RunMessageLoop(); | |
160 EXPECT_FALSE(client->got_request_data()); | |
161 EXPECT_FALSE(client->got_on_closed()); | |
162 EXPECT_TRUE(read_cb_called); | |
163 EXPECT_EQ(mojo::files::Error::OK, error); | |
164 EXPECT_EQ(s, ArrayToString(data)); | |
165 } else { | |
166 // Otherwise, only the read callback will be called and quit the message | |
167 // loop. | |
168 RunMessageLoop(); | |
169 EXPECT_TRUE(read_cb_called); | |
170 EXPECT_EQ(mojo::files::Error::UNAVAILABLE, error); | |
171 EXPECT_TRUE(data.is_null()); | |
172 } | |
173 } | |
174 | |
175 void TestReadAsync(mojo::files::File* file, | |
176 TestClient* client, | |
177 const std::string& s) { | |
178 MOJO_CHECK(client); | |
179 | |
180 bool read_cb_called = false; | |
181 mojo::files::Error error = mojo::files::Error::INTERNAL; | |
182 mojo::Array<uint8_t> data; | |
183 file->Read(100u, 0, mojo::files::Whence::FROM_CURRENT, | |
184 [&read_cb_called, &error, &data](mojo::files::Error e, | |
185 mojo::Array<uint8_t> d) { | |
186 read_cb_called = true; | |
187 error = e; | |
188 data = d.Pass(); | |
189 QuitMessageLoop(); | |
190 }); | |
191 client->Reset(); | |
192 client->set_complete_synchronously(false); | |
193 RunMessageLoop(); | |
194 EXPECT_TRUE(client->got_request_data()); | |
195 EXPECT_FALSE(client->got_on_closed()); | |
196 EXPECT_FALSE(read_cb_called); | |
197 // The read callback won't get called until we tell the client to run its | |
198 // callback. | |
199 client->Reset(); | |
200 client->RunRequestDataCallback(StringToArray(s)); | |
201 EXPECT_FALSE(client->got_request_data()); | |
202 EXPECT_FALSE(client->got_on_closed()); | |
203 EXPECT_FALSE(read_cb_called); | |
204 // Spin the message loop again to get the read callback. | |
205 client->Reset(); | |
206 RunMessageLoop(); | |
207 EXPECT_FALSE(client->got_request_data()); | |
208 EXPECT_FALSE(client->got_on_closed()); | |
209 EXPECT_TRUE(read_cb_called); | |
210 EXPECT_EQ(mojo::files::Error::OK, error); | |
211 EXPECT_EQ(s, ArrayToString(data)); | |
212 } | |
213 | |
214 void TestClose(mojo::files::File* file, TestClient* client) { | |
215 bool close_cb_called = false; | |
216 mojo::files::Error error = mojo::files::Error::INTERNAL; | |
217 file->Close([&close_cb_called, &error](mojo::files::Error e) { | |
218 close_cb_called = true; | |
219 error = e; | |
220 QuitMessageLoop(); | |
221 }); | |
222 if (client) { | |
223 // (This is analogous to |TestReadSync()|.) | |
224 client->Reset(); | |
225 RunMessageLoop(); | |
226 EXPECT_FALSE(client->got_request_data()); | |
227 EXPECT_TRUE(client->got_on_closed()); | |
228 EXPECT_FALSE(close_cb_called); | |
229 client->Reset(); | |
230 RunMessageLoop(); | |
231 EXPECT_FALSE(client->got_request_data()); | |
232 EXPECT_FALSE(client->got_on_closed()); | |
233 } else { | |
234 RunMessageLoop(); | |
235 } | |
236 EXPECT_TRUE(close_cb_called); | |
237 EXPECT_EQ(mojo::files::Error::OK, error); | |
238 } | |
239 | |
240 TEST_F(InputStreamFileTest, BasicSync) { | |
241 mojo::files::FilePtr file; | |
242 TestClient client; | |
243 std::unique_ptr<InputStreamFile> file_impl = | |
244 InputStreamFile::Create(&client, GetProxy(&file)); | |
245 | |
246 TestReadSync(file.get(), &client, "hello"); | |
247 TestReadSync(file.get(), &client, "world"); | |
248 TestClose(file.get(), &client); | |
249 } | |
250 | |
251 TEST_F(InputStreamFileTest, BasicAsync) { | |
252 mojo::files::FilePtr file; | |
253 TestClient client; | |
254 std::unique_ptr<InputStreamFile> file_impl = | |
255 InputStreamFile::Create(&client, GetProxy(&file)); | |
256 | |
257 TestReadAsync(file.get(), &client, "hello"); | |
258 TestReadAsync(file.get(), &client, "world"); | |
259 TestClose(file.get(), &client); | |
260 } | |
261 | |
262 TEST_F(InputStreamFileTest, SetClient) { | |
263 mojo::files::FilePtr file; | |
264 TestClient client1; | |
265 std::unique_ptr<InputStreamFile> file_impl = | |
266 InputStreamFile::Create(&client1, GetProxy(&file)); | |
267 | |
268 TestReadSync(file.get(), &client1, "hello"); | |
269 | |
270 TestClient client2; | |
271 file_impl->set_client(&client2); | |
272 TestReadSync(file.get(), &client2, "world"); | |
273 | |
274 file_impl->set_client(&client1); | |
275 TestReadAsync(file.get(), &client1, "!"); | |
276 TestClose(file.get(), &client1); | |
277 } | |
278 | |
279 TEST_F(InputStreamFileTest, NullClient) { | |
280 mojo::files::FilePtr file; | |
281 std::unique_ptr<InputStreamFile> file_impl = | |
282 InputStreamFile::Create(nullptr, GetProxy(&file)); | |
283 | |
284 TestReadSync(file.get(), nullptr, "hello"); | |
285 TestClose(file.get(), nullptr); | |
286 } | |
287 | |
288 TEST_F(InputStreamFileTest, SetNullClient) { | |
289 mojo::files::FilePtr file; | |
290 TestClient client; | |
291 std::unique_ptr<InputStreamFile> file_impl = | |
292 InputStreamFile::Create(&client, GetProxy(&file)); | |
293 | |
294 TestReadSync(file.get(), &client, "hello"); | |
295 | |
296 file_impl->set_client(nullptr); | |
297 client.Reset(); | |
298 TestReadSync(file.get(), nullptr, "hello"); | |
299 TestClose(file.get(), nullptr); | |
300 EXPECT_FALSE(client.got_request_data()); | |
301 EXPECT_FALSE(client.got_on_closed()); | |
302 } | |
303 | |
304 TEST_F(InputStreamFileTest, ImplOnlyClosesMessagePipeOnDestruction) { | |
305 mojo::files::FilePtr file; | |
306 std::unique_ptr<InputStreamFile> file_impl = | |
307 InputStreamFile::Create(nullptr, GetProxy(&file)); | |
308 bool got_connection_error = false; | |
309 file.set_connection_error_handler([&got_connection_error]() { | |
310 got_connection_error = true; | |
311 QuitMessageLoop(); | |
312 }); | |
313 | |
314 TestClose(file.get(), nullptr); | |
315 // The impl should only close its end when it's destroyed (even if |Close()| | |
316 // has been called). | |
317 RunMessageLoopUntilIdle(); | |
318 EXPECT_FALSE(got_connection_error); | |
319 file_impl.reset(); | |
320 RunMessageLoop(); | |
321 EXPECT_TRUE(got_connection_error); | |
322 } | |
323 | |
324 TEST_F(InputStreamFileTest, ClosingMessagePipeCausesOnClosed) { | |
325 mojo::files::FilePtr file; | |
326 TestClient client; | |
327 std::unique_ptr<InputStreamFile> file_impl = | |
328 InputStreamFile::Create(&client, GetProxy(&file)); | |
329 | |
330 file.reset(); | |
331 RunMessageLoop(); | |
332 EXPECT_FALSE(client.got_request_data()); | |
333 EXPECT_TRUE(client.got_on_closed()); | |
334 } | |
335 | |
336 // Clients may own the impl (and this is a typical pattern). This client will | |
337 // own/destroy its impl on any |Client| call (and we'll test that this doesn't | |
338 // result in any additional calls to the client). | |
339 class TestClientDestroysImplClient : public InputStreamFile::Client { | |
340 public: | |
341 explicit TestClientDestroysImplClient( | |
342 mojo::InterfaceRequest<mojo::files::File> request) | |
343 : file_impl_(InputStreamFile::Create(this, request.Pass())) {} | |
344 ~TestClientDestroysImplClient() override {} | |
345 | |
346 private: | |
347 // InputStreamFile::Client|: | |
348 bool RequestData(size_t /*max_num_bytes*/, | |
349 mojo::files::Error* /*error*/, | |
350 mojo::Array<uint8_t>* /*data*/, | |
351 const RequestDataCallback& /*callback*/) override { | |
352 // We reset the impl on any call, and afterwards it shouldn't call us. | |
353 EXPECT_TRUE(file_impl_); | |
354 file_impl_.reset(); | |
355 return true; | |
356 } | |
357 void OnClosed() override { | |
358 // We reset the impl on any call, and afterwards it shouldn't call us. | |
359 EXPECT_TRUE(file_impl_); | |
360 file_impl_.reset(); | |
361 } | |
362 | |
363 std::unique_ptr<InputStreamFile> file_impl_; | |
364 | |
365 MOJO_DISALLOW_COPY_AND_ASSIGN(TestClientDestroysImplClient); | |
366 }; | |
367 | |
368 TEST_F(InputStreamFileTest, ClientDestroysImpl) { | |
369 // Test destruction due to reading. | |
370 { | |
371 mojo::files::FilePtr file; | |
372 TestClientDestroysImplClient client(GetProxy(&file)); | |
373 bool got_connection_error = false; | |
374 file.set_connection_error_handler([&got_connection_error]() { | |
375 got_connection_error = true; | |
376 QuitMessageLoop(); | |
377 }); | |
378 // If the impl is destroyed while trying to answer a read, it doesn't | |
379 // respond. | |
380 // TODO(vtl): I'm not sure if this is the best behavior. Maybe it should | |
381 // respond with an error? | |
382 file->Read(100u, 0, mojo::files::Whence::FROM_CURRENT, | |
383 [](mojo::files::Error, mojo::Array<uint8_t>) { | |
384 MOJO_CHECK(false) << "Not reached"; | |
385 }); | |
386 RunMessageLoop(); | |
387 EXPECT_TRUE(got_connection_error); | |
388 } | |
389 | |
390 // Test destruction due to closing. | |
391 { | |
392 mojo::files::FilePtr file; | |
393 TestClientDestroysImplClient client(GetProxy(&file)); | |
394 bool got_connection_error = false; | |
395 file.set_connection_error_handler([&got_connection_error]() { | |
396 got_connection_error = true; | |
397 QuitMessageLoop(); | |
398 }); | |
399 TestClose(file.get(), nullptr); | |
400 if (!got_connection_error) | |
401 RunMessageLoop(); | |
402 EXPECT_TRUE(got_connection_error); | |
403 } | |
404 } | |
405 | |
406 // This responds synchronously to any (non-zero-byte) read with a single byte, | |
407 // starting with 0 and incrementing each time. | |
408 class TestClientFifoSync : public InputStreamFile::Client { | |
409 public: | |
410 explicit TestClientFifoSync(mojo::InterfaceRequest<mojo::files::File> request) | |
411 : file_impl_(InputStreamFile::Create(this, request.Pass())), | |
412 next_byte_(0u) {} | |
413 ~TestClientFifoSync() override {} | |
414 | |
415 private: | |
416 // InputStreamFile::Client|: | |
417 bool RequestData(size_t max_num_bytes, | |
418 mojo::files::Error* error, | |
419 mojo::Array<uint8_t>* data, | |
420 const RequestDataCallback& callback) override { | |
421 *error = mojo::files::Error::OK; | |
422 data->resize(1); | |
423 (*data)[0] = next_byte_++; | |
424 return true; | |
425 } | |
426 void OnClosed() override {} | |
427 | |
428 std::unique_ptr<InputStreamFile> file_impl_; | |
429 uint8_t next_byte_; | |
430 | |
431 MOJO_DISALLOW_COPY_AND_ASSIGN(TestClientFifoSync); | |
432 }; | |
433 | |
434 // Like |TestClientFifoSync|, but asynchronous. | |
435 class TestClientFifoAsync : public InputStreamFile::Client { | |
436 public: | |
437 explicit TestClientFifoAsync( | |
438 mojo::InterfaceRequest<mojo::files::File> request) | |
439 : file_impl_(InputStreamFile::Create(this, request.Pass())), | |
440 next_byte_(0u) {} | |
441 ~TestClientFifoAsync() override {} | |
442 | |
443 private: | |
444 // InputStreamFile::Client|: | |
445 bool RequestData(size_t max_num_bytes, | |
446 mojo::files::Error* error, | |
447 mojo::Array<uint8_t>* data, | |
448 const RequestDataCallback& callback) override { | |
449 PostTaskToMessageLoop([this, callback]() { | |
450 mojo::Array<uint8_t> data; | |
451 data.push_back(next_byte_++); | |
452 callback.Run(mojo::files::Error::OK, data.Pass()); | |
453 }); | |
454 return false; | |
455 } | |
456 void OnClosed() override {} | |
457 | |
458 std::unique_ptr<InputStreamFile> file_impl_; | |
459 uint8_t next_byte_; | |
460 | |
461 MOJO_DISALLOW_COPY_AND_ASSIGN(TestClientFifoAsync); | |
462 }; | |
463 | |
464 void TestFifo(mojo::files::File* file) { | |
465 int expect_callback = 0; | |
466 uint8_t expect_byte = 0; | |
467 | |
468 // Test a couple of zero-byte reads. | |
469 file->Read(0u, 0, mojo::files::Whence::FROM_CURRENT, | |
470 [&expect_callback](mojo::files::Error e, mojo::Array<uint8_t> a) { | |
471 EXPECT_EQ(0, expect_callback++); | |
472 EXPECT_EQ(mojo::files::Error::OK, e); | |
473 EXPECT_EQ(0u, a.size()); | |
474 }); | |
475 file->Read(0u, 0, mojo::files::Whence::FROM_CURRENT, | |
476 [&expect_callback](mojo::files::Error e, mojo::Array<uint8_t> a) { | |
477 EXPECT_EQ(1, expect_callback++); | |
478 EXPECT_EQ(mojo::files::Error::OK, e); | |
479 EXPECT_EQ(0u, a.size()); | |
480 }); | |
481 | |
482 // Test a couple of non-zero-byte reads. | |
483 file->Read(100u, 0, mojo::files::Whence::FROM_CURRENT, | |
484 [&expect_callback, &expect_byte](mojo::files::Error e, | |
485 mojo::Array<uint8_t> a) { | |
486 EXPECT_EQ(2, expect_callback++); | |
487 EXPECT_EQ(mojo::files::Error::OK, e); | |
488 EXPECT_EQ(1u, a.size()); | |
489 EXPECT_EQ(expect_byte++, a[0]); | |
490 }); | |
491 file->Read(100u, 0, mojo::files::Whence::FROM_CURRENT, | |
492 [&expect_callback, &expect_byte](mojo::files::Error e, | |
493 mojo::Array<uint8_t> a) { | |
494 EXPECT_EQ(3, expect_callback++); | |
495 EXPECT_EQ(mojo::files::Error::OK, e); | |
496 EXPECT_EQ(1u, a.size()); | |
497 EXPECT_EQ(expect_byte++, a[0]); | |
498 }); | |
499 // Throw in a zero-byte read. | |
500 file->Read(0u, 0, mojo::files::Whence::FROM_CURRENT, | |
501 [&expect_callback](mojo::files::Error e, mojo::Array<uint8_t> a) { | |
502 EXPECT_EQ(4, expect_callback++); | |
503 EXPECT_EQ(mojo::files::Error::OK, e); | |
504 EXPECT_EQ(0u, a.size()); | |
505 }); | |
506 // And a final non-zero-byte read (we'll quit the message loop). | |
507 file->Read(100u, 0, mojo::files::Whence::FROM_CURRENT, | |
508 [&expect_callback, &expect_byte](mojo::files::Error e, | |
509 mojo::Array<uint8_t> a) { | |
510 EXPECT_EQ(5, expect_callback++); | |
511 EXPECT_EQ(mojo::files::Error::OK, e); | |
512 EXPECT_EQ(1u, a.size()); | |
513 EXPECT_EQ(expect_byte++, a[0]); | |
514 QuitMessageLoop(); | |
515 }); | |
516 RunMessageLoop(); | |
517 | |
518 // Check that all the callbacks ran. | |
519 EXPECT_EQ(6, expect_callback); | |
520 EXPECT_EQ(3u, expect_byte); | |
521 } | |
522 | |
523 // Tests that if multiple reads are sent, they are completed in FIFO order. This | |
524 // tests the synchronous completion path. | |
525 TEST_F(InputStreamFileTest, FifoSync) { | |
526 mojo::files::FilePtr file; | |
527 TestClientFifoSync client(GetProxy(&file)); | |
528 TestFifo(file.get()); | |
529 } | |
530 | |
531 // Like |InputStreamFileTest.FifoSync|, but asynchronous. | |
532 TEST_F(InputStreamFileTest, FifoAsync) { | |
533 mojo::files::FilePtr file; | |
534 TestClientFifoAsync client(GetProxy(&file)); | |
535 TestFifo(file.get()); | |
536 } | |
537 | |
538 } // namespace | |
539 } // namespace files_impl | |
OLD | NEW |