1 // Copyright 2013 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 <string> 6 #include <vector> 7 8 #include "base/basictypes.h" 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/run_loop.h" 14 #include "content/public/test/async_file_test_helper.h" 15 #include "content/public/test/test_file_system_context.h" 16 #include "net/base/io_buffer.h" 17 #include "net/base/request_priority.h" 18 #include "net/url_request/url_request.h" 19 #include "net/url_request/url_request_context.h" 20 #include "net/url_request/url_request_job.h" 21 #include "net/url_request/url_request_job_factory.h" 22 #include "net/url_request/url_request_status.h" 23 #include "storage/browser/fileapi/file_system_context.h" 24 #include "storage/browser/fileapi/file_system_quota_util.h" 25 #include "storage/browser/fileapi/file_writer_delegate.h" 26 #include "storage/browser/fileapi/sandbox_file_stream_writer.h" 27 #include "testing/platform_test.h" 28 #include "url/gurl.h" 29 30 using content::AsyncFileTestHelper; 31 using storage::FileSystemURL; 32 using storage::FileWriterDelegate; 33 34 namespace content { 35 36 namespace { 37 38 const GURL kOrigin("http://example.com"); 39 const storage::FileSystemType kFileSystemType = storage::kFileSystemTypeTest; 40 41 const char kData[] = "The quick brown fox jumps over the lazy dog.\n"; 42 const int kDataSize = ARRAYSIZE_UNSAFE(kData) - 1; 43 44 class Result { 45 public: 46 Result() 47 : status_(base::File::FILE_OK), 48 bytes_written_(0), 49 write_status_(FileWriterDelegate::SUCCESS_IO_PENDING) {} 50 51 base::File::Error status() const { return status_; } 52 int64 bytes_written() const { return bytes_written_; } 53 FileWriterDelegate::WriteProgressStatus write_status() const { 54 return write_status_; 55 } 56 57 void DidWrite(base::File::Error status, int64 bytes, 58 FileWriterDelegate::WriteProgressStatus write_status) { 59 write_status_ = write_status; 60 if (status == base::File::FILE_OK) { 61 bytes_written_ += bytes; 62 if (write_status_ != FileWriterDelegate::SUCCESS_IO_PENDING) 63 base::MessageLoop::current()->Quit(); 64 } else { 65 EXPECT_EQ(base::File::FILE_OK, status_); 66 status_ = status; 67 base::MessageLoop::current()->Quit(); 68 } 69 } 70 71 private: 72 // For post-operation status. 73 base::File::Error status_; 74 int64 bytes_written_; 75 FileWriterDelegate::WriteProgressStatus write_status_; 76 }; 77 78 class BlobURLRequestJobFactory; 79 80 } // namespace (anonymous) 81 82 class FileWriterDelegateTest : public PlatformTest { 83 public: 84 FileWriterDelegateTest() {} 85 86 protected: 87 virtual void SetUp() OVERRIDE; 88 virtual void TearDown() OVERRIDE; 89 90 int64 usage() { 91 return file_system_context_->GetQuotaUtil(kFileSystemType) 92 ->GetOriginUsageOnFileTaskRunner( 93 file_system_context_.get(), kOrigin, kFileSystemType); 94 } 95 96 int64 GetFileSizeOnDisk(const char* test_file_path) { 97 // There might be in-flight flush/write. 98 base::MessageLoop::current()->PostTask( 99 FROM_HERE, base::Bind(&base::DoNothing)); 100 base::RunLoop().RunUntilIdle(); 101 102 FileSystemURL url = GetFileSystemURL(test_file_path); 103 base::File::Info file_info; 104 EXPECT_EQ(base::File::FILE_OK, 105 AsyncFileTestHelper::GetMetadata( 106 file_system_context_.get(), url, &file_info)); 107 return file_info.size; 108 } 109 110 FileSystemURL GetFileSystemURL(const char* file_name) const { 111 return file_system_context_->CreateCrackedFileSystemURL( 112 kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name)); 113 } 114 115 FileWriterDelegate* CreateWriterDelegate( 116 const char* test_file_path, 117 int64 offset, 118 int64 allowed_growth) { 119 storage::SandboxFileStreamWriter* writer = 120 new storage::SandboxFileStreamWriter( 121 file_system_context_.get(), 122 GetFileSystemURL(test_file_path), 123 offset, 124 *file_system_context_->GetUpdateObservers(kFileSystemType)); 125 writer->set_default_quota(allowed_growth); 126 return new FileWriterDelegate(scoped_ptr<storage::FileStreamWriter>(writer), 127 FileWriterDelegate::FLUSH_ON_COMPLETION); 128 } 129 130 FileWriterDelegate::DelegateWriteCallback GetWriteCallback(Result* result) { 131 return base::Bind(&Result::DidWrite, base::Unretained(result)); 132 } 133 134 // Creates and sets up a FileWriterDelegate for writing the given |blob_url|, 135 // and creates a new FileWriterDelegate for the file. 136 void PrepareForWrite(const char* test_file_path, 137 const GURL& blob_url, 138 int64 offset, 139 int64 allowed_growth) { 140 file_writer_delegate_.reset( 141 CreateWriterDelegate(test_file_path, offset, allowed_growth)); 142 request_ = empty_context_.CreateRequest( 143 blob_url, net::DEFAULT_PRIORITY, file_writer_delegate_.get(), NULL); 144 } 145 146 // This should be alive until the very end of this instance. 147 base::MessageLoopForIO loop_; 148 149 scoped_refptr<storage::FileSystemContext> file_system_context_; 150 151 net::URLRequestContext empty_context_; 152 scoped_ptr<FileWriterDelegate> file_writer_delegate_; 153 scoped_ptr<net::URLRequest> request_; 154 scoped_ptr<BlobURLRequestJobFactory> job_factory_; 155 156 base::ScopedTempDir dir_; 157 158 static const char* content_; 159 }; 160 161 const char* FileWriterDelegateTest::content_ = NULL; 162 163 namespace { 164 165 static std::string g_content; 166 167 class FileWriterDelegateTestJob : public net::URLRequestJob { 168 public: 169 FileWriterDelegateTestJob(net::URLRequest* request, 170 net::NetworkDelegate* network_delegate, 171 const std::string& content) 172 : net::URLRequestJob(request, network_delegate), 173 content_(content), 174 remaining_bytes_(content.length()), 175 cursor_(0) { 176 } 177 178 virtual void Start() OVERRIDE { 179 base::MessageLoop::current()->PostTask( 180 FROM_HERE, 181 base::Bind(&FileWriterDelegateTestJob::NotifyHeadersComplete, this)); 182 } 183 184 virtual bool ReadRawData(net::IOBuffer* buf, 185 int buf_size, 186 int *bytes_read) OVERRIDE { 187 if (remaining_bytes_ < buf_size) 188 buf_size = static_cast<int>(remaining_bytes_); 189 190 for (int i = 0; i < buf_size; ++i) 191 buf->data()[i] = content_[cursor_++]; 192 remaining_bytes_ -= buf_size; 193 194 SetStatus(net::URLRequestStatus()); 195 *bytes_read = buf_size; 196 return true; 197 } 198 199 virtual int GetResponseCode() const OVERRIDE { 200 return 200; 201 } 202 203 protected: 204 virtual ~FileWriterDelegateTestJob() {} 205 206 private: 207 std::string content_; 208 int remaining_bytes_; 209 int cursor_; 210 }; 211 212 class BlobURLRequestJobFactory : public net::URLRequestJobFactory { 213 public: 214 explicit BlobURLRequestJobFactory(const char** content_data) 215 : content_data_(content_data) { 216 } 217 218 virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler( 219 const std::string& scheme, 220 net::URLRequest* request, 221 net::NetworkDelegate* network_delegate) const OVERRIDE { 222 return new FileWriterDelegateTestJob( 223 request, network_delegate, *content_data_); 224 } 225 226 virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE { 227 return scheme == "blob"; 228 } 229 230 virtual bool IsHandledURL(const GURL& url) const OVERRIDE { 231 return url.SchemeIs("blob"); 232 } 233 234 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 235 return true; 236 } 237 238 private: 239 const char** content_data_; 240 241 DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJobFactory); 242 }; 243 244 } // namespace (anonymous) 245 246 void FileWriterDelegateTest::SetUp() { 247 ASSERT_TRUE(dir_.CreateUniqueTempDir()); 248 249 file_system_context_ = CreateFileSystemContextForTesting( 250 NULL, dir_.path()); 251 ASSERT_EQ(base::File::FILE_OK, 252 AsyncFileTestHelper::CreateFile(file_system_context_.get(), 253 GetFileSystemURL("test"))); 254 job_factory_.reset(new BlobURLRequestJobFactory(&content_)); 255 empty_context_.set_job_factory(job_factory_.get()); 256 } 257 258 void FileWriterDelegateTest::TearDown() { 259 file_system_context_ = NULL; 260 base::RunLoop().RunUntilIdle(); 261 } 262 263 TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) { 264 const GURL kBlobURL("blob:nolimit"); 265 content_ = kData; 266 267 PrepareForWrite("test", kBlobURL, 0, kint64max); 268 269 Result result; 270 ASSERT_EQ(0, usage()); 271 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 272 base::MessageLoop::current()->Run(); 273 274 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 275 file_writer_delegate_.reset(); 276 277 ASSERT_EQ(kDataSize, usage()); 278 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 279 EXPECT_EQ(kDataSize, result.bytes_written()); 280 EXPECT_EQ(base::File::FILE_OK, result.status()); 281 } 282 283 TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) { 284 const GURL kBlobURL("blob:just"); 285 content_ = kData; 286 const int64 kAllowedGrowth = kDataSize; 287 PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); 288 289 Result result; 290 ASSERT_EQ(0, usage()); 291 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 292 base::MessageLoop::current()->Run(); 293 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 294 file_writer_delegate_.reset(); 295 296 ASSERT_EQ(kAllowedGrowth, usage()); 297 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 298 299 EXPECT_EQ(kAllowedGrowth, result.bytes_written()); 300 EXPECT_EQ(base::File::FILE_OK, result.status()); 301 } 302 303 TEST_F(FileWriterDelegateTest, DISABLED_WriteFailureByQuota) { 304 const GURL kBlobURL("blob:failure"); 305 content_ = kData; 306 const int64 kAllowedGrowth = kDataSize - 1; 307 PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); 308 309 Result result; 310 ASSERT_EQ(0, usage()); 311 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 312 base::MessageLoop::current()->Run(); 313 ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); 314 file_writer_delegate_.reset(); 315 316 ASSERT_EQ(kAllowedGrowth, usage()); 317 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 318 319 EXPECT_EQ(kAllowedGrowth, result.bytes_written()); 320 EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, result.status()); 321 ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); 322 } 323 324 TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) { 325 const GURL kBlobURL("blob:zero"); 326 content_ = ""; 327 int64 kAllowedGrowth = 0; 328 PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); 329 330 Result result; 331 ASSERT_EQ(0, usage()); 332 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 333 base::MessageLoop::current()->Run(); 334 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 335 file_writer_delegate_.reset(); 336 337 ASSERT_EQ(kAllowedGrowth, usage()); 338 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 339 340 EXPECT_EQ(kAllowedGrowth, result.bytes_written()); 341 EXPECT_EQ(base::File::FILE_OK, result.status()); 342 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 343 } 344 345 TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimitConcurrent) { 346 scoped_ptr<FileWriterDelegate> file_writer_delegate2; 347 scoped_ptr<net::URLRequest> request2; 348 349 ASSERT_EQ(base::File::FILE_OK, 350 AsyncFileTestHelper::CreateFile(file_system_context_.get(), 351 GetFileSystemURL("test2"))); 352 353 const GURL kBlobURL("blob:nolimitconcurrent"); 354 const GURL kBlobURL2("blob:nolimitconcurrent2"); 355 content_ = kData; 356 357 PrepareForWrite("test", kBlobURL, 0, kint64max); 358 359 // Credate another FileWriterDelegate for concurrent write. 360 file_writer_delegate2.reset(CreateWriterDelegate("test2", 0, kint64max)); 361 request2 = empty_context_.CreateRequest( 362 kBlobURL2, net::DEFAULT_PRIORITY, file_writer_delegate2.get(), NULL); 363 364 Result result, result2; 365 ASSERT_EQ(0, usage()); 366 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 367 file_writer_delegate2->Start(request2.Pass(), GetWriteCallback(&result2)); 368 base::MessageLoop::current()->Run(); 369 if (result.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING || 370 result2.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING) 371 base::MessageLoop::current()->Run(); 372 373 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 374 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result2.write_status()); 375 file_writer_delegate_.reset(); 376 file_writer_delegate2.reset(); 377 378 ASSERT_EQ(kDataSize * 2, usage()); 379 EXPECT_EQ(GetFileSizeOnDisk("test") + GetFileSizeOnDisk("test2"), usage()); 380 381 EXPECT_EQ(kDataSize, result.bytes_written()); 382 EXPECT_EQ(base::File::FILE_OK, result.status()); 383 EXPECT_EQ(kDataSize, result2.bytes_written()); 384 EXPECT_EQ(base::File::FILE_OK, result2.status()); 385 } 386 387 TEST_F(FileWriterDelegateTest, WritesWithQuotaAndOffset) { 388 const GURL kBlobURL("blob:failure-with-updated-quota"); 389 content_ = kData; 390 391 // Writing kDataSize (=45) bytes data while allowed_growth is 100. 392 int64 offset = 0; 393 int64 allowed_growth = 100; 394 ASSERT_LT(kDataSize, allowed_growth); 395 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 396 397 { 398 Result result; 399 ASSERT_EQ(0, usage()); 400 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 401 base::MessageLoop::current()->Run(); 402 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 403 file_writer_delegate_.reset(); 404 405 ASSERT_EQ(kDataSize, usage()); 406 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 407 EXPECT_EQ(kDataSize, result.bytes_written()); 408 EXPECT_EQ(base::File::FILE_OK, result.status()); 409 } 410 411 // Trying to overwrite kDataSize bytes data while allowed_growth is 20. 412 offset = 0; 413 allowed_growth = 20; 414 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 415 416 { 417 Result result; 418 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 419 base::MessageLoop::current()->Run(); 420 EXPECT_EQ(kDataSize, usage()); 421 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 422 EXPECT_EQ(kDataSize, result.bytes_written()); 423 EXPECT_EQ(base::File::FILE_OK, result.status()); 424 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 425 } 426 427 // Trying to write kDataSize bytes data from offset 25 while 428 // allowed_growth is 55. 429 offset = 25; 430 allowed_growth = 55; 431 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 432 433 { 434 Result result; 435 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 436 base::MessageLoop::current()->Run(); 437 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 438 file_writer_delegate_.reset(); 439 440 EXPECT_EQ(offset + kDataSize, usage()); 441 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 442 EXPECT_EQ(kDataSize, result.bytes_written()); 443 EXPECT_EQ(base::File::FILE_OK, result.status()); 444 } 445 446 // Trying to overwrite 45 bytes data while allowed_growth is -20. 447 offset = 0; 448 allowed_growth = -20; 449 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 450 int64 pre_write_usage = GetFileSizeOnDisk("test"); 451 452 { 453 Result result; 454 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 455 base::MessageLoop::current()->Run(); 456 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 457 file_writer_delegate_.reset(); 458 459 EXPECT_EQ(pre_write_usage, usage()); 460 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 461 EXPECT_EQ(kDataSize, result.bytes_written()); 462 EXPECT_EQ(base::File::FILE_OK, result.status()); 463 } 464 465 // Trying to overwrite 45 bytes data with offset pre_write_usage - 20, 466 // while allowed_growth is 10. 467 const int kOverlap = 20; 468 offset = pre_write_usage - kOverlap; 469 allowed_growth = 10; 470 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 471 472 { 473 Result result; 474 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 475 base::MessageLoop::current()->Run(); 476 ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); 477 file_writer_delegate_.reset(); 478 479 EXPECT_EQ(pre_write_usage + allowed_growth, usage()); 480 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 481 EXPECT_EQ(kOverlap + allowed_growth, result.bytes_written()); 482 EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, result.status()); 483 } 484 } 485 486 } // namespace content 487