OLD | NEW |
(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 "websocket_blob_receiver.h" |
| 6 |
| 7 #include <string> |
| 8 #include <utility> |
| 9 |
| 10 #include "base/run_loop.h" |
| 11 #include "base/strings/string_piece.h" |
| 12 #include "content/browser/fileapi/chrome_blob_storage_context.h" |
| 13 #include "content/public/browser/browser_context.h" |
| 14 #include "content/public/browser/browser_thread.h" |
| 15 #include "content/public/browser/storage_partition.h" |
| 16 #include "content/public/test/test_browser_context.h" |
| 17 #include "content/public/test/test_browser_thread_bundle.h" |
| 18 #include "net/base/completion_callback.h" |
| 19 #include "net/base/io_buffer.h" |
| 20 #include "net/base/net_errors.h" |
| 21 #include "net/base/test_completion_callback.h" |
| 22 #include "storage/browser/blob/blob_data_handle.h" |
| 23 #include "storage/browser/blob/blob_reader.h" |
| 24 #include "storage/browser/fileapi/file_system_context.h" |
| 25 #include "testing/gtest/include/gtest/gtest.h" |
| 26 #include "url/gurl.h" |
| 27 |
| 28 namespace content { |
| 29 |
| 30 namespace { |
| 31 |
| 32 using base::RunLoop; |
| 33 using base::WeakPtr; |
| 34 using net::TestCompletionCallback; |
| 35 using storage::BlobDataHandle; |
| 36 |
| 37 class FakeClient : public WebSocketBlobReceiver::Client { |
| 38 public: |
| 39 FakeClient() : weak_factory_(this) {} |
| 40 |
| 41 void BlobCreated(scoped_ptr<BlobDataHandle> blob_data_handle, |
| 42 uint64_t size) override { |
| 43 blob_data_handle_ = std::move(blob_data_handle); |
| 44 size_ = size; |
| 45 done_run_loop_.Quit(); |
| 46 } |
| 47 |
| 48 void BlobFailed(int net_error_code) override { |
| 49 failure_code_ = net_error_code; |
| 50 done_run_loop_.Quit(); |
| 51 } |
| 52 |
| 53 void AddFlowControlQuota(size_t quota) override { |
| 54 total_quota_ += quota; |
| 55 if (flow_control_run_loop_) |
| 56 flow_control_run_loop_->Quit(); |
| 57 } |
| 58 |
| 59 WeakPtr<FakeClient> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } |
| 60 |
| 61 const BlobDataHandle* blob_data_handle() const { |
| 62 return blob_data_handle_.get(); |
| 63 } |
| 64 |
| 65 uint64_t size() const { return size_; } |
| 66 |
| 67 int failure_code() const { return failure_code_; } |
| 68 |
| 69 void WaitUntilDone() { done_run_loop_.Run(); } |
| 70 |
| 71 size_t total_quota() const { return total_quota_; } |
| 72 |
| 73 // The point of this class is that it starts waiting at the point when it is |
| 74 // initialised. This means Wait() will always return, even if the flow control |
| 75 // is supplied before Wait() is called. |
| 76 class FlowControlWaiter { |
| 77 public: |
| 78 FlowControlWaiter(WeakPtr<FakeClient> fake_client) |
| 79 : fake_client_(fake_client) { |
| 80 fake_client->flow_control_run_loop_ = &run_loop_; |
| 81 } |
| 82 |
| 83 ~FlowControlWaiter() { |
| 84 if (fake_client_) |
| 85 fake_client_->flow_control_run_loop_ = nullptr; |
| 86 } |
| 87 |
| 88 void Wait() { |
| 89 run_loop_.Run(); |
| 90 if (fake_client_) |
| 91 fake_client_->flow_control_run_loop_ = nullptr; |
| 92 } |
| 93 |
| 94 private: |
| 95 WeakPtr<FakeClient> fake_client_; |
| 96 RunLoop run_loop_; |
| 97 |
| 98 DISALLOW_COPY_AND_ASSIGN(FlowControlWaiter); |
| 99 }; |
| 100 |
| 101 private: |
| 102 RunLoop done_run_loop_; |
| 103 RunLoop* flow_control_run_loop_; |
| 104 scoped_ptr<BlobDataHandle> blob_data_handle_; |
| 105 uint64_t size_ = 0; |
| 106 int failure_code_ = net::OK; |
| 107 size_t total_quota_ = 0; |
| 108 base::WeakPtrFactory<FakeClient> weak_factory_; |
| 109 |
| 110 DISALLOW_COPY_AND_ASSIGN(FakeClient); |
| 111 }; |
| 112 |
| 113 class WebSocketBlobReceiverTest : public ::testing::Test { |
| 114 protected: |
| 115 // The Windows implementation of net::FileStream::Context requires a real IO |
| 116 // MessageLoop. |
| 117 WebSocketBlobReceiverTest() |
| 118 : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), |
| 119 browser_context_(), |
| 120 chrome_blob_storage_context_( |
| 121 ChromeBlobStorageContext::GetFor(&browser_context_)) {} |
| 122 |
| 123 void SetUp() override { |
| 124 // Allow ChromeBlobStorageContext to initialise the BlobStorageContext on |
| 125 // the "IO thread". |
| 126 RunLoop().RunUntilIdle(); |
| 127 FakeClient* fake_client = new FakeClient; |
| 128 fake_client_ = fake_client->GetWeakPtr(); |
| 129 receiver_.reset(new WebSocketBlobReceiver( |
| 130 make_scoped_ptr(fake_client), chrome_blob_storage_context_->context())); |
| 131 } |
| 132 |
| 133 std::string BlobToString(const BlobDataHandle* blob_data_handle) { |
| 134 static const char kDummyUrl[] = "http://www.example.com/"; |
| 135 using storage::BlobReader; |
| 136 |
| 137 StoragePartition* partition = BrowserContext::GetStoragePartitionForSite( |
| 138 &browser_context_, GURL(kDummyUrl)); |
| 139 storage::FileSystemContext* file_system_context = |
| 140 partition->GetFileSystemContext(); |
| 141 scoped_ptr<BlobReader> blob_reader = blob_data_handle->CreateReader( |
| 142 file_system_context, |
| 143 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get()); |
| 144 |
| 145 TestCompletionCallback size_callback; |
| 146 switch (blob_reader->CalculateSize(size_callback.callback())) { |
| 147 case BlobReader::Status::DONE: |
| 148 break; |
| 149 |
| 150 case BlobReader::Status::IO_PENDING: |
| 151 EXPECT_EQ(net::OK, size_callback.WaitForResult()); |
| 152 break; |
| 153 |
| 154 case BlobReader::Status::NET_ERROR: |
| 155 ADD_FAILURE() << "BlobReader::CalculateSize returned error: " |
| 156 << net::ErrorToString(blob_reader->net_error()); |
| 157 break; |
| 158 } |
| 159 |
| 160 TestCompletionCallback read_callback; |
| 161 size_t dest_size = blob_reader->total_size(); |
| 162 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(dest_size)); |
| 163 int bytes_read = 0; |
| 164 switch (blob_reader->Read(buffer.get(), dest_size, &bytes_read, |
| 165 read_callback.callback())) { |
| 166 case BlobReader::Status::DONE: |
| 167 EXPECT_EQ(dest_size, static_cast<size_t>(bytes_read)); |
| 168 break; |
| 169 |
| 170 case BlobReader::Status::IO_PENDING: |
| 171 EXPECT_EQ(static_cast<int>(dest_size), read_callback.WaitForResult()); |
| 172 break; |
| 173 |
| 174 case BlobReader::Status::NET_ERROR: |
| 175 ADD_FAILURE() << "BlobReader::Read returned error: " |
| 176 << net::ErrorToString(blob_reader->net_error()); |
| 177 break; |
| 178 } |
| 179 |
| 180 return std::string(buffer->data(), buffer->data() + dest_size); |
| 181 } |
| 182 |
| 183 // Test for success, and provide a useful diagnostic if we don't have it. |
| 184 void ExpectSuccess() { |
| 185 EXPECT_EQ(net::OK, fake_client_->failure_code()) |
| 186 << net::ErrorToString(fake_client_->failure_code()); |
| 187 } |
| 188 |
| 189 // Start the WebSocketBlobReceiver, and wait for the temporary file to be |
| 190 // opened (indicated by quota being supplied). |
| 191 void StartAndWaitForOpen() { |
| 192 FakeClient::FlowControlWaiter await_open(fake_client_); |
| 193 receiver_->Start(); |
| 194 await_open.Wait(); |
| 195 } |
| 196 |
| 197 // Convert |data| to a vector and append it. Expect success. |data| must be |
| 198 // non-empty. |
| 199 void AppendDataSuccessfully(base::StringPiece data) { |
| 200 CHECK(!data.empty()); |
| 201 EXPECT_EQ(net::ERR_IO_PENDING, receiver_->AppendData(std::vector<char>( |
| 202 data.begin(), data.end()))); |
| 203 } |
| 204 |
| 205 // Finish, wait until completion, and expect success. |
| 206 void FinishAndWaitForSuccess() { |
| 207 EXPECT_EQ(net::ERR_IO_PENDING, receiver_->Finish()); |
| 208 fake_client_->WaitUntilDone(); |
| 209 ExpectSuccess(); |
| 210 } |
| 211 |
| 212 TestBrowserThreadBundle thread_bundle_; |
| 213 TestBrowserContext browser_context_; |
| 214 scoped_refptr<ChromeBlobStorageContext> chrome_blob_storage_context_; |
| 215 WeakPtr<FakeClient> fake_client_; |
| 216 scoped_ptr<WebSocketBlobReceiver> receiver_; |
| 217 }; |
| 218 |
| 219 TEST_F(WebSocketBlobReceiverTest, Construct) {} |
| 220 |
| 221 TEST_F(WebSocketBlobReceiverTest, SendEmptyBlob) { |
| 222 receiver_->Start(); |
| 223 |
| 224 EXPECT_EQ(net::OK, receiver_->AppendData(std::vector<char>())); |
| 225 |
| 226 EXPECT_EQ(net::ERR_IO_PENDING, receiver_->Finish()); |
| 227 fake_client_->WaitUntilDone(); |
| 228 |
| 229 ExpectSuccess(); |
| 230 EXPECT_EQ("", BlobToString(fake_client_->blob_data_handle())); |
| 231 } |
| 232 |
| 233 TEST_F(WebSocketBlobReceiverTest, SendNonEmptyBlob) { |
| 234 receiver_->Start(); |
| 235 |
| 236 // This violates the interface contract since no quota has been provided yet, |
| 237 // but WebSocketBlobReceiver doesn't strictly enforce its quota invariants |
| 238 // since the WebSocket wire implementation does that. |
| 239 EXPECT_EQ(net::ERR_IO_PENDING, |
| 240 receiver_->AppendData(std::vector<char>(4, 'a'))); |
| 241 |
| 242 FinishAndWaitForSuccess(); |
| 243 |
| 244 EXPECT_EQ("aaaa", BlobToString(fake_client_->blob_data_handle())); |
| 245 } |
| 246 |
| 247 TEST_F(WebSocketBlobReceiverTest, SendAfterOpenComplete) { |
| 248 StartAndWaitForOpen(); |
| 249 |
| 250 EXPECT_GT(fake_client_->total_quota(), 0u); |
| 251 |
| 252 AppendDataSuccessfully("bbbb"); |
| 253 |
| 254 FinishAndWaitForSuccess(); |
| 255 EXPECT_EQ("bbbb", BlobToString(fake_client_->blob_data_handle())); |
| 256 } |
| 257 |
| 258 TEST_F(WebSocketBlobReceiverTest, SendDuringWrite) { |
| 259 StartAndWaitForOpen(); |
| 260 |
| 261 AppendDataSuccessfully("cc"); |
| 262 AppendDataSuccessfully("dd"); |
| 263 |
| 264 FinishAndWaitForSuccess(); |
| 265 EXPECT_EQ("ccdd", BlobToString(fake_client_->blob_data_handle())); |
| 266 } |
| 267 |
| 268 TEST_F(WebSocketBlobReceiverTest, UsedQuotaIsReturned) { |
| 269 const size_t kBytesToUse = 8; |
| 270 StartAndWaitForOpen(); |
| 271 |
| 272 size_t initial_quota = fake_client_->total_quota(); |
| 273 std::string data(kBytesToUse, 'e'); |
| 274 |
| 275 // It isn't actually guaranteed that all the quota will be returned in one |
| 276 // call, but this should work in practice. |
| 277 FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
| 278 AppendDataSuccessfully(data); |
| 279 await_more_quota.Wait(); |
| 280 |
| 281 size_t new_quota = fake_client_->total_quota() - initial_quota; |
| 282 EXPECT_EQ(kBytesToUse, new_quota); |
| 283 |
| 284 FinishAndWaitForSuccess(); |
| 285 } |
| 286 |
| 287 TEST_F(WebSocketBlobReceiverTest, WaitForQuota) { |
| 288 StartAndWaitForOpen(); |
| 289 |
| 290 size_t initial_quota = fake_client_->total_quota(); |
| 291 std::string data1(initial_quota, 'w'); |
| 292 |
| 293 FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
| 294 AppendDataSuccessfully(data1); |
| 295 await_more_quota.Wait(); |
| 296 |
| 297 size_t new_quota = fake_client_->total_quota() - initial_quota; |
| 298 std::string data2(new_quota, 'f'); |
| 299 AppendDataSuccessfully(data2); |
| 300 |
| 301 FinishAndWaitForSuccess(); |
| 302 EXPECT_EQ(data1 + data2, BlobToString(fake_client_->blob_data_handle())); |
| 303 } |
| 304 |
| 305 TEST_F(WebSocketBlobReceiverTest, SizeIsCorrect) { |
| 306 const size_t kTotallyRandomNumber = 42; |
| 307 |
| 308 StartAndWaitForOpen(); |
| 309 |
| 310 std::string data(kTotallyRandomNumber, 'q'); |
| 311 AppendDataSuccessfully(data); |
| 312 |
| 313 FinishAndWaitForSuccess(); |
| 314 EXPECT_EQ(kTotallyRandomNumber, fake_client_->size()); |
| 315 } |
| 316 |
| 317 // Deleting the WebSocketBlobReceiver should cleanly abort all operations and |
| 318 // leak no memory or temporary files. |
| 319 TEST_F(WebSocketBlobReceiverTest, AbortAfterStart) { |
| 320 receiver_->Start(); |
| 321 receiver_.reset(); |
| 322 } |
| 323 |
| 324 TEST_F(WebSocketBlobReceiverTest, AbortAfterOpen) { |
| 325 StartAndWaitForOpen(); |
| 326 receiver_.reset(); |
| 327 } |
| 328 |
| 329 TEST_F(WebSocketBlobReceiverTest, AbortAfterAppend) { |
| 330 StartAndWaitForOpen(); |
| 331 AppendDataSuccessfully("banana"); |
| 332 receiver_.reset(); |
| 333 } |
| 334 |
| 335 TEST_F(WebSocketBlobReceiverTest, AbortDuringLargeWrite) { |
| 336 StartAndWaitForOpen(); |
| 337 size_t initial_quota = fake_client_->total_quota(); |
| 338 receiver_->AppendData(std::vector<char>(initial_quota, 'g')); |
| 339 receiver_.reset(); |
| 340 } |
| 341 |
| 342 TEST_F(WebSocketBlobReceiverTest, AbortAfterSendQuota) { |
| 343 StartAndWaitForOpen(); |
| 344 FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
| 345 AppendDataSuccessfully("orange"); |
| 346 await_more_quota.Wait(); |
| 347 receiver_.reset(); |
| 348 } |
| 349 |
| 350 TEST_F(WebSocketBlobReceiverTest, AbortAfterAppendAndFinish) { |
| 351 StartAndWaitForOpen(); |
| 352 AppendDataSuccessfully("apple"); |
| 353 receiver_->Finish(); |
| 354 receiver_.reset(); |
| 355 } |
| 356 |
| 357 // The difference of this test from AbortAfterAppendAndFinish is that it waits |
| 358 // for the write to complete before calling Finish(). |
| 359 TEST_F(WebSocketBlobReceiverTest, AbortDuringFinish) { |
| 360 StartAndWaitForOpen(); |
| 361 FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
| 362 AppendDataSuccessfully("kiwi"); |
| 363 await_more_quota.Wait(); |
| 364 receiver_->Finish(); |
| 365 receiver_.reset(); |
| 366 } |
| 367 |
| 368 } // namespace |
| 369 |
| 370 } // namespace content |
OLD | NEW |