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 "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