Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(664)

Side by Side Diff: content/browser/renderer_host/websocket_blob_sender_unittest.cc

Issue 1568523002: Implement content::WebSocketBlobSender (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@websocket_blob_send_ipcs
Patch Set: Minor fixes. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 "content/browser/renderer_host/websocket_blob_sender.h"
6
7 #include <string.h>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/callback.h"
12 #include "base/files/file.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/location.h"
17 #include "base/memory/weak_ptr.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/run_loop.h"
20 #include "base/task_runner.h"
21 #include "base/time/time.h"
22 #include "content/browser/fileapi/chrome_blob_storage_context.h"
23 #include "content/public/browser/blob_handle.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "content/public/test/test_browser_context.h"
27 #include "content/public/test/test_browser_thread_bundle.h"
28 #include "net/base/net_errors.h"
29 #include "storage/common/fileapi/file_system_types.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31 #include "url/gurl.h"
32
33 namespace content {
34
35 namespace {
36
37 const char kDummyUrl[] = "http://www.example.com/";
38 const char kBanana[] = "banana";
39
40 // This is small so that the tests do not waste too much time just copying bytes
41 // around. But it has to be larger than kMinimumNonFinalFrameSize defined in
42 // websocket_blob_sender.cc.
43 const int kInitialQuota = 16 * 1024;
44
45 // A fake channel for testing. Records the contents of the message that was sent
46 // through it. Quota is restricted, and is refreshed asynchronously in response
47 // to calls to SendFrame().
48 class FakeChannel : public WebSocketBlobSender::Channel {
49 public:
50 // |notify_new_quota| will be run asynchronously on the current MessageLoop
51 // every time GetSendQuota() increases.
52 FakeChannel() : weak_factory_(this) {}
53
54 // This method must be called before SendFrame() is.
55 void set_notify_new_quota(const base::Closure& notify_new_quota) {
56 notify_new_quota_ = notify_new_quota;
57 }
58
59 int GetSendQuota() const override { return current_send_quota_; }
60
61 ChannelState SendFrame(bool fin, const std::vector<char>& data) override {
62 ++frames_sent_;
63 EXPECT_FALSE(got_fin_);
64 if (fin)
65 got_fin_ = true;
66 EXPECT_LE(data.size(), static_cast<size_t>(current_send_quota_));
67 message_.insert(message_.end(), data.begin(), data.end());
68 current_send_quota_ -= data.size();
69 base::MessageLoop::current()->PostTask(
70 FROM_HERE,
71 base::Bind(&FakeChannel::RefreshQuota, weak_factory_.GetWeakPtr()));
72 return net::WebSocketEventInterface::CHANNEL_ALIVE;
73 }
74
75 bool got_fin() const { return got_fin_; }
76
77 int frames_sent() const { return frames_sent_; }
78
79 const std::vector<char>& message() const { return message_; }
80
81 private:
82 void RefreshQuota() {
83 if (current_send_quota_ == kInitialQuota)
84 return;
85 current_send_quota_ = kInitialQuota;
86 DCHECK(!notify_new_quota_.is_null());
87 notify_new_quota_.Run();
88 }
89
90 base::Closure notify_new_quota_;
91 int current_send_quota_ = kInitialQuota;
92 int frames_sent_ = 0;
93 bool got_fin_ = false;
94 std::vector<char> message_;
95 base::WeakPtrFactory<FakeChannel> weak_factory_;
96 };
97
98 class WebSocketBlobSenderTest : public ::testing::Test {
99 protected:
100 // The Windows implementation of net::FileStream::Context requires a real IO
101 // MessageLoop.
102 WebSocketBlobSenderTest()
103 : threads_(TestBrowserThreadBundle::IO_MAINLOOP),
104 chrome_blob_storage_context_(
105 ChromeBlobStorageContext::GetFor(&browser_context_)),
106 fake_channel_(nullptr),
107 sender_() {}
108 ~WebSocketBlobSenderTest() override {}
109
110 void SetUp() override {
111 // ChromeBlobStorageContext::GetFor() does some work asynchronously.
112 base::RunLoop().RunUntilIdle();
113 SetUpSender();
114 }
115
116 // This method can be overriden to use a different channel implementation.
117 virtual void SetUpSender() {
118 fake_channel_ = new FakeChannel;
119 sender_.reset(new WebSocketBlobSender(make_scoped_ptr(fake_channel_)));
120 fake_channel_->set_notify_new_quota(base::Bind(
121 &WebSocketBlobSender::OnNewSendQuota, base::Unretained(sender_.get())));
122 }
123
124 storage::BlobStorageContext* context() {
125 return chrome_blob_storage_context_->context();
126 }
127
128 storage::FileSystemContext* GetFileSystemContext() {
129 StoragePartition* partition = BrowserContext::GetStoragePartitionForSite(
130 &browser_context_, GURL(kDummyUrl));
131 return partition->GetFileSystemContext();
132 }
133
134 // |string| is copied.
135 scoped_ptr<BlobHandle> CreateMemoryBackedBlob(const char* string) {
136 scoped_ptr<BlobHandle> handle =
137 chrome_blob_storage_context_->CreateMemoryBackedBlob(string,
138 strlen(string));
139 EXPECT_TRUE(handle);
140 return handle;
141 }
142
143 // Call sender_.Start() with the other parameters filled in appropriately for
144 // this test fixture.
145 int Start(const std::string& uuid,
146 uint64_t expected_size,
147 net::CompletionCallback callback) {
148 net::WebSocketEventInterface::ChannelState channel_state =
149 net::WebSocketEventInterface::CHANNEL_ALIVE;
150 return sender_->Start(
151 uuid, expected_size, context(), GetFileSystemContext(),
152 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
153 &channel_state, callback);
154 }
155
156 void NotCalledCallbackImpl(int rv) {
157 ADD_FAILURE()
158 << "Callback that should not be called was called with argument " << rv;
159 }
160
161 net::CompletionCallback NotCalled() {
162 return base::Bind(&WebSocketBlobSenderTest::NotCalledCallbackImpl,
163 base::Unretained(this));
164 }
165
166 void ExpectOkAndQuit(base::RunLoop* run_loop, int result) {
167 EXPECT_EQ(net::OK, result);
168 run_loop->Quit();
169 }
170
171 net::CompletionCallback ExpectOkAndQuitCallback(base::RunLoop* run_loop) {
172 return base::Bind(&WebSocketBlobSenderTest::ExpectOkAndQuit,
173 base::Unretained(this), run_loop);
174 }
175
176 TestBrowserThreadBundle threads_;
177 TestBrowserContext browser_context_;
178 scoped_refptr<ChromeBlobStorageContext> chrome_blob_storage_context_;
179 // |fake_channel_| is owned by |sender_|.
180 FakeChannel* fake_channel_;
181 scoped_ptr<WebSocketBlobSender> sender_;
182 };
183
184 TEST_F(WebSocketBlobSenderTest, Construction) {}
185
186 TEST_F(WebSocketBlobSenderTest, EmptyBlob) {
187 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob("");
188
189 // The APIs allow for this to be asynchronous but that is unlikely in
190 // practice.
191 int result = Start(handle->GetUUID(), UINT64_C(0), NotCalled());
192 // If this fails with result == -1, someone has changed the code to be
193 // asynchronous and this test should be adapted to match.
194 EXPECT_EQ(net::OK, result);
195 EXPECT_TRUE(fake_channel_->got_fin());
196 EXPECT_EQ(0U, fake_channel_->message().size());
197 }
198
199 TEST_F(WebSocketBlobSenderTest, SmallBlob) {
200 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(kBanana);
201
202 EXPECT_EQ(net::OK, Start(handle->GetUUID(), UINT64_C(6), NotCalled()));
203 EXPECT_TRUE(fake_channel_->got_fin());
204 EXPECT_EQ(1, fake_channel_->frames_sent());
205 EXPECT_EQ(std::vector<char>(kBanana, kBanana + 6), fake_channel_->message());
206 }
207
208 TEST_F(WebSocketBlobSenderTest, SizeMismatch) {
209 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(kBanana);
210
211 EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED,
212 Start(handle->GetUUID(), UINT64_C(5), NotCalled()));
213 EXPECT_EQ(0, fake_channel_->frames_sent());
214 }
215
216 TEST_F(WebSocketBlobSenderTest, InvalidUUID) {
217 EXPECT_EQ(net::ERR_INVALID_HANDLE,
218 Start("sandwich", UINT64_C(0), NotCalled()));
219 }
220
221 TEST_F(WebSocketBlobSenderTest, LargeMessage) {
222 std::string message(kInitialQuota + 10, 'a');
223 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
224
225 base::RunLoop run_loop;
226 int rv = Start(handle->GetUUID(), message.size(),
227 ExpectOkAndQuitCallback(&run_loop));
228 EXPECT_EQ(net::ERR_IO_PENDING, rv);
229 EXPECT_EQ(1, fake_channel_->frames_sent());
230 run_loop.Run();
231 EXPECT_EQ(2, fake_channel_->frames_sent());
232 EXPECT_TRUE(fake_channel_->got_fin());
233 std::vector<char> expected_message(message.begin(), message.end());
234 EXPECT_EQ(expected_message, fake_channel_->message());
235 }
236
237 // A message exactly equal to the available quota should be sent in one frame.
238 TEST_F(WebSocketBlobSenderTest, ExactSizeMessage) {
239 std::string message(kInitialQuota, 'a');
240 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
241
242 EXPECT_EQ(net::OK, Start(handle->GetUUID(), message.size(), NotCalled()));
243 EXPECT_EQ(1, fake_channel_->frames_sent());
244 EXPECT_TRUE(fake_channel_->got_fin());
245 EXPECT_EQ(static_cast<size_t>(kInitialQuota),
246 fake_channel_->message().size());
247 }
248
249 // If the connection is closed while sending a message, the WebSocketBlobSender
250 // object will be destroyed. It needs to handle this case without error.
251 TEST_F(WebSocketBlobSenderTest, AbortedSend) {
252 std::string message(kInitialQuota + 10, 'a');
253 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
254
255 int rv = Start(handle->GetUUID(), message.size(), NotCalled());
256 EXPECT_EQ(net::ERR_IO_PENDING, rv);
257 sender_.reset();
258 }
259
260 // This is very similar to net::TestCompletionCallback, except simpler and
261 // usable from //content.
262 class TestCompletionCallback {
263 public:
264 TestCompletionCallback()
265 : callback_(base::Bind(&TestCompletionCallback::SetResult,
266 base::Unretained(this))) {}
267
268 // Wait for |callback_| to be called and then return the value that was passed
269 // to it. Can only be called once.
270 int WaitForResult() {
271 run_loop_.Run();
272 return result_;
273 }
274
275 // If |result| is ERR_IO_PENDING, then wait for |callback_| to be called and
276 // return the value that was passed to it. Otherwise, return |result|.
277 // This permits writing tests that work regardless of whether they complete
278 // synchronously or not. Can only be called once.
279 int GetResult(int result) {
280 if (result != net::ERR_IO_PENDING)
281 return result;
282 return WaitForResult();
283 }
284
285 // The callback to pass to Start().
286 const net::CompletionCallback& callback() { return callback_; }
287
288 private:
289 void SetResult(int result) {
290 result_ = result;
291 run_loop_.Quit();
292 }
293
294 base::RunLoop run_loop_;
295 int result_ = net::ERR_IO_PENDING;
296 net::CompletionCallback callback_;
297
298 DISALLOW_COPY_AND_ASSIGN(TestCompletionCallback);
299 };
300
301 // Invalid file-backed blob.
302 TEST_F(WebSocketBlobSenderTest, InvalidFileBackedBlob) {
303 base::FilePath path(FILE_PATH_LITERAL(
304 "WebSocketBlobSentTest.InvalidFileBackedBlob.NonExistentFile"));
305 scoped_ptr<BlobHandle> handle =
306 chrome_blob_storage_context_->CreateFileBackedBlob(path, 0u, 32u,
307 base::Time::Now());
308 EXPECT_TRUE(handle);
309
310 TestCompletionCallback callback;
311 int rv =
312 callback.GetResult(Start(handle->GetUUID(), 5u, callback.callback()));
313 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, rv);
314 }
315
316 // A test fixture that does the additional work necessary to create working
317 // file-backed blobs.
318 class WebSocketFileBackedBlobSenderTest : public WebSocketBlobSenderTest {
319 protected:
320 void SetUp() override {
321 WebSocketBlobSenderTest::SetUp();
322 // temp_dir_ is recursively deleted on destruction.
323 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
324 }
325
326 void CreateFile(const std::string& contents,
327 const base::FilePath& path,
328 base::File::Info* info) {
329 ASSERT_EQ(contents.size(), static_cast<size_t>(base::WriteFile(
330 path, contents.data(), contents.size())));
331 ASSERT_TRUE(base::GetFileInfo(path, info));
332 }
333
334 scoped_ptr<BlobHandle> CreateFileBackedBlob(const std::string& contents) {
335 base::FilePath path = temp_dir_.path().AppendASCII("blob.dat");
336 base::File::Info info;
337 CreateFile(contents, path, &info);
338 if (HasFatalFailure())
339 return nullptr;
340 return chrome_blob_storage_context_->CreateFileBackedBlob(
341 path, 0u, contents.size(), info.last_modified);
342 }
343
344 base::ScopedTempDir temp_dir_;
345 };
346
347 TEST_F(WebSocketFileBackedBlobSenderTest, EmptyBlob) {
348 scoped_ptr<BlobHandle> handle = CreateFileBackedBlob("");
349 ASSERT_TRUE(handle);
350
351 TestCompletionCallback callback;
352 int result = callback.GetResult(
353 Start(handle->GetUUID(), UINT64_C(0), callback.callback()));
354 EXPECT_EQ(net::OK, result);
355 EXPECT_TRUE(fake_channel_->got_fin());
356 EXPECT_EQ(0U, fake_channel_->message().size());
357 }
358
359 TEST_F(WebSocketFileBackedBlobSenderTest, SizeMismatch) {
360 scoped_ptr<BlobHandle> handle = CreateFileBackedBlob(kBanana);
361 ASSERT_TRUE(handle);
362
363 TestCompletionCallback callback;
364 int result = Start(handle->GetUUID(), UINT64_C(8), callback.callback());
365 // This test explicitly aims to test the asynchronous code path, otherwise it
366 // would be identical to the other SizeMismatch test above.
367 EXPECT_EQ(net::ERR_IO_PENDING, result);
368 EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, callback.WaitForResult());
369 EXPECT_EQ(0, fake_channel_->frames_sent());
370 }
371
372 TEST_F(WebSocketFileBackedBlobSenderTest, LargeMessage) {
373 std::string message = "the green potato had lunch with the angry cat. ";
374 while (message.size() <= kInitialQuota) {
375 message = message + message;
376 }
377 scoped_ptr<BlobHandle> handle = CreateFileBackedBlob(message);
378 ASSERT_TRUE(handle);
379
380 TestCompletionCallback callback;
381 int result = Start(handle->GetUUID(), message.size(), callback.callback());
382 EXPECT_EQ(net::OK, callback.GetResult(result));
383 std::vector<char> expected_message(message.begin(), message.end());
384 EXPECT_EQ(expected_message, fake_channel_->message());
385 }
386
387 // The WebSocketBlobSender needs to handle a connection close while doing file
388 // IO cleanly.
389 TEST_F(WebSocketFileBackedBlobSenderTest, Aborted) {
390 scoped_ptr<BlobHandle> handle = CreateFileBackedBlob(kBanana);
391
392 int rv = Start(handle->GetUUID(), UINT64_C(6), NotCalled());
393 EXPECT_EQ(net::ERR_IO_PENDING, rv);
394 sender_.reset();
395 }
396
397 class DeletingFakeChannel : public WebSocketBlobSender::Channel {
398 public:
399 explicit DeletingFakeChannel(
400 scoped_ptr<WebSocketBlobSender>* sender_to_delete)
401 : sender_(sender_to_delete) {}
402
403 int GetSendQuota() const override { return kInitialQuota; }
404
405 ChannelState SendFrame(bool fin, const std::vector<char>& data) override {
406 sender_->reset();
407 // |this| is deleted here.
408 return net::WebSocketEventInterface::CHANNEL_DELETED;
409 }
410
411 private:
412 scoped_ptr<WebSocketBlobSender>* sender_;
413 };
414
415 class WebSocketBlobSenderDeletingTest : public WebSocketBlobSenderTest {
416 protected:
417 void SetUpSender() override {
418 sender_.reset(new WebSocketBlobSender(
419 make_scoped_ptr(new DeletingFakeChannel(&sender_))));
420 }
421 };
422
423 // This test only does something useful when run under AddressSanitizer or a
424 // similar tool that can detect use-after-free bugs.
425 TEST_F(WebSocketBlobSenderDeletingTest, SenderDeleted) {
426 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(kBanana);
427
428 EXPECT_EQ(net::ERR_CONNECTION_RESET,
429 Start(handle->GetUUID(), UINT64_C(6), NotCalled()));
430 EXPECT_FALSE(sender_);
431 }
432
433 // SendFrame() calls OnSendNewQuota() synchronously while filling the operating
434 // system's socket write buffer. The purpose of this Channel implementation is
435 // to verify that the synchronous case works correctly.
436 class SynchronousFakeChannel : public WebSocketBlobSender::Channel {
437 public:
438 // This method must be called before SendFrame() is.
439 void set_notify_new_quota(const base::Closure& notify_new_quota) {
440 notify_new_quota_ = notify_new_quota;
441 }
442
443 int GetSendQuota() const override { return kInitialQuota; }
444
445 ChannelState SendFrame(bool fin, const std::vector<char>& data) override {
446 message_.insert(message_.end(), data.begin(), data.end());
447 notify_new_quota_.Run();
448 return net::WebSocketEventInterface::CHANNEL_ALIVE;
449 }
450
451 const std::vector<char>& message() const { return message_; }
452
453 private:
454 base::Closure notify_new_quota_;
455 std::vector<char> message_;
456 };
457
458 class WebSocketBlobSenderSynchronousTest : public WebSocketBlobSenderTest {
459 protected:
460 void SetUpSender() override {
461 synchronous_fake_channel_ = new SynchronousFakeChannel;
462 sender_.reset(
463 new WebSocketBlobSender(make_scoped_ptr(synchronous_fake_channel_)));
464 synchronous_fake_channel_->set_notify_new_quota(base::Bind(
465 &WebSocketBlobSender::OnNewSendQuota, base::Unretained(sender_.get())));
466 }
467
468 SynchronousFakeChannel* synchronous_fake_channel_ = nullptr;
469 };
470
471 TEST_F(WebSocketBlobSenderSynchronousTest, LargeMessage) {
472 std::string message(kInitialQuota + 10, 'a');
473 scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
474
475 int rv = Start(handle->GetUUID(), message.size(), NotCalled());
476 EXPECT_EQ(net::OK, rv);
477 std::vector<char> expected_message(message.begin(), message.end());
478 EXPECT_EQ(expected_message, synchronous_fake_channel_->message());
479 }
480
481 } // namespace
482
483 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698