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 <vector>
6
7#include "base/files/scoped_temp_dir.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/memory/weak_ptr.h"
10#include "base/message_loop/message_loop_proxy.h"
11#include "base/run_loop.h"
12#include "content/browser/fileapi/mock_file_change_observer.h"
13#include "content/browser/quota/mock_quota_manager.h"
14#include "content/public/test/mock_blob_url_request_context.h"
15#include "content/public/test/test_file_system_backend.h"
16#include "content/public/test/test_file_system_context.h"
17#include "net/url_request/url_request.h"
18#include "net/url_request/url_request_context.h"
19#include "net/url_request/url_request_job.h"
20#include "net/url_request/url_request_job_factory_impl.h"
21#include "storage/browser/blob/blob_storage_context.h"
22#include "storage/browser/blob/blob_url_request_job.h"
23#include "storage/browser/fileapi/file_system_context.h"
24#include "storage/browser/fileapi/file_system_file_util.h"
25#include "storage/browser/fileapi/file_system_operation_context.h"
26#include "storage/browser/fileapi/file_system_operation_runner.h"
27#include "storage/browser/fileapi/local_file_util.h"
28#include "storage/common/blob/blob_data.h"
29#include "storage/common/fileapi/file_system_util.h"
30#include "testing/gtest/include/gtest/gtest.h"
31#include "url/gurl.h"
32
33using storage::FileSystemOperation;
34using storage::FileSystemOperationRunner;
35using storage::FileSystemURL;
36using content::MockBlobURLRequestContext;
37using content::ScopedTextBlob;
38
39namespace content {
40
41namespace {
42
43const GURL kOrigin("http://example.com");
44const storage::FileSystemType kFileSystemType = storage::kFileSystemTypeTest;
45
46void AssertStatusEq(base::File::Error expected,
47                    base::File::Error actual) {
48  ASSERT_EQ(expected, actual);
49}
50
51}  // namespace
52
53class FileSystemOperationImplWriteTest
54    : public testing::Test {
55 public:
56  FileSystemOperationImplWriteTest()
57      : status_(base::File::FILE_OK),
58        cancel_status_(base::File::FILE_ERROR_FAILED),
59        bytes_written_(0),
60        complete_(false),
61        weak_factory_(this) {
62    change_observers_ =
63        storage::MockFileChangeObserver::CreateList(&change_observer_);
64  }
65
66  virtual void SetUp() {
67    ASSERT_TRUE(dir_.CreateUniqueTempDir());
68
69    quota_manager_ =
70        new MockQuotaManager(false /* is_incognito */,
71                                    dir_.path(),
72                                    base::MessageLoopProxy::current().get(),
73                                    base::MessageLoopProxy::current().get(),
74                                    NULL /* special storage policy */);
75    virtual_path_ = base::FilePath(FILE_PATH_LITERAL("temporary file"));
76
77    file_system_context_ = CreateFileSystemContextForTesting(
78        quota_manager_->proxy(), dir_.path());
79    url_request_context_.reset(
80        new MockBlobURLRequestContext(file_system_context_.get()));
81
82    file_system_context_->operation_runner()->CreateFile(
83        URLForPath(virtual_path_), true /* exclusive */,
84        base::Bind(&AssertStatusEq, base::File::FILE_OK));
85
86    static_cast<TestFileSystemBackend*>(
87        file_system_context_->GetFileSystemBackend(kFileSystemType))
88        ->AddFileChangeObserver(change_observer());
89  }
90
91  virtual void TearDown() {
92    quota_manager_ = NULL;
93    file_system_context_ = NULL;
94    base::RunLoop().RunUntilIdle();
95  }
96
97  base::File::Error status() const { return status_; }
98  base::File::Error cancel_status() const { return cancel_status_; }
99  void add_bytes_written(int64 bytes, bool complete) {
100    bytes_written_ += bytes;
101    EXPECT_FALSE(complete_);
102    complete_ = complete;
103  }
104  int64 bytes_written() const { return bytes_written_; }
105  bool complete() const { return complete_; }
106
107 protected:
108  const storage::ChangeObserverList& change_observers() const {
109    return change_observers_;
110  }
111
112  storage::MockFileChangeObserver* change_observer() {
113    return &change_observer_;
114  }
115
116  FileSystemURL URLForPath(const base::FilePath& path) const {
117    return file_system_context_->CreateCrackedFileSystemURL(
118        kOrigin, kFileSystemType, path);
119  }
120
121  // Callback function for recording test results.
122  FileSystemOperation::WriteCallback RecordWriteCallback() {
123    return base::Bind(&FileSystemOperationImplWriteTest::DidWrite,
124                      weak_factory_.GetWeakPtr());
125  }
126
127  FileSystemOperation::StatusCallback RecordCancelCallback() {
128    return base::Bind(&FileSystemOperationImplWriteTest::DidCancel,
129                      weak_factory_.GetWeakPtr());
130  }
131
132  void DidWrite(base::File::Error status, int64 bytes, bool complete) {
133    if (status == base::File::FILE_OK) {
134      add_bytes_written(bytes, complete);
135      if (complete)
136        base::MessageLoop::current()->Quit();
137    } else {
138      EXPECT_FALSE(complete_);
139      EXPECT_EQ(status_, base::File::FILE_OK);
140      complete_ = true;
141      status_ = status;
142      if (base::MessageLoop::current()->is_running())
143        base::MessageLoop::current()->Quit();
144    }
145  }
146
147  void DidCancel(base::File::Error status) {
148    cancel_status_ = status;
149  }
150
151  const MockBlobURLRequestContext& url_request_context() const {
152    return *url_request_context_;
153  }
154
155  scoped_refptr<storage::FileSystemContext> file_system_context_;
156  scoped_refptr<MockQuotaManager> quota_manager_;
157
158  base::MessageLoopForIO loop_;
159
160  base::ScopedTempDir dir_;
161  base::FilePath virtual_path_;
162
163  // For post-operation status.
164  base::File::Error status_;
165  base::File::Error cancel_status_;
166  int64 bytes_written_;
167  bool complete_;
168
169  scoped_ptr<MockBlobURLRequestContext> url_request_context_;
170
171  storage::MockFileChangeObserver change_observer_;
172  storage::ChangeObserverList change_observers_;
173
174  base::WeakPtrFactory<FileSystemOperationImplWriteTest> weak_factory_;
175
176  DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplWriteTest);
177};
178
179TEST_F(FileSystemOperationImplWriteTest, TestWriteSuccess) {
180  ScopedTextBlob blob(url_request_context(),
181                      "blob-id:success",
182                      "Hello, world!\n");
183  file_system_context_->operation_runner()->Write(
184      &url_request_context(), URLForPath(virtual_path_),
185      blob.GetBlobDataHandle(),
186      0, RecordWriteCallback());
187  base::MessageLoop::current()->Run();
188
189  EXPECT_EQ(14, bytes_written());
190  EXPECT_EQ(base::File::FILE_OK, status());
191  EXPECT_TRUE(complete());
192
193  EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
194}
195
196TEST_F(FileSystemOperationImplWriteTest, TestWriteZero) {
197  ScopedTextBlob blob(url_request_context(), "blob_id:zero", "");
198  file_system_context_->operation_runner()->Write(
199      &url_request_context(), URLForPath(virtual_path_),
200      blob.GetBlobDataHandle(), 0, RecordWriteCallback());
201  base::MessageLoop::current()->Run();
202
203  EXPECT_EQ(0, bytes_written());
204  EXPECT_EQ(base::File::FILE_OK, status());
205  EXPECT_TRUE(complete());
206
207  EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
208}
209
210
211TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidBlobUrl) {
212  scoped_ptr<storage::BlobDataHandle> null_handle;
213  file_system_context_->operation_runner()->Write(
214      &url_request_context(), URLForPath(virtual_path_),
215      null_handle.Pass(), 0, RecordWriteCallback());
216  base::MessageLoop::current()->Run();
217
218  EXPECT_EQ(0, bytes_written());
219  EXPECT_EQ(base::File::FILE_ERROR_FAILED, status());
220  EXPECT_TRUE(complete());
221
222  EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
223}
224
225TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidFile) {
226  ScopedTextBlob blob(url_request_context(), "blob_id:writeinvalidfile",
227                      "It\'ll not be written.");
228  file_system_context_->operation_runner()->Write(
229      &url_request_context(),
230      URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))),
231      blob.GetBlobDataHandle(), 0, RecordWriteCallback());
232  base::MessageLoop::current()->Run();
233
234  EXPECT_EQ(0, bytes_written());
235  EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, status());
236  EXPECT_TRUE(complete());
237
238  EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
239}
240
241TEST_F(FileSystemOperationImplWriteTest, TestWriteDir) {
242  base::FilePath virtual_dir_path(FILE_PATH_LITERAL("d"));
243  file_system_context_->operation_runner()->CreateDirectory(
244      URLForPath(virtual_dir_path),
245      true /* exclusive */, false /* recursive */,
246      base::Bind(&AssertStatusEq, base::File::FILE_OK));
247
248  ScopedTextBlob blob(url_request_context(), "blob:writedir",
249                      "It\'ll not be written, too.");
250  file_system_context_->operation_runner()->Write(
251      &url_request_context(), URLForPath(virtual_dir_path),
252      blob.GetBlobDataHandle(),  0, RecordWriteCallback());
253  base::MessageLoop::current()->Run();
254
255  EXPECT_EQ(0, bytes_written());
256  // TODO(kinuko): This error code is platform- or fileutil- dependent
257  // right now.  Make it return File::FILE_ERROR_NOT_A_FILE in every case.
258  EXPECT_TRUE(status() == base::File::FILE_ERROR_NOT_A_FILE ||
259              status() == base::File::FILE_ERROR_ACCESS_DENIED ||
260              status() == base::File::FILE_ERROR_FAILED);
261  EXPECT_TRUE(complete());
262
263  EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
264}
265
266TEST_F(FileSystemOperationImplWriteTest, TestWriteFailureByQuota) {
267  ScopedTextBlob blob(url_request_context(), "blob:success",
268                      "Hello, world!\n");
269  quota_manager_->SetQuota(
270      kOrigin, FileSystemTypeToQuotaStorageType(kFileSystemType), 10);
271  file_system_context_->operation_runner()->Write(
272      &url_request_context(), URLForPath(virtual_path_),
273      blob.GetBlobDataHandle(), 0, RecordWriteCallback());
274  base::MessageLoop::current()->Run();
275
276  EXPECT_EQ(10, bytes_written());
277  EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE, status());
278  EXPECT_TRUE(complete());
279
280  EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
281}
282
283TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelSuccessfulWrite) {
284  ScopedTextBlob blob(url_request_context(), "blob:success",
285                      "Hello, world!\n");
286  FileSystemOperationRunner::OperationID id =
287      file_system_context_->operation_runner()->Write(
288          &url_request_context(), URLForPath(virtual_path_),
289          blob.GetBlobDataHandle(), 0, RecordWriteCallback());
290  file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback());
291  // We use RunAllPendings() instead of Run() here, because we won't dispatch
292  // callbacks after Cancel() is issued (so no chance to Quit) nor do we need
293  // to run another write cycle.
294  base::RunLoop().RunUntilIdle();
295
296  // Issued Cancel() before receiving any response from Write(),
297  // so nothing should have happen.
298  EXPECT_EQ(0, bytes_written());
299  EXPECT_EQ(base::File::FILE_ERROR_ABORT, status());
300  EXPECT_EQ(base::File::FILE_OK, cancel_status());
301  EXPECT_TRUE(complete());
302
303  EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
304}
305
306TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelFailingWrite) {
307  ScopedTextBlob blob(url_request_context(), "blob:writeinvalidfile",
308                      "It\'ll not be written.");
309  FileSystemOperationRunner::OperationID id =
310      file_system_context_->operation_runner()->Write(
311          &url_request_context(),
312          URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))),
313          blob.GetBlobDataHandle(), 0, RecordWriteCallback());
314  file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback());
315  // We use RunAllPendings() instead of Run() here, because we won't dispatch
316  // callbacks after Cancel() is issued (so no chance to Quit) nor do we need
317  // to run another write cycle.
318  base::RunLoop().RunUntilIdle();
319
320  // Issued Cancel() before receiving any response from Write(),
321  // so nothing should have happen.
322  EXPECT_EQ(0, bytes_written());
323  EXPECT_EQ(base::File::FILE_ERROR_ABORT, status());
324  EXPECT_EQ(base::File::FILE_OK, cancel_status());
325  EXPECT_TRUE(complete());
326
327  EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
328}
329
330// TODO(ericu,dmikurube,kinuko): Add more tests for cancel cases.
331
332}  // namespace content
333