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