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