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

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

Powered by Google App Engine
This is Rietveld 408576698