Home | History | Annotate | Download | only in fileapi
      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