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 "storage/browser/fileapi/recursive_operation_delegate.h"
6
7 #include <vector>
8
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/message_loop/message_loop_proxy.h"
15 #include "base/run_loop.h"
16 #include "content/public/test/sandbox_file_system_test_helper.h"
17 #include "storage/browser/fileapi/file_system_file_util.h"
18 #include "storage/browser/fileapi/file_system_operation.h"
19 #include "storage/browser/fileapi/file_system_operation_runner.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 using storage::FileSystemContext;
23 using storage::FileSystemOperationContext;
24 using storage::FileSystemURL;
25
26 namespace content {
27 namespace {
28
29 class LoggingRecursiveOperation : public storage::RecursiveOperationDelegate {
30 public:
31 struct LogEntry {
32 enum Type {
33 PROCESS_FILE,
34 PROCESS_DIRECTORY,
35 POST_PROCESS_DIRECTORY
36 };
37 Type type;
38 FileSystemURL url;
39 };
40
LoggingRecursiveOperation(FileSystemContext * file_system_context,const FileSystemURL & root,const StatusCallback & callback)41 LoggingRecursiveOperation(FileSystemContext* file_system_context,
42 const FileSystemURL& root,
43 const StatusCallback& callback)
44 : storage::RecursiveOperationDelegate(file_system_context),
45 root_(root),
46 callback_(callback),
47 weak_factory_(this) {}
~LoggingRecursiveOperation()48 virtual ~LoggingRecursiveOperation() {}
49
log_entries() const50 const std::vector<LogEntry>& log_entries() const { return log_entries_; }
51
52 // RecursiveOperationDelegate overrides.
Run()53 virtual void Run() OVERRIDE {
54 NOTREACHED();
55 }
56
RunRecursively()57 virtual void RunRecursively() OVERRIDE {
58 StartRecursiveOperation(root_, callback_);
59 }
60
ProcessFile(const FileSystemURL & url,const StatusCallback & callback)61 virtual void ProcessFile(const FileSystemURL& url,
62 const StatusCallback& callback) OVERRIDE {
63 RecordLogEntry(LogEntry::PROCESS_FILE, url);
64 operation_runner()->GetMetadata(
65 url,
66 base::Bind(&LoggingRecursiveOperation::DidGetMetadata,
67 weak_factory_.GetWeakPtr(), callback));
68 }
69
ProcessDirectory(const FileSystemURL & url,const StatusCallback & callback)70 virtual void ProcessDirectory(const FileSystemURL& url,
71 const StatusCallback& callback) OVERRIDE {
72 RecordLogEntry(LogEntry::PROCESS_DIRECTORY, url);
73 callback.Run(base::File::FILE_OK);
74 }
75
PostProcessDirectory(const FileSystemURL & url,const StatusCallback & callback)76 virtual void PostProcessDirectory(const FileSystemURL& url,
77 const StatusCallback& callback) OVERRIDE {
78 RecordLogEntry(LogEntry::POST_PROCESS_DIRECTORY, url);
79 callback.Run(base::File::FILE_OK);
80 }
81
82 private:
RecordLogEntry(LogEntry::Type type,const FileSystemURL & url)83 void RecordLogEntry(LogEntry::Type type, const FileSystemURL& url) {
84 LogEntry entry;
85 entry.type = type;
86 entry.url = url;
87 log_entries_.push_back(entry);
88 }
89
DidGetMetadata(const StatusCallback & callback,base::File::Error result,const base::File::Info & file_info)90 void DidGetMetadata(const StatusCallback& callback,
91 base::File::Error result,
92 const base::File::Info& file_info) {
93 if (result != base::File::FILE_OK) {
94 callback.Run(result);
95 return;
96 }
97
98 callback.Run(file_info.is_directory ?
99 base::File::FILE_ERROR_NOT_A_FILE :
100 base::File::FILE_OK);
101 }
102
103 FileSystemURL root_;
104 StatusCallback callback_;
105 std::vector<LogEntry> log_entries_;
106
107 base::WeakPtrFactory<LoggingRecursiveOperation> weak_factory_;
108 DISALLOW_COPY_AND_ASSIGN(LoggingRecursiveOperation);
109 };
110
ReportStatus(base::File::Error * out_error,base::File::Error error)111 void ReportStatus(base::File::Error* out_error,
112 base::File::Error error) {
113 DCHECK(out_error);
114 *out_error = error;
115 }
116
117 // To test the Cancel() during operation, calls Cancel() of |operation|
118 // after |counter| times message posting.
CallCancelLater(storage::RecursiveOperationDelegate * operation,int counter)119 void CallCancelLater(storage::RecursiveOperationDelegate* operation,
120 int counter) {
121 if (counter > 0) {
122 base::MessageLoopProxy::current()->PostTask(
123 FROM_HERE,
124 base::Bind(&CallCancelLater, base::Unretained(operation), counter - 1));
125 return;
126 }
127
128 operation->Cancel();
129 }
130
131 } // namespace
132
133 class RecursiveOperationDelegateTest : public testing::Test {
134 protected:
SetUp()135 virtual void SetUp() OVERRIDE {
136 EXPECT_TRUE(base_.CreateUniqueTempDir());
137 sandbox_file_system_.SetUp(base_.path().AppendASCII("filesystem"));
138 }
139
TearDown()140 virtual void TearDown() OVERRIDE {
141 sandbox_file_system_.TearDown();
142 }
143
NewContext()144 scoped_ptr<FileSystemOperationContext> NewContext() {
145 FileSystemOperationContext* context =
146 sandbox_file_system_.NewOperationContext();
147 // Grant enough quota for all test cases.
148 context->set_allowed_bytes_growth(1000000);
149 return make_scoped_ptr(context);
150 }
151
file_util()152 storage::FileSystemFileUtil* file_util() {
153 return sandbox_file_system_.file_util();
154 }
155
URLForPath(const std::string & path) const156 FileSystemURL URLForPath(const std::string& path) const {
157 return sandbox_file_system_.CreateURLFromUTF8(path);
158 }
159
CreateFile(const std::string & path)160 FileSystemURL CreateFile(const std::string& path) {
161 FileSystemURL url = URLForPath(path);
162 bool created = false;
163 EXPECT_EQ(base::File::FILE_OK,
164 file_util()->EnsureFileExists(NewContext().get(),
165 url, &created));
166 EXPECT_TRUE(created);
167 return url;
168 }
169
CreateDirectory(const std::string & path)170 FileSystemURL CreateDirectory(const std::string& path) {
171 FileSystemURL url = URLForPath(path);
172 EXPECT_EQ(base::File::FILE_OK,
173 file_util()->CreateDirectory(NewContext().get(), url,
174 false /* exclusive */, true));
175 return url;
176 }
177
178 private:
179 base::MessageLoop message_loop_;
180
181 // Common temp base for nondestructive uses.
182 base::ScopedTempDir base_;
183 SandboxFileSystemTestHelper sandbox_file_system_;
184 };
185
TEST_F(RecursiveOperationDelegateTest,RootIsFile)186 TEST_F(RecursiveOperationDelegateTest, RootIsFile) {
187 FileSystemURL src_file(CreateFile("src"));
188
189 base::File::Error error = base::File::FILE_ERROR_FAILED;
190 scoped_ptr<FileSystemOperationContext> context = NewContext();
191 scoped_ptr<LoggingRecursiveOperation> operation(
192 new LoggingRecursiveOperation(
193 context->file_system_context(), src_file,
194 base::Bind(&ReportStatus, &error)));
195 operation->RunRecursively();
196 base::RunLoop().RunUntilIdle();
197 ASSERT_EQ(base::File::FILE_OK, error);
198
199 const std::vector<LoggingRecursiveOperation::LogEntry>& log_entries =
200 operation->log_entries();
201 ASSERT_EQ(1U, log_entries.size());
202 const LoggingRecursiveOperation::LogEntry& entry = log_entries[0];
203 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, entry.type);
204 EXPECT_EQ(src_file, entry.url);
205 }
206
TEST_F(RecursiveOperationDelegateTest,RootIsDirectory)207 TEST_F(RecursiveOperationDelegateTest, RootIsDirectory) {
208 FileSystemURL src_root(CreateDirectory("src"));
209 FileSystemURL src_dir1(CreateDirectory("src/dir1"));
210 FileSystemURL src_file1(CreateFile("src/file1"));
211 FileSystemURL src_file2(CreateFile("src/dir1/file2"));
212 FileSystemURL src_file3(CreateFile("src/dir1/file3"));
213
214 base::File::Error error = base::File::FILE_ERROR_FAILED;
215 scoped_ptr<FileSystemOperationContext> context = NewContext();
216 scoped_ptr<LoggingRecursiveOperation> operation(
217 new LoggingRecursiveOperation(
218 context->file_system_context(), src_root,
219 base::Bind(&ReportStatus, &error)));
220 operation->RunRecursively();
221 base::RunLoop().RunUntilIdle();
222 ASSERT_EQ(base::File::FILE_OK, error);
223
224 const std::vector<LoggingRecursiveOperation::LogEntry>& log_entries =
225 operation->log_entries();
226 ASSERT_EQ(8U, log_entries.size());
227
228 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
229 log_entries[0].type);
230 EXPECT_EQ(src_root, log_entries[0].url);
231
232 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
233 log_entries[1].type);
234 EXPECT_EQ(src_root, log_entries[1].url);
235
236 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
237 log_entries[2].type);
238 EXPECT_EQ(src_file1, log_entries[2].url);
239
240 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY,
241 log_entries[3].type);
242 EXPECT_EQ(src_dir1, log_entries[3].url);
243
244 // The order of src/dir1/file2 and src/dir1/file3 depends on the file system
245 // implementation (can be swapped).
246 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
247 log_entries[4].type);
248 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE,
249 log_entries[5].type);
250 EXPECT_TRUE((src_file2 == log_entries[4].url &&
251 src_file3 == log_entries[5].url) ||
252 (src_file3 == log_entries[4].url &&
253 src_file2 == log_entries[5].url));
254
255 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY,
256 log_entries[6].type);
257 EXPECT_EQ(src_dir1, log_entries[6].url);
258
259 EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY,
260 log_entries[7].type);
261 EXPECT_EQ(src_root, log_entries[7].url);
262 }
263
TEST_F(RecursiveOperationDelegateTest,Cancel)264 TEST_F(RecursiveOperationDelegateTest, Cancel) {
265 FileSystemURL src_root(CreateDirectory("src"));
266 FileSystemURL src_dir1(CreateDirectory("src/dir1"));
267 FileSystemURL src_file1(CreateFile("src/file1"));
268 FileSystemURL src_file2(CreateFile("src/dir1/file2"));
269
270 base::File::Error error = base::File::FILE_ERROR_FAILED;
271 scoped_ptr<FileSystemOperationContext> context = NewContext();
272 scoped_ptr<LoggingRecursiveOperation> operation(
273 new LoggingRecursiveOperation(
274 context->file_system_context(), src_root,
275 base::Bind(&ReportStatus, &error)));
276 operation->RunRecursively();
277
278 // Invoke Cancel(), after 5 times message posting.
279 CallCancelLater(operation.get(), 5);
280 base::RunLoop().RunUntilIdle();
281 ASSERT_EQ(base::File::FILE_ERROR_ABORT, error);
282 }
283
284 } // namespace content
285