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 "testing/platform_test.h" 24 #include "url/gurl.h" 25 #include "webkit/browser/fileapi/file_system_context.h" 26 #include "webkit/browser/fileapi/file_system_quota_util.h" 27 #include "webkit/browser/fileapi/file_writer_delegate.h" 28 #include "webkit/browser/fileapi/sandbox_file_stream_writer.h" 29 30 using content::AsyncFileTestHelper; 31 using fileapi::FileSystemURL; 32 using fileapi::FileWriterDelegate; 33 34 namespace content { 35 36 namespace { 37 38 const GURL kOrigin("http://example.com"); 39 const fileapi::FileSystemType kFileSystemType = fileapi::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_, 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 fileapi::SandboxFileStreamWriter* writer = 120 new fileapi::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( 127 scoped_ptr<fileapi::FileStreamWriter>(writer), 128 FileWriterDelegate::FLUSH_ON_COMPLETION); 129 } 130 131 FileWriterDelegate::DelegateWriteCallback GetWriteCallback(Result* result) { 132 return base::Bind(&Result::DidWrite, base::Unretained(result)); 133 } 134 135 // Creates and sets up a FileWriterDelegate for writing the given |blob_url|, 136 // and creates a new FileWriterDelegate for the file. 137 void PrepareForWrite(const char* test_file_path, 138 const GURL& blob_url, 139 int64 offset, 140 int64 allowed_growth) { 141 file_writer_delegate_.reset( 142 CreateWriterDelegate(test_file_path, offset, allowed_growth)); 143 request_ = empty_context_.CreateRequest( 144 blob_url, net::DEFAULT_PRIORITY, file_writer_delegate_.get(), NULL); 145 } 146 147 // This should be alive until the very end of this instance. 148 base::MessageLoopForIO loop_; 149 150 scoped_refptr<fileapi::FileSystemContext> file_system_context_; 151 152 net::URLRequestContext empty_context_; 153 scoped_ptr<FileWriterDelegate> file_writer_delegate_; 154 scoped_ptr<net::URLRequest> request_; 155 scoped_ptr<BlobURLRequestJobFactory> job_factory_; 156 157 base::ScopedTempDir dir_; 158 159 static const char* content_; 160 }; 161 162 const char* FileWriterDelegateTest::content_ = NULL; 163 164 namespace { 165 166 static std::string g_content; 167 168 class FileWriterDelegateTestJob : public net::URLRequestJob { 169 public: 170 FileWriterDelegateTestJob(net::URLRequest* request, 171 net::NetworkDelegate* network_delegate, 172 const std::string& content) 173 : net::URLRequestJob(request, network_delegate), 174 content_(content), 175 remaining_bytes_(content.length()), 176 cursor_(0) { 177 } 178 179 virtual void Start() OVERRIDE { 180 base::MessageLoop::current()->PostTask( 181 FROM_HERE, 182 base::Bind(&FileWriterDelegateTestJob::NotifyHeadersComplete, this)); 183 } 184 185 virtual bool ReadRawData(net::IOBuffer* buf, 186 int buf_size, 187 int *bytes_read) OVERRIDE { 188 if (remaining_bytes_ < buf_size) 189 buf_size = static_cast<int>(remaining_bytes_); 190 191 for (int i = 0; i < buf_size; ++i) 192 buf->data()[i] = content_[cursor_++]; 193 remaining_bytes_ -= buf_size; 194 195 SetStatus(net::URLRequestStatus()); 196 *bytes_read = buf_size; 197 return true; 198 } 199 200 virtual int GetResponseCode() const OVERRIDE { 201 return 200; 202 } 203 204 protected: 205 virtual ~FileWriterDelegateTestJob() {} 206 207 private: 208 std::string content_; 209 int remaining_bytes_; 210 int cursor_; 211 }; 212 213 class BlobURLRequestJobFactory : public net::URLRequestJobFactory { 214 public: 215 explicit BlobURLRequestJobFactory(const char** content_data) 216 : content_data_(content_data) { 217 } 218 219 virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler( 220 const std::string& scheme, 221 net::URLRequest* request, 222 net::NetworkDelegate* network_delegate) const OVERRIDE { 223 return new FileWriterDelegateTestJob( 224 request, network_delegate, *content_data_); 225 } 226 227 virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE { 228 return scheme == "blob"; 229 } 230 231 virtual bool IsHandledURL(const GURL& url) const OVERRIDE { 232 return url.SchemeIs("blob"); 233 } 234 235 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 236 return true; 237 } 238 239 private: 240 const char** content_data_; 241 242 DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJobFactory); 243 }; 244 245 } // namespace (anonymous) 246 247 void FileWriterDelegateTest::SetUp() { 248 ASSERT_TRUE(dir_.CreateUniqueTempDir()); 249 250 file_system_context_ = CreateFileSystemContextForTesting( 251 NULL, dir_.path()); 252 ASSERT_EQ(base::File::FILE_OK, 253 AsyncFileTestHelper::CreateFile( 254 file_system_context_, GetFileSystemURL("test"))); 255 job_factory_.reset(new BlobURLRequestJobFactory(&content_)); 256 empty_context_.set_job_factory(job_factory_.get()); 257 } 258 259 void FileWriterDelegateTest::TearDown() { 260 file_system_context_ = NULL; 261 base::RunLoop().RunUntilIdle(); 262 } 263 264 TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) { 265 const GURL kBlobURL("blob:nolimit"); 266 content_ = kData; 267 268 PrepareForWrite("test", kBlobURL, 0, kint64max); 269 270 Result result; 271 ASSERT_EQ(0, usage()); 272 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 273 base::MessageLoop::current()->Run(); 274 275 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 276 file_writer_delegate_.reset(); 277 278 ASSERT_EQ(kDataSize, usage()); 279 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 280 EXPECT_EQ(kDataSize, result.bytes_written()); 281 EXPECT_EQ(base::File::FILE_OK, result.status()); 282 } 283 284 TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) { 285 const GURL kBlobURL("blob:just"); 286 content_ = kData; 287 const int64 kAllowedGrowth = kDataSize; 288 PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); 289 290 Result result; 291 ASSERT_EQ(0, usage()); 292 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 293 base::MessageLoop::current()->Run(); 294 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 295 file_writer_delegate_.reset(); 296 297 ASSERT_EQ(kAllowedGrowth, usage()); 298 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 299 300 EXPECT_EQ(kAllowedGrowth, result.bytes_written()); 301 EXPECT_EQ(base::File::FILE_OK, result.status()); 302 } 303 304 TEST_F(FileWriterDelegateTest, DISABLED_WriteFailureByQuota) { 305 const GURL kBlobURL("blob:failure"); 306 content_ = kData; 307 const int64 kAllowedGrowth = kDataSize - 1; 308 PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); 309 310 Result result; 311 ASSERT_EQ(0, usage()); 312 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 313 base::MessageLoop::current()->Run(); 314 ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); 315 file_writer_delegate_.reset(); 316 317 ASSERT_EQ(kAllowedGrowth, usage()); 318 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 319 320 EXPECT_EQ(kAllowedGrowth, result.bytes_written()); 321 EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, result.status()); 322 ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); 323 } 324 325 TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) { 326 const GURL kBlobURL("blob:zero"); 327 content_ = ""; 328 int64 kAllowedGrowth = 0; 329 PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); 330 331 Result result; 332 ASSERT_EQ(0, usage()); 333 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 334 base::MessageLoop::current()->Run(); 335 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 336 file_writer_delegate_.reset(); 337 338 ASSERT_EQ(kAllowedGrowth, usage()); 339 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 340 341 EXPECT_EQ(kAllowedGrowth, result.bytes_written()); 342 EXPECT_EQ(base::File::FILE_OK, result.status()); 343 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 344 } 345 346 TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimitConcurrent) { 347 scoped_ptr<FileWriterDelegate> file_writer_delegate2; 348 scoped_ptr<net::URLRequest> request2; 349 350 ASSERT_EQ(base::File::FILE_OK, 351 AsyncFileTestHelper::CreateFile( 352 file_system_context_, GetFileSystemURL("test2"))); 353 354 const GURL kBlobURL("blob:nolimitconcurrent"); 355 const GURL kBlobURL2("blob:nolimitconcurrent2"); 356 content_ = kData; 357 358 PrepareForWrite("test", kBlobURL, 0, kint64max); 359 360 // Credate another FileWriterDelegate for concurrent write. 361 file_writer_delegate2.reset(CreateWriterDelegate("test2", 0, kint64max)); 362 request2 = empty_context_.CreateRequest( 363 kBlobURL2, net::DEFAULT_PRIORITY, file_writer_delegate2.get(), NULL); 364 365 Result result, result2; 366 ASSERT_EQ(0, usage()); 367 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 368 file_writer_delegate2->Start(request2.Pass(), GetWriteCallback(&result2)); 369 base::MessageLoop::current()->Run(); 370 if (result.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING || 371 result2.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING) 372 base::MessageLoop::current()->Run(); 373 374 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 375 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result2.write_status()); 376 file_writer_delegate_.reset(); 377 file_writer_delegate2.reset(); 378 379 ASSERT_EQ(kDataSize * 2, usage()); 380 EXPECT_EQ(GetFileSizeOnDisk("test") + GetFileSizeOnDisk("test2"), usage()); 381 382 EXPECT_EQ(kDataSize, result.bytes_written()); 383 EXPECT_EQ(base::File::FILE_OK, result.status()); 384 EXPECT_EQ(kDataSize, result2.bytes_written()); 385 EXPECT_EQ(base::File::FILE_OK, result2.status()); 386 } 387 388 TEST_F(FileWriterDelegateTest, WritesWithQuotaAndOffset) { 389 const GURL kBlobURL("blob:failure-with-updated-quota"); 390 content_ = kData; 391 392 // Writing kDataSize (=45) bytes data while allowed_growth is 100. 393 int64 offset = 0; 394 int64 allowed_growth = 100; 395 ASSERT_LT(kDataSize, allowed_growth); 396 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 397 398 { 399 Result result; 400 ASSERT_EQ(0, usage()); 401 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 402 base::MessageLoop::current()->Run(); 403 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 404 file_writer_delegate_.reset(); 405 406 ASSERT_EQ(kDataSize, usage()); 407 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 408 EXPECT_EQ(kDataSize, result.bytes_written()); 409 EXPECT_EQ(base::File::FILE_OK, result.status()); 410 } 411 412 // Trying to overwrite kDataSize bytes data while allowed_growth is 20. 413 offset = 0; 414 allowed_growth = 20; 415 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 416 417 { 418 Result result; 419 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 420 base::MessageLoop::current()->Run(); 421 EXPECT_EQ(kDataSize, usage()); 422 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 423 EXPECT_EQ(kDataSize, result.bytes_written()); 424 EXPECT_EQ(base::File::FILE_OK, result.status()); 425 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 426 } 427 428 // Trying to write kDataSize bytes data from offset 25 while 429 // allowed_growth is 55. 430 offset = 25; 431 allowed_growth = 55; 432 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 433 434 { 435 Result result; 436 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 437 base::MessageLoop::current()->Run(); 438 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 439 file_writer_delegate_.reset(); 440 441 EXPECT_EQ(offset + kDataSize, usage()); 442 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 443 EXPECT_EQ(kDataSize, result.bytes_written()); 444 EXPECT_EQ(base::File::FILE_OK, result.status()); 445 } 446 447 // Trying to overwrite 45 bytes data while allowed_growth is -20. 448 offset = 0; 449 allowed_growth = -20; 450 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 451 int64 pre_write_usage = GetFileSizeOnDisk("test"); 452 453 { 454 Result result; 455 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 456 base::MessageLoop::current()->Run(); 457 ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); 458 file_writer_delegate_.reset(); 459 460 EXPECT_EQ(pre_write_usage, usage()); 461 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 462 EXPECT_EQ(kDataSize, result.bytes_written()); 463 EXPECT_EQ(base::File::FILE_OK, result.status()); 464 } 465 466 // Trying to overwrite 45 bytes data with offset pre_write_usage - 20, 467 // while allowed_growth is 10. 468 const int kOverlap = 20; 469 offset = pre_write_usage - kOverlap; 470 allowed_growth = 10; 471 PrepareForWrite("test", kBlobURL, offset, allowed_growth); 472 473 { 474 Result result; 475 file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); 476 base::MessageLoop::current()->Run(); 477 ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); 478 file_writer_delegate_.reset(); 479 480 EXPECT_EQ(pre_write_usage + allowed_growth, usage()); 481 EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); 482 EXPECT_EQ(kOverlap + allowed_growth, result.bytes_written()); 483 EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, result.status()); 484 } 485 } 486 487 } // namespace content 488