copy_or_move_operation_delegate_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 <map>
6#include <queue>
7
8#include "base/basictypes.h"
9#include "base/bind.h"
10#include "base/file_util.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 "base/stl_util.h"
15#include "content/public/test/async_file_test_helper.h"
16#include "content/public/test/test_file_system_backend.h"
17#include "content/public/test/test_file_system_context.h"
18#include "content/test/fileapi_test_file_set.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "webkit/browser/blob/file_stream_reader.h"
21#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
22#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h"
23#include "webkit/browser/fileapi/file_stream_writer.h"
24#include "webkit/browser/fileapi/file_system_backend.h"
25#include "webkit/browser/fileapi/file_system_context.h"
26#include "webkit/browser/fileapi/file_system_operation.h"
27#include "webkit/browser/fileapi/file_system_url.h"
28#include "webkit/browser/quota/mock_quota_manager.h"
29#include "webkit/browser/quota/mock_quota_manager_proxy.h"
30#include "webkit/browser/quota/quota_manager.h"
31#include "webkit/common/fileapi/file_system_util.h"
32
33using content::AsyncFileTestHelper;
34using fileapi::CopyOrMoveOperationDelegate;
35using fileapi::FileStreamWriter;
36using fileapi::FileSystemOperation;
37using fileapi::FileSystemType;
38using fileapi::FileSystemURL;
39
40namespace content {
41
42typedef fileapi::FileSystemOperation::FileEntryList FileEntryList;
43
44namespace {
45
46void ExpectOk(const GURL& origin_url,
47              const std::string& name,
48              base::File::Error error) {
49  ASSERT_EQ(base::File::FILE_OK, error);
50}
51
52class TestValidatorFactory : public fileapi::CopyOrMoveFileValidatorFactory {
53 public:
54  // A factory that creates validators that accept everything or nothing.
55  TestValidatorFactory() {}
56  virtual ~TestValidatorFactory() {}
57
58  virtual fileapi::CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
59      const FileSystemURL& /*src_url*/,
60      const base::FilePath& /*platform_path*/) OVERRIDE {
61    // Move arg management to TestValidator?
62    return new TestValidator(true, true, std::string("2"));
63  }
64
65 private:
66  class TestValidator : public fileapi::CopyOrMoveFileValidator {
67   public:
68    explicit TestValidator(bool pre_copy_valid,
69                           bool post_copy_valid,
70                           const std::string& reject_string)
71        : result_(pre_copy_valid ? base::File::FILE_OK :
72                                   base::File::FILE_ERROR_SECURITY),
73          write_result_(post_copy_valid ? base::File::FILE_OK :
74                                          base::File::FILE_ERROR_SECURITY),
75          reject_string_(reject_string) {
76    }
77    virtual ~TestValidator() {}
78
79    virtual void StartPreWriteValidation(
80        const ResultCallback& result_callback) OVERRIDE {
81      // Post the result since a real validator must do work asynchronously.
82      base::MessageLoop::current()->PostTask(
83          FROM_HERE, base::Bind(result_callback, result_));
84    }
85
86    virtual void StartPostWriteValidation(
87        const base::FilePath& dest_platform_path,
88        const ResultCallback& result_callback) OVERRIDE {
89      base::File::Error result = write_result_;
90      std::string unsafe = dest_platform_path.BaseName().AsUTF8Unsafe();
91      if (unsafe.find(reject_string_) != std::string::npos) {
92        result = base::File::FILE_ERROR_SECURITY;
93      }
94      // Post the result since a real validator must do work asynchronously.
95      base::MessageLoop::current()->PostTask(
96          FROM_HERE, base::Bind(result_callback, result));
97    }
98
99   private:
100    base::File::Error result_;
101    base::File::Error write_result_;
102    std::string reject_string_;
103
104    DISALLOW_COPY_AND_ASSIGN(TestValidator);
105  };
106};
107
108// Records CopyProgressCallback invocations.
109struct ProgressRecord {
110  fileapi::FileSystemOperation::CopyProgressType type;
111  FileSystemURL source_url;
112  FileSystemURL dest_url;
113  int64 size;
114};
115
116void RecordProgressCallback(std::vector<ProgressRecord>* records,
117                            fileapi::FileSystemOperation::CopyProgressType type,
118                            const FileSystemURL& source_url,
119                            const FileSystemURL& dest_url,
120                            int64 size) {
121  ProgressRecord record;
122  record.type = type;
123  record.source_url = source_url;
124  record.dest_url = dest_url;
125  record.size = size;
126  records->push_back(record);
127}
128
129void RecordFileProgressCallback(std::vector<int64>* records,
130                                int64 progress) {
131  records->push_back(progress);
132}
133
134void AssignAndQuit(base::RunLoop* run_loop,
135                   base::File::Error* result_out,
136                   base::File::Error result) {
137  *result_out = result;
138  run_loop->Quit();
139}
140
141class ScopedThreadStopper {
142 public:
143  ScopedThreadStopper(base::Thread* thread) : thread_(thread) {
144  }
145
146  ~ScopedThreadStopper() {
147    if (thread_) {
148      // Give another chance for deleted streams to perform Close.
149      base::RunLoop run_loop;
150      thread_->message_loop_proxy()->PostTaskAndReply(
151          FROM_HERE, base::Bind(&base::DoNothing), run_loop.QuitClosure());
152      run_loop.Run();
153      thread_->Stop();
154    }
155  }
156
157  bool is_valid() const { return thread_; }
158
159 private:
160  base::Thread* thread_;
161  DISALLOW_COPY_AND_ASSIGN(ScopedThreadStopper);
162};
163
164}  // namespace
165
166class CopyOrMoveOperationTestHelper {
167 public:
168  CopyOrMoveOperationTestHelper(
169      const GURL& origin,
170      FileSystemType src_type,
171      FileSystemType dest_type)
172      : origin_(origin),
173        src_type_(src_type),
174        dest_type_(dest_type) {}
175
176  ~CopyOrMoveOperationTestHelper() {
177    file_system_context_ = NULL;
178    quota_manager_proxy_->SimulateQuotaManagerDestroyed();
179    quota_manager_ = NULL;
180    quota_manager_proxy_ = NULL;
181    base::RunLoop().RunUntilIdle();
182  }
183
184  void SetUp() {
185    SetUp(true, true);
186  }
187
188  void SetUpNoValidator() {
189    SetUp(true, false);
190  }
191
192  void SetUp(bool require_copy_or_move_validator,
193             bool init_copy_or_move_validator) {
194    ASSERT_TRUE(base_.CreateUniqueTempDir());
195    base::FilePath base_dir = base_.path();
196    quota_manager_ =
197        new quota::MockQuotaManager(false /* is_incognito */,
198                                    base_dir,
199                                    base::MessageLoopProxy::current().get(),
200                                    base::MessageLoopProxy::current().get(),
201                                    NULL /* special storage policy */);
202    quota_manager_proxy_ = new quota::MockQuotaManagerProxy(
203        quota_manager_.get(), base::MessageLoopProxy::current().get());
204    file_system_context_ =
205        CreateFileSystemContextForTesting(quota_manager_proxy_.get(), base_dir);
206
207    // Prepare the origin's root directory.
208    fileapi::FileSystemBackend* backend =
209        file_system_context_->GetFileSystemBackend(src_type_);
210    backend->ResolveURL(
211        FileSystemURL::CreateForTest(origin_, src_type_, base::FilePath()),
212        fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
213        base::Bind(&ExpectOk));
214    backend = file_system_context_->GetFileSystemBackend(dest_type_);
215    if (dest_type_ == fileapi::kFileSystemTypeTest) {
216      TestFileSystemBackend* test_backend =
217          static_cast<TestFileSystemBackend*>(backend);
218      scoped_ptr<fileapi::CopyOrMoveFileValidatorFactory> factory(
219          new TestValidatorFactory);
220      test_backend->set_require_copy_or_move_validator(
221          require_copy_or_move_validator);
222      if (init_copy_or_move_validator)
223        test_backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass());
224    }
225    backend->ResolveURL(
226        FileSystemURL::CreateForTest(origin_, dest_type_, base::FilePath()),
227        fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
228        base::Bind(&ExpectOk));
229    base::RunLoop().RunUntilIdle();
230
231    // Grant relatively big quota initially.
232    quota_manager_->SetQuota(
233        origin_,
234        fileapi::FileSystemTypeToQuotaStorageType(src_type_),
235        1024 * 1024);
236    quota_manager_->SetQuota(
237        origin_,
238        fileapi::FileSystemTypeToQuotaStorageType(dest_type_),
239        1024 * 1024);
240  }
241
242  int64 GetSourceUsage() {
243    int64 usage = 0;
244    GetUsageAndQuota(src_type_, &usage, NULL);
245    return usage;
246  }
247
248  int64 GetDestUsage() {
249    int64 usage = 0;
250    GetUsageAndQuota(dest_type_, &usage, NULL);
251    return usage;
252  }
253
254  FileSystemURL SourceURL(const std::string& path) {
255    return file_system_context_->CreateCrackedFileSystemURL(
256        origin_, src_type_, base::FilePath::FromUTF8Unsafe(path));
257  }
258
259  FileSystemURL DestURL(const std::string& path) {
260    return file_system_context_->CreateCrackedFileSystemURL(
261        origin_, dest_type_, base::FilePath::FromUTF8Unsafe(path));
262  }
263
264  base::File::Error Copy(const FileSystemURL& src,
265                         const FileSystemURL& dest) {
266    return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest);
267  }
268
269  base::File::Error CopyWithProgress(
270      const FileSystemURL& src,
271      const FileSystemURL& dest,
272      const AsyncFileTestHelper::CopyProgressCallback& progress_callback) {
273    return AsyncFileTestHelper::CopyWithProgress(
274        file_system_context_.get(), src, dest, progress_callback);
275  }
276
277  base::File::Error Move(const FileSystemURL& src,
278                         const FileSystemURL& dest) {
279    return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest);
280  }
281
282  base::File::Error SetUpTestCaseFiles(
283      const FileSystemURL& root,
284      const FileSystemTestCaseRecord* const test_cases,
285      size_t test_case_size) {
286    base::File::Error result = base::File::FILE_ERROR_FAILED;
287    for (size_t i = 0; i < test_case_size; ++i) {
288      const FileSystemTestCaseRecord& test_case = test_cases[i];
289      FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
290          root.origin(),
291          root.mount_type(),
292          root.virtual_path().Append(test_case.path));
293      if (test_case.is_directory)
294        result = CreateDirectory(url);
295      else
296        result = CreateFile(url, test_case.data_file_size);
297      EXPECT_EQ(base::File::FILE_OK, result) << url.DebugString();
298      if (result != base::File::FILE_OK)
299        return result;
300    }
301    return result;
302  }
303
304  void VerifyTestCaseFiles(
305      const FileSystemURL& root,
306      const FileSystemTestCaseRecord* const test_cases,
307      size_t test_case_size) {
308    std::map<base::FilePath, const FileSystemTestCaseRecord*> test_case_map;
309    for (size_t i = 0; i < test_case_size; ++i) {
310      test_case_map[
311          base::FilePath(test_cases[i].path).NormalizePathSeparators()] =
312              &test_cases[i];
313    }
314
315    std::queue<FileSystemURL> directories;
316    FileEntryList entries;
317    directories.push(root);
318    while (!directories.empty()) {
319      FileSystemURL dir = directories.front();
320      directories.pop();
321      ASSERT_EQ(base::File::FILE_OK, ReadDirectory(dir, &entries));
322      for (size_t i = 0; i < entries.size(); ++i) {
323        FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
324            dir.origin(),
325            dir.mount_type(),
326            dir.virtual_path().Append(entries[i].name));
327        base::FilePath relative;
328        root.virtual_path().AppendRelativePath(url.virtual_path(), &relative);
329        relative = relative.NormalizePathSeparators();
330        ASSERT_TRUE(ContainsKey(test_case_map, relative));
331        if (entries[i].is_directory) {
332          EXPECT_TRUE(test_case_map[relative]->is_directory);
333          directories.push(url);
334        } else {
335          EXPECT_FALSE(test_case_map[relative]->is_directory);
336          EXPECT_TRUE(FileExists(url, test_case_map[relative]->data_file_size));
337        }
338        test_case_map.erase(relative);
339      }
340    }
341    EXPECT_TRUE(test_case_map.empty());
342    std::map<base::FilePath,
343        const FileSystemTestCaseRecord*>::const_iterator it;
344    for (it = test_case_map.begin(); it != test_case_map.end(); ++it) {
345      LOG(ERROR) << "Extra entry: " << it->first.LossyDisplayName();
346    }
347  }
348
349  base::File::Error ReadDirectory(const FileSystemURL& url,
350                                  FileEntryList* entries) {
351    return AsyncFileTestHelper::ReadDirectory(
352        file_system_context_.get(), url, entries);
353  }
354
355  base::File::Error CreateDirectory(const FileSystemURL& url) {
356    return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
357                                                url);
358  }
359
360  base::File::Error CreateFile(const FileSystemURL& url, size_t size) {
361    base::File::Error result =
362        AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
363    if (result != base::File::FILE_OK)
364      return result;
365    return AsyncFileTestHelper::TruncateFile(
366        file_system_context_.get(), url, size);
367  }
368
369  bool FileExists(const FileSystemURL& url, int64 expected_size) {
370    return AsyncFileTestHelper::FileExists(
371        file_system_context_.get(), url, expected_size);
372  }
373
374  bool DirectoryExists(const FileSystemURL& url) {
375    return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(),
376                                                url);
377  }
378
379 private:
380  void GetUsageAndQuota(FileSystemType type, int64* usage, int64* quota) {
381    quota::QuotaStatusCode status = AsyncFileTestHelper::GetUsageAndQuota(
382        quota_manager_.get(), origin_, type, usage, quota);
383    ASSERT_EQ(quota::kQuotaStatusOk, status);
384  }
385
386 private:
387  base::ScopedTempDir base_;
388
389  const GURL origin_;
390  const FileSystemType src_type_;
391  const FileSystemType dest_type_;
392
393  base::MessageLoopForIO message_loop_;
394  scoped_refptr<fileapi::FileSystemContext> file_system_context_;
395  scoped_refptr<quota::MockQuotaManagerProxy> quota_manager_proxy_;
396  scoped_refptr<quota::MockQuotaManager> quota_manager_;
397
398  DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationTestHelper);
399};
400
401TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFile) {
402  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
403                                       fileapi::kFileSystemTypeTemporary,
404                                       fileapi::kFileSystemTypePersistent);
405  helper.SetUp();
406
407  FileSystemURL src = helper.SourceURL("a");
408  FileSystemURL dest = helper.DestURL("b");
409  int64 src_initial_usage = helper.GetSourceUsage();
410  int64 dest_initial_usage = helper.GetDestUsage();
411
412  // Set up a source file.
413  ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
414  int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
415
416  // Copy it.
417  ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
418
419  // Verify.
420  ASSERT_TRUE(helper.FileExists(src, 10));
421  ASSERT_TRUE(helper.FileExists(dest, 10));
422
423  int64 src_new_usage = helper.GetSourceUsage();
424  ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
425
426  int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
427  ASSERT_EQ(src_increase, dest_increase);
428}
429
430TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleFile) {
431  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
432                                       fileapi::kFileSystemTypeTemporary,
433                                       fileapi::kFileSystemTypePersistent);
434  helper.SetUp();
435
436  FileSystemURL src = helper.SourceURL("a");
437  FileSystemURL dest = helper.DestURL("b");
438  int64 src_initial_usage = helper.GetSourceUsage();
439  int64 dest_initial_usage = helper.GetDestUsage();
440
441  // Set up a source file.
442  ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
443  int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
444
445  // Move it.
446  ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
447
448  // Verify.
449  ASSERT_FALSE(helper.FileExists(src, AsyncFileTestHelper::kDontCheckSize));
450  ASSERT_TRUE(helper.FileExists(dest, 10));
451
452  int64 src_new_usage = helper.GetSourceUsage();
453  ASSERT_EQ(src_initial_usage, src_new_usage);
454
455  int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
456  ASSERT_EQ(src_increase, dest_increase);
457}
458
459TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleDirectory) {
460  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
461                                       fileapi::kFileSystemTypeTemporary,
462                                       fileapi::kFileSystemTypePersistent);
463  helper.SetUp();
464
465  FileSystemURL src = helper.SourceURL("a");
466  FileSystemURL dest = helper.DestURL("b");
467  int64 src_initial_usage = helper.GetSourceUsage();
468  int64 dest_initial_usage = helper.GetDestUsage();
469
470  // Set up a source directory.
471  ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
472  int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
473
474  // Copy it.
475  ASSERT_EQ(base::File::FILE_OK, helper.Copy(src, dest));
476
477  // Verify.
478  ASSERT_TRUE(helper.DirectoryExists(src));
479  ASSERT_TRUE(helper.DirectoryExists(dest));
480
481  int64 src_new_usage = helper.GetSourceUsage();
482  ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
483
484  int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
485  ASSERT_EQ(src_increase, dest_increase);
486}
487
488TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleDirectory) {
489  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
490                                       fileapi::kFileSystemTypeTemporary,
491                                       fileapi::kFileSystemTypePersistent);
492  helper.SetUp();
493
494  FileSystemURL src = helper.SourceURL("a");
495  FileSystemURL dest = helper.DestURL("b");
496  int64 src_initial_usage = helper.GetSourceUsage();
497  int64 dest_initial_usage = helper.GetDestUsage();
498
499  // Set up a source directory.
500  ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
501  int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
502
503  // Move it.
504  ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
505
506  // Verify.
507  ASSERT_FALSE(helper.DirectoryExists(src));
508  ASSERT_TRUE(helper.DirectoryExists(dest));
509
510  int64 src_new_usage = helper.GetSourceUsage();
511  ASSERT_EQ(src_initial_usage, src_new_usage);
512
513  int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
514  ASSERT_EQ(src_increase, dest_increase);
515}
516
517TEST(LocalFileSystemCopyOrMoveOperationTest, CopyDirectory) {
518  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
519                                       fileapi::kFileSystemTypeTemporary,
520                                       fileapi::kFileSystemTypePersistent);
521  helper.SetUp();
522
523  FileSystemURL src = helper.SourceURL("a");
524  FileSystemURL dest = helper.DestURL("b");
525  int64 src_initial_usage = helper.GetSourceUsage();
526  int64 dest_initial_usage = helper.GetDestUsage();
527
528  // Set up a source directory.
529  ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
530  ASSERT_EQ(base::File::FILE_OK,
531            helper.SetUpTestCaseFiles(src,
532                                      kRegularFileSystemTestCases,
533                                      kRegularFileSystemTestCaseSize));
534  int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
535
536  // Copy it.
537  ASSERT_EQ(base::File::FILE_OK,
538            helper.CopyWithProgress(
539                src, dest,
540                AsyncFileTestHelper::CopyProgressCallback()));
541
542  // Verify.
543  ASSERT_TRUE(helper.DirectoryExists(src));
544  ASSERT_TRUE(helper.DirectoryExists(dest));
545
546  helper.VerifyTestCaseFiles(dest,
547                             kRegularFileSystemTestCases,
548                             kRegularFileSystemTestCaseSize);
549
550  int64 src_new_usage = helper.GetSourceUsage();
551  ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
552
553  int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
554  ASSERT_EQ(src_increase, dest_increase);
555}
556
557TEST(LocalFileSystemCopyOrMoveOperationTest, MoveDirectory) {
558  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
559                                       fileapi::kFileSystemTypeTemporary,
560                                       fileapi::kFileSystemTypePersistent);
561  helper.SetUp();
562
563  FileSystemURL src = helper.SourceURL("a");
564  FileSystemURL dest = helper.DestURL("b");
565  int64 src_initial_usage = helper.GetSourceUsage();
566  int64 dest_initial_usage = helper.GetDestUsage();
567
568  // Set up a source directory.
569  ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
570  ASSERT_EQ(base::File::FILE_OK,
571            helper.SetUpTestCaseFiles(src,
572                                      kRegularFileSystemTestCases,
573                                      kRegularFileSystemTestCaseSize));
574  int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
575
576  // Move it.
577  ASSERT_EQ(base::File::FILE_OK, helper.Move(src, dest));
578
579  // Verify.
580  ASSERT_FALSE(helper.DirectoryExists(src));
581  ASSERT_TRUE(helper.DirectoryExists(dest));
582
583  helper.VerifyTestCaseFiles(dest,
584                             kRegularFileSystemTestCases,
585                             kRegularFileSystemTestCaseSize);
586
587  int64 src_new_usage = helper.GetSourceUsage();
588  ASSERT_EQ(src_initial_usage, src_new_usage);
589
590  int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
591  ASSERT_EQ(src_increase, dest_increase);
592}
593
594TEST(LocalFileSystemCopyOrMoveOperationTest,
595     MoveDirectoryFailPostWriteValidation) {
596  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
597                                       fileapi::kFileSystemTypeTemporary,
598                                       fileapi::kFileSystemTypeTest);
599  helper.SetUp();
600
601  FileSystemURL src = helper.SourceURL("a");
602  FileSystemURL dest = helper.DestURL("b");
603
604  // Set up a source directory.
605  ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
606  ASSERT_EQ(base::File::FILE_OK,
607            helper.SetUpTestCaseFiles(src,
608                                      kRegularFileSystemTestCases,
609                                      kRegularFileSystemTestCaseSize));
610
611  // Move it.
612  helper.Move(src, dest);
613
614  // Verify.
615  ASSERT_TRUE(helper.DirectoryExists(src));
616  ASSERT_TRUE(helper.DirectoryExists(dest));
617
618  FileSystemTestCaseRecord kMoveDirResultCases[] = {
619    {false, FILE_PATH_LITERAL("file 0"), 38},
620    {false, FILE_PATH_LITERAL("file 3"), 0},
621  };
622
623  helper.VerifyTestCaseFiles(dest,
624                             kMoveDirResultCases,
625                             arraysize(kMoveDirResultCases));
626}
627
628TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) {
629  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
630                                       fileapi::kFileSystemTypeTemporary,
631                                       fileapi::kFileSystemTypeTest);
632  helper.SetUpNoValidator();
633
634  FileSystemURL src = helper.SourceURL("a");
635  FileSystemURL dest = helper.DestURL("b");
636
637  // Set up a source file.
638  ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10));
639
640  // The copy attempt should fail with a security error -- getting
641  // the factory returns a security error, and the copy operation must
642  // respect that.
643  ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.Copy(src, dest));
644}
645
646TEST(LocalFileSystemCopyOrMoveOperationTest, ProgressCallback) {
647  CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
648                                       fileapi::kFileSystemTypeTemporary,
649                                       fileapi::kFileSystemTypePersistent);
650  helper.SetUp();
651
652  FileSystemURL src = helper.SourceURL("a");
653  FileSystemURL dest = helper.DestURL("b");
654
655  // Set up a source directory.
656  ASSERT_EQ(base::File::FILE_OK, helper.CreateDirectory(src));
657  ASSERT_EQ(base::File::FILE_OK,
658            helper.SetUpTestCaseFiles(src,
659                                      kRegularFileSystemTestCases,
660                                      kRegularFileSystemTestCaseSize));
661
662  std::vector<ProgressRecord> records;
663  ASSERT_EQ(base::File::FILE_OK,
664            helper.CopyWithProgress(src, dest,
665                                    base::Bind(&RecordProgressCallback,
666                                               base::Unretained(&records))));
667
668  // Verify progress callback.
669  for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
670    const FileSystemTestCaseRecord& test_case = kRegularFileSystemTestCases[i];
671
672    FileSystemURL src_url = helper.SourceURL(
673        std::string("a/") + base::FilePath(test_case.path).AsUTF8Unsafe());
674    FileSystemURL dest_url = helper.DestURL(
675        std::string("b/") + base::FilePath(test_case.path).AsUTF8Unsafe());
676
677    // Find the first and last progress record.
678    size_t begin_index = records.size();
679    size_t end_index = records.size();
680    for (size_t j = 0; j < records.size(); ++j) {
681      if (records[j].source_url == src_url) {
682        if (begin_index == records.size())
683          begin_index = j;
684        end_index = j;
685      }
686    }
687
688    // The record should be found.
689    ASSERT_NE(begin_index, records.size());
690    ASSERT_NE(end_index, records.size());
691    ASSERT_NE(begin_index, end_index);
692
693    EXPECT_EQ(FileSystemOperation::BEGIN_COPY_ENTRY,
694              records[begin_index].type);
695    EXPECT_FALSE(records[begin_index].dest_url.is_valid());
696    EXPECT_EQ(FileSystemOperation::END_COPY_ENTRY, records[end_index].type);
697    EXPECT_EQ(dest_url, records[end_index].dest_url);
698
699    if (test_case.is_directory) {
700      // For directory copy, the progress shouldn't be interlaced.
701      EXPECT_EQ(begin_index + 1, end_index);
702    } else {
703      // PROGRESS event's size should be assending order.
704      int64 current_size = 0;
705      for (size_t j = begin_index + 1; j < end_index; ++j) {
706        if (records[j].source_url == src_url) {
707          EXPECT_EQ(FileSystemOperation::PROGRESS, records[j].type);
708          EXPECT_FALSE(records[j].dest_url.is_valid());
709          EXPECT_GE(records[j].size, current_size);
710          current_size = records[j].size;
711        }
712      }
713    }
714  }
715}
716
717TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper) {
718  base::ScopedTempDir temp_dir;
719  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
720  base::FilePath source_path = temp_dir.path().AppendASCII("source");
721  const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
722  base::WriteFile(source_path, kTestData,
723                  arraysize(kTestData) - 1);  // Exclude trailing '\0'.
724
725  base::FilePath dest_path = temp_dir.path().AppendASCII("dest");
726  // LocalFileWriter requires the file exists. So create an empty file here.
727  base::WriteFile(dest_path, "", 0);
728
729  base::MessageLoopForIO message_loop;
730  base::Thread file_thread("file_thread");
731  ASSERT_TRUE(file_thread.Start());
732  ScopedThreadStopper thread_stopper(&file_thread);
733  ASSERT_TRUE(thread_stopper.is_valid());
734
735  scoped_refptr<base::MessageLoopProxy> task_runner =
736      file_thread.message_loop_proxy();
737
738  scoped_ptr<webkit_blob::FileStreamReader> reader(
739      webkit_blob::FileStreamReader::CreateForLocalFile(
740          task_runner.get(), source_path, 0, base::Time()));
741
742  scoped_ptr<FileStreamWriter> writer(
743      FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0));
744
745  std::vector<int64> progress;
746  CopyOrMoveOperationDelegate::StreamCopyHelper helper(
747      reader.Pass(), writer.Pass(),
748      false,  // don't need flush
749      10,  // buffer size
750      base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
751      base::TimeDelta());  // For testing, we need all the progress.
752
753  base::File::Error error = base::File::FILE_ERROR_FAILED;
754  base::RunLoop run_loop;
755  helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
756  run_loop.Run();
757
758  EXPECT_EQ(base::File::FILE_OK, error);
759  ASSERT_EQ(5U, progress.size());
760  EXPECT_EQ(0, progress[0]);
761  EXPECT_EQ(10, progress[1]);
762  EXPECT_EQ(20, progress[2]);
763  EXPECT_EQ(30, progress[3]);
764  EXPECT_EQ(36, progress[4]);
765
766  std::string content;
767  ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
768  EXPECT_EQ(kTestData, content);
769}
770
771TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelperWithFlush) {
772  // Testing the same configuration as StreamCopyHelper, but with |need_flush|
773  // parameter set to true. Since it is hard to test that the flush is indeed
774  // taking place, this test just only verifies that the file is correctly
775  // written with or without the flag.
776  base::ScopedTempDir temp_dir;
777  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
778  base::FilePath source_path = temp_dir.path().AppendASCII("source");
779  const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
780  base::WriteFile(source_path, kTestData,
781                  arraysize(kTestData) - 1);  // Exclude trailing '\0'.
782
783  base::FilePath dest_path = temp_dir.path().AppendASCII("dest");
784  // LocalFileWriter requires the file exists. So create an empty file here.
785  base::WriteFile(dest_path, "", 0);
786
787  base::MessageLoopForIO message_loop;
788  base::Thread file_thread("file_thread");
789  ASSERT_TRUE(file_thread.Start());
790  ScopedThreadStopper thread_stopper(&file_thread);
791  ASSERT_TRUE(thread_stopper.is_valid());
792
793  scoped_refptr<base::MessageLoopProxy> task_runner =
794      file_thread.message_loop_proxy();
795
796  scoped_ptr<webkit_blob::FileStreamReader> reader(
797      webkit_blob::FileStreamReader::CreateForLocalFile(
798          task_runner.get(), source_path, 0, base::Time()));
799
800  scoped_ptr<FileStreamWriter> writer(
801      FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0));
802
803  std::vector<int64> progress;
804  CopyOrMoveOperationDelegate::StreamCopyHelper helper(
805      reader.Pass(), writer.Pass(),
806      true,  // need flush
807      10,  // buffer size
808      base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
809      base::TimeDelta());  // For testing, we need all the progress.
810
811  base::File::Error error = base::File::FILE_ERROR_FAILED;
812  base::RunLoop run_loop;
813  helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
814  run_loop.Run();
815
816  EXPECT_EQ(base::File::FILE_OK, error);
817  ASSERT_EQ(5U, progress.size());
818  EXPECT_EQ(0, progress[0]);
819  EXPECT_EQ(10, progress[1]);
820  EXPECT_EQ(20, progress[2]);
821  EXPECT_EQ(30, progress[3]);
822  EXPECT_EQ(36, progress[4]);
823
824  std::string content;
825  ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
826  EXPECT_EQ(kTestData, content);
827}
828
829TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper_Cancel) {
830  base::ScopedTempDir temp_dir;
831  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
832  base::FilePath source_path = temp_dir.path().AppendASCII("source");
833  const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
834  base::WriteFile(source_path, kTestData,
835                  arraysize(kTestData) - 1);  // Exclude trailing '\0'.
836
837  base::FilePath dest_path = temp_dir.path().AppendASCII("dest");
838  // LocalFileWriter requires the file exists. So create an empty file here.
839  base::WriteFile(dest_path, "", 0);
840
841  base::MessageLoopForIO message_loop;
842  base::Thread file_thread("file_thread");
843  ASSERT_TRUE(file_thread.Start());
844  ScopedThreadStopper thread_stopper(&file_thread);
845  ASSERT_TRUE(thread_stopper.is_valid());
846
847  scoped_refptr<base::MessageLoopProxy> task_runner =
848      file_thread.message_loop_proxy();
849
850  scoped_ptr<webkit_blob::FileStreamReader> reader(
851      webkit_blob::FileStreamReader::CreateForLocalFile(
852          task_runner.get(), source_path, 0, base::Time()));
853
854  scoped_ptr<FileStreamWriter> writer(
855      FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0));
856
857  std::vector<int64> progress;
858  CopyOrMoveOperationDelegate::StreamCopyHelper helper(
859      reader.Pass(), writer.Pass(),
860      false,  // need_flush
861      10,  // buffer size
862      base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
863      base::TimeDelta());  // For testing, we need all the progress.
864
865  // Call Cancel() later.
866  base::MessageLoopProxy::current()->PostTask(
867      FROM_HERE,
868      base::Bind(&CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel,
869                 base::Unretained(&helper)));
870
871  base::File::Error error = base::File::FILE_ERROR_FAILED;
872  base::RunLoop run_loop;
873  helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
874  run_loop.Run();
875
876  EXPECT_EQ(base::File::FILE_ERROR_ABORT, error);
877}
878
879}  // namespace content
880