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