syncable_file_operation_runner_unittest.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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
7#include "base/basictypes.h"
8#include "base/file_util.h"
9#include "base/files/file.h"
10#include "base/location.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/message_loop/message_loop.h"
13#include "chrome/browser/sync_file_system/local/canned_syncable_file_system.h"
14#include "chrome/browser/sync_file_system/local/local_file_change_tracker.h"
15#include "chrome/browser/sync_file_system/local/local_file_sync_context.h"
16#include "chrome/browser/sync_file_system/local/local_file_sync_status.h"
17#include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
18#include "chrome/browser/sync_file_system/local/syncable_file_operation_runner.h"
19#include "chrome/browser/sync_file_system/local/syncable_file_system_operation.h"
20#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
21#include "content/public/test/mock_blob_url_request_context.h"
22#include "content/public/test/test_browser_thread_bundle.h"
23#include "testing/gtest/include/gtest/gtest.h"
24#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
25#include "third_party/leveldatabase/src/include/leveldb/env.h"
26#include "webkit/browser/fileapi/file_system_context.h"
27#include "webkit/browser/fileapi/file_system_operation_runner.h"
28
29using fileapi::FileSystemOperation;
30using fileapi::FileSystemURL;
31using content::MockBlobURLRequestContext;
32using content::ScopedTextBlob;
33using base::File;
34
35namespace sync_file_system {
36
37namespace {
38const std::string kParent = "foo";
39const std::string kFile   = "foo/file";
40const std::string kDir    = "foo/dir";
41const std::string kChild  = "foo/dir/bar";
42const std::string kOther  = "bar";
43}  // namespace
44
45class SyncableFileOperationRunnerTest : public testing::Test {
46 protected:
47  typedef FileSystemOperation::StatusCallback StatusCallback;
48
49  // Use the current thread as IO thread so that we can directly call
50  // operations in the tests.
51  SyncableFileOperationRunnerTest()
52      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
53        in_memory_env_(leveldb::NewMemEnv(leveldb::Env::Default())),
54        file_system_(GURL("http://example.com"),
55                     in_memory_env_.get(),
56                     base::MessageLoopProxy::current().get(),
57                     base::MessageLoopProxy::current().get()),
58        callback_count_(0),
59        write_status_(File::FILE_ERROR_FAILED),
60        write_bytes_(0),
61        write_complete_(false),
62        url_request_context_(file_system_.file_system_context()),
63        weak_factory_(this) {}
64
65  virtual void SetUp() OVERRIDE {
66    ASSERT_TRUE(dir_.CreateUniqueTempDir());
67
68    file_system_.SetUp(CannedSyncableFileSystem::QUOTA_ENABLED);
69    sync_context_ = new LocalFileSyncContext(
70        dir_.path(),
71        in_memory_env_.get(),
72        base::MessageLoopProxy::current().get(),
73        base::MessageLoopProxy::current().get());
74    ASSERT_EQ(
75        SYNC_STATUS_OK,
76        file_system_.MaybeInitializeFileSystemContext(sync_context_.get()));
77
78    ASSERT_EQ(File::FILE_OK, file_system_.OpenFileSystem());
79    ASSERT_EQ(File::FILE_OK,
80              file_system_.CreateDirectory(URL(kParent)));
81  }
82
83  virtual void TearDown() OVERRIDE {
84    if (sync_context_.get())
85      sync_context_->ShutdownOnUIThread();
86    sync_context_ = NULL;
87
88    file_system_.TearDown();
89    RevokeSyncableFileSystem();
90  }
91
92  FileSystemURL URL(const std::string& path) {
93    return file_system_.URL(path);
94  }
95
96  LocalFileSyncStatus* sync_status() {
97    return file_system_.backend()->sync_context()->sync_status();
98  }
99
100  void ResetCallbackStatus() {
101    write_status_ = File::FILE_ERROR_FAILED;
102    write_bytes_ = 0;
103    write_complete_ = false;
104    callback_count_ = 0;
105  }
106
107  StatusCallback ExpectStatus(const tracked_objects::Location& location,
108                              File::Error expect) {
109    return base::Bind(&SyncableFileOperationRunnerTest::DidFinish,
110                      weak_factory_.GetWeakPtr(), location, expect);
111  }
112
113  FileSystemOperation::WriteCallback GetWriteCallback(
114      const tracked_objects::Location& location) {
115    return base::Bind(&SyncableFileOperationRunnerTest::DidWrite,
116                      weak_factory_.GetWeakPtr(), location);
117  }
118
119  void DidWrite(const tracked_objects::Location& location,
120                File::Error status, int64 bytes, bool complete) {
121    SCOPED_TRACE(testing::Message() << location.ToString());
122    write_status_ = status;
123    write_bytes_ += bytes;
124    write_complete_ = complete;
125    ++callback_count_;
126  }
127
128  void DidFinish(const tracked_objects::Location& location,
129                 File::Error expect, File::Error status) {
130    SCOPED_TRACE(testing::Message() << location.ToString());
131    EXPECT_EQ(expect, status);
132    ++callback_count_;
133  }
134
135  bool CreateTempFile(base::FilePath* path) {
136    return base::CreateTemporaryFileInDir(dir_.path(), path);
137  }
138
139  ScopedEnableSyncFSDirectoryOperation enable_directory_operation_;
140  content::TestBrowserThreadBundle thread_bundle_;
141
142  base::ScopedTempDir dir_;
143  scoped_ptr<leveldb::Env> in_memory_env_;
144
145  CannedSyncableFileSystem file_system_;
146  scoped_refptr<LocalFileSyncContext> sync_context_;
147
148  int callback_count_;
149  File::Error write_status_;
150  size_t write_bytes_;
151  bool write_complete_;
152
153  MockBlobURLRequestContext url_request_context_;
154
155 private:
156  base::WeakPtrFactory<SyncableFileOperationRunnerTest> weak_factory_;
157
158  DISALLOW_COPY_AND_ASSIGN(SyncableFileOperationRunnerTest);
159};
160
161TEST_F(SyncableFileOperationRunnerTest, SimpleQueue) {
162  sync_status()->StartSyncing(URL(kFile));
163  ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
164
165  // The URL is in syncing so the write operations won't run.
166  ResetCallbackStatus();
167  file_system_.operation_runner()->CreateFile(
168      URL(kFile), false /* exclusive */,
169      ExpectStatus(FROM_HERE, File::FILE_OK));
170  file_system_.operation_runner()->Truncate(
171      URL(kFile), 1,
172      ExpectStatus(FROM_HERE, File::FILE_OK));
173  base::MessageLoop::current()->RunUntilIdle();
174  EXPECT_EQ(0, callback_count_);
175
176  // Read operations are not blocked (and are executed before queued ones).
177  file_system_.operation_runner()->FileExists(
178      URL(kFile), ExpectStatus(FROM_HERE, File::FILE_ERROR_NOT_FOUND));
179  base::MessageLoop::current()->RunUntilIdle();
180  EXPECT_EQ(1, callback_count_);
181
182  // End syncing (to enable write).
183  sync_status()->EndSyncing(URL(kFile));
184  ASSERT_TRUE(sync_status()->IsWritable(URL(kFile)));
185
186  ResetCallbackStatus();
187  base::MessageLoop::current()->RunUntilIdle();
188  EXPECT_EQ(2, callback_count_);
189
190  // Now the file must have been created and updated.
191  ResetCallbackStatus();
192  file_system_.operation_runner()->FileExists(
193      URL(kFile), ExpectStatus(FROM_HERE, File::FILE_OK));
194  base::MessageLoop::current()->RunUntilIdle();
195  EXPECT_EQ(1, callback_count_);
196}
197
198TEST_F(SyncableFileOperationRunnerTest, WriteToParentAndChild) {
199  // First create the kDir directory and kChild in the dir.
200  EXPECT_EQ(File::FILE_OK, file_system_.CreateDirectory(URL(kDir)));
201  EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kChild)));
202
203  // Start syncing the kDir directory.
204  sync_status()->StartSyncing(URL(kDir));
205  ASSERT_FALSE(sync_status()->IsWritable(URL(kDir)));
206
207  // Writes to kParent and kChild should be all queued up.
208  ResetCallbackStatus();
209  file_system_.operation_runner()->Truncate(
210      URL(kChild), 1, ExpectStatus(FROM_HERE, File::FILE_OK));
211  file_system_.operation_runner()->Remove(
212      URL(kParent), true /* recursive */,
213      ExpectStatus(FROM_HERE, File::FILE_OK));
214  base::MessageLoop::current()->RunUntilIdle();
215  EXPECT_EQ(0, callback_count_);
216
217  // Read operations are not blocked (and are executed before queued ones).
218  file_system_.operation_runner()->DirectoryExists(
219      URL(kDir), ExpectStatus(FROM_HERE, File::FILE_OK));
220  base::MessageLoop::current()->RunUntilIdle();
221  EXPECT_EQ(1, callback_count_);
222
223  // Writes to unrelated files must succeed as well.
224  ResetCallbackStatus();
225  file_system_.operation_runner()->CreateDirectory(
226      URL(kOther), false /* exclusive */, false /* recursive */,
227      ExpectStatus(FROM_HERE, File::FILE_OK));
228  base::MessageLoop::current()->RunUntilIdle();
229  EXPECT_EQ(1, callback_count_);
230
231  // End syncing (to enable write).
232  sync_status()->EndSyncing(URL(kDir));
233  ASSERT_TRUE(sync_status()->IsWritable(URL(kDir)));
234
235  ResetCallbackStatus();
236  base::MessageLoop::current()->RunUntilIdle();
237  EXPECT_EQ(2, callback_count_);
238}
239
240TEST_F(SyncableFileOperationRunnerTest, CopyAndMove) {
241  // First create the kDir directory and kChild in the dir.
242  EXPECT_EQ(File::FILE_OK, file_system_.CreateDirectory(URL(kDir)));
243  EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kChild)));
244
245  // Start syncing the kParent directory.
246  sync_status()->StartSyncing(URL(kParent));
247
248  // Copying kDir to other directory should succeed, while moving would fail
249  // (since the source directory is in syncing).
250  ResetCallbackStatus();
251  file_system_.operation_runner()->Copy(
252      URL(kDir), URL("dest-copy"),
253      fileapi::FileSystemOperation::OPTION_NONE,
254      fileapi::FileSystemOperationRunner::CopyProgressCallback(),
255      ExpectStatus(FROM_HERE, File::FILE_OK));
256  file_system_.operation_runner()->Move(
257      URL(kDir), URL("dest-move"),
258      fileapi::FileSystemOperation::OPTION_NONE,
259      ExpectStatus(FROM_HERE, File::FILE_OK));
260  base::MessageLoop::current()->RunUntilIdle();
261  EXPECT_EQ(1, callback_count_);
262
263  // Only "dest-copy1" should exist.
264  EXPECT_EQ(File::FILE_OK,
265            file_system_.DirectoryExists(URL("dest-copy")));
266  EXPECT_EQ(File::FILE_ERROR_NOT_FOUND,
267            file_system_.DirectoryExists(URL("dest-move")));
268
269  // Start syncing the "dest-copy2" directory.
270  sync_status()->StartSyncing(URL("dest-copy2"));
271
272  // Now the destination is also locked copying kDir should be queued.
273  ResetCallbackStatus();
274  file_system_.operation_runner()->Copy(
275      URL(kDir), URL("dest-copy2"),
276      fileapi::FileSystemOperation::OPTION_NONE,
277      fileapi::FileSystemOperationRunner::CopyProgressCallback(),
278      ExpectStatus(FROM_HERE, File::FILE_OK));
279  base::MessageLoop::current()->RunUntilIdle();
280  EXPECT_EQ(0, callback_count_);
281
282  // Finish syncing the "dest-copy2" directory to unlock Copy.
283  sync_status()->EndSyncing(URL("dest-copy2"));
284  ResetCallbackStatus();
285  base::MessageLoop::current()->RunUntilIdle();
286  EXPECT_EQ(1, callback_count_);
287
288  // Now we should have "dest-copy2".
289  EXPECT_EQ(File::FILE_OK,
290            file_system_.DirectoryExists(URL("dest-copy2")));
291
292  // Finish syncing the kParent to unlock Move.
293  sync_status()->EndSyncing(URL(kParent));
294  ResetCallbackStatus();
295  base::MessageLoop::current()->RunUntilIdle();
296  EXPECT_EQ(1, callback_count_);
297
298  // Now we should have "dest-move".
299  EXPECT_EQ(File::FILE_OK,
300            file_system_.DirectoryExists(URL("dest-move")));
301}
302
303TEST_F(SyncableFileOperationRunnerTest, Write) {
304  EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kFile)));
305  const std::string kData("Lorem ipsum.");
306  ScopedTextBlob blob(url_request_context_, "blob:foo", kData);
307
308  sync_status()->StartSyncing(URL(kFile));
309
310  ResetCallbackStatus();
311  file_system_.operation_runner()->Write(
312      &url_request_context_,
313      URL(kFile), blob.GetBlobDataHandle(), 0, GetWriteCallback(FROM_HERE));
314  base::MessageLoop::current()->RunUntilIdle();
315  EXPECT_EQ(0, callback_count_);
316
317  sync_status()->EndSyncing(URL(kFile));
318  ResetCallbackStatus();
319
320  while (!write_complete_)
321    base::MessageLoop::current()->RunUntilIdle();
322
323  EXPECT_EQ(File::FILE_OK, write_status_);
324  EXPECT_EQ(kData.size(), write_bytes_);
325  EXPECT_TRUE(write_complete_);
326}
327
328TEST_F(SyncableFileOperationRunnerTest, QueueAndCancel) {
329  sync_status()->StartSyncing(URL(kFile));
330  ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
331
332  ResetCallbackStatus();
333  file_system_.operation_runner()->CreateFile(
334      URL(kFile), false /* exclusive */,
335      ExpectStatus(FROM_HERE, File::FILE_ERROR_ABORT));
336  file_system_.operation_runner()->Truncate(
337      URL(kFile), 1,
338      ExpectStatus(FROM_HERE, File::FILE_ERROR_ABORT));
339  base::MessageLoop::current()->RunUntilIdle();
340  EXPECT_EQ(0, callback_count_);
341
342  ResetCallbackStatus();
343
344  // This shouldn't crash nor leak memory.
345  sync_context_->ShutdownOnUIThread();
346  sync_context_ = NULL;
347  base::MessageLoop::current()->RunUntilIdle();
348  EXPECT_EQ(2, callback_count_);
349}
350
351// Test if CopyInForeignFile runs cooperatively with other Sync operations.
352TEST_F(SyncableFileOperationRunnerTest, CopyInForeignFile) {
353  const std::string kTestData("test data");
354
355  base::FilePath temp_path;
356  ASSERT_TRUE(CreateTempFile(&temp_path));
357  ASSERT_EQ(static_cast<int>(kTestData.size()),
358            base::WriteFile(
359                temp_path, kTestData.data(), kTestData.size()));
360
361  sync_status()->StartSyncing(URL(kFile));
362  ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
363
364  // The URL is in syncing so CopyIn (which is a write operation) won't run.
365  ResetCallbackStatus();
366  file_system_.operation_runner()->CopyInForeignFile(
367      temp_path, URL(kFile),
368      ExpectStatus(FROM_HERE, File::FILE_OK));
369  base::MessageLoop::current()->RunUntilIdle();
370  EXPECT_EQ(0, callback_count_);
371
372  // End syncing (to enable write).
373  sync_status()->EndSyncing(URL(kFile));
374  ASSERT_TRUE(sync_status()->IsWritable(URL(kFile)));
375
376  ResetCallbackStatus();
377  base::MessageLoop::current()->RunUntilIdle();
378  EXPECT_EQ(1, callback_count_);
379
380  // Now the file must have been created and have the same content as temp_path.
381  ResetCallbackStatus();
382  file_system_.DoVerifyFile(
383      URL(kFile), kTestData,
384      ExpectStatus(FROM_HERE, File::FILE_OK));
385  base::MessageLoop::current()->RunUntilIdle();
386  EXPECT_EQ(1, callback_count_);
387}
388
389TEST_F(SyncableFileOperationRunnerTest, Cancel) {
390  // Prepare a file.
391  file_system_.operation_runner()->CreateFile(
392      URL(kFile), false /* exclusive */,
393      ExpectStatus(FROM_HERE, File::FILE_OK));
394  base::MessageLoop::current()->RunUntilIdle();
395  EXPECT_EQ(1, callback_count_);
396
397  // Run Truncate and immediately cancel. This shouldn't crash.
398  ResetCallbackStatus();
399  fileapi::FileSystemOperationRunner::OperationID id =
400      file_system_.operation_runner()->Truncate(
401          URL(kFile), 10,
402          ExpectStatus(FROM_HERE, File::FILE_OK));
403  file_system_.operation_runner()->Cancel(
404      id, ExpectStatus(FROM_HERE, File::FILE_ERROR_INVALID_OPERATION));
405  base::MessageLoop::current()->RunUntilIdle();
406  EXPECT_EQ(2, callback_count_);
407}
408
409}  // namespace sync_file_system
410