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 <utility>
6 
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/macros.h"
11 #include "base/memory/scoped_refptr.h"
12 #include "base/run_loop.h"
13 #include "base/task/post_task.h"
14 #include "base/task/task_traits.h"
15 #include "base/task/thread_pool.h"
16 #include "base/task/thread_pool/thread_pool_instance.h"
17 #include "base/test/task_environment.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "base/threading/thread_task_runner_handle.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/test/browser_task_environment.h"
22 #include "storage/browser/file_system/external_mount_points.h"
23 #include "storage/browser/file_system/file_system_backend.h"
24 #include "storage/browser/file_system/file_system_context.h"
25 #include "storage/browser/file_system/file_system_operation_runner.h"
26 #include "storage/browser/test/mock_special_storage_policy.h"
27 #include "storage/browser/test/test_file_system_context.h"
28 #include "storage/browser/test/test_file_system_options.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "url/gurl.h"
31 #include "url/origin.h"
32 
33 using storage::FileSystemContext;
34 using storage::FileSystemOperationRunner;
35 using storage::FileSystemType;
36 using storage::FileSystemURL;
37 
38 namespace content {
39 
40 namespace {
41 
GetStatus(bool * done,base::File::Error * status_out,base::File::Error status)42 void GetStatus(bool* done,
43                base::File::Error* status_out,
44                base::File::Error status) {
45   ASSERT_FALSE(*done);
46   *done = true;
47   *status_out = status;
48 }
49 
GetCancelStatus(bool * operation_done,bool * cancel_done,base::File::Error * status_out,base::File::Error status)50 void GetCancelStatus(bool* operation_done,
51                      bool* cancel_done,
52                      base::File::Error* status_out,
53                      base::File::Error status) {
54   // Cancel callback must be always called after the operation's callback.
55   ASSERT_TRUE(*operation_done);
56   ASSERT_FALSE(*cancel_done);
57   *cancel_done = true;
58   *status_out = status;
59 }
60 
DidOpenFile(base::File file,base::OnceClosure on_close_callback)61 void DidOpenFile(base::File file, base::OnceClosure on_close_callback) {}
62 
63 }  // namespace
64 
65 class FileSystemOperationRunnerTest : public testing::Test {
66  protected:
FileSystemOperationRunnerTest()67   FileSystemOperationRunnerTest() {}
~FileSystemOperationRunnerTest()68   ~FileSystemOperationRunnerTest() override {}
69 
SetUp()70   void SetUp() override {
71     ASSERT_TRUE(base_.CreateUniqueTempDir());
72     base::FilePath base_dir = base_.GetPath();
73     file_system_context_ =
74         storage::CreateFileSystemContextForTesting(nullptr, base_dir);
75   }
76 
TearDown()77   void TearDown() override {
78     file_system_context_ = nullptr;
79     base::RunLoop().RunUntilIdle();
80   }
81 
URL(const std::string & path)82   FileSystemURL URL(const std::string& path) {
83     return file_system_context_->CreateCrackedFileSystemURL(
84         url::Origin::Create(GURL("http://example.com")),
85         storage::kFileSystemTypeTemporary,
86         base::FilePath::FromUTF8Unsafe(path));
87   }
88 
operation_runner()89   FileSystemOperationRunner* operation_runner() {
90     return file_system_context_->operation_runner();
91   }
92 
93  private:
94   base::ScopedTempDir base_;
95   base::test::SingleThreadTaskEnvironment task_environment_;
96   scoped_refptr<FileSystemContext> file_system_context_;
97 
98   DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunnerTest);
99 };
100 
TEST_F(FileSystemOperationRunnerTest,NotFoundError)101 TEST_F(FileSystemOperationRunnerTest, NotFoundError) {
102   bool done = false;
103   base::File::Error status = base::File::FILE_ERROR_FAILED;
104 
105   // Regular NOT_FOUND error, which is called asynchronously.
106   operation_runner()->Truncate(URL("foo"), 0,
107                                base::BindOnce(&GetStatus, &done, &status));
108   ASSERT_FALSE(done);
109   base::RunLoop().RunUntilIdle();
110   ASSERT_TRUE(done);
111   ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, status);
112 }
113 
TEST_F(FileSystemOperationRunnerTest,InvalidURLError)114 TEST_F(FileSystemOperationRunnerTest, InvalidURLError) {
115   bool done = false;
116   base::File::Error status = base::File::FILE_ERROR_FAILED;
117 
118   // Invalid URL error, which calls DidFinish synchronously.
119   operation_runner()->Truncate(FileSystemURL(), 0,
120                                base::BindOnce(&GetStatus, &done, &status));
121   // The error call back shouldn't be fired synchronously.
122   ASSERT_FALSE(done);
123 
124   base::RunLoop().RunUntilIdle();
125   ASSERT_TRUE(done);
126   ASSERT_EQ(base::File::FILE_ERROR_INVALID_URL, status);
127 }
128 
TEST_F(FileSystemOperationRunnerTest,NotFoundErrorAndCancel)129 TEST_F(FileSystemOperationRunnerTest, NotFoundErrorAndCancel) {
130   bool done = false;
131   bool cancel_done = false;
132   base::File::Error status = base::File::FILE_ERROR_FAILED;
133   base::File::Error cancel_status = base::File::FILE_ERROR_FAILED;
134 
135   // Call Truncate with non-existent URL, and try to cancel it immediately
136   // after that (before its callback is fired).
137   FileSystemOperationRunner::OperationID id = operation_runner()->Truncate(
138       URL("foo"), 0, base::BindOnce(&GetStatus, &done, &status));
139   operation_runner()->Cancel(id, base::BindOnce(&GetCancelStatus, &done,
140                                                 &cancel_done, &cancel_status));
141 
142   ASSERT_FALSE(done);
143   ASSERT_FALSE(cancel_done);
144   base::RunLoop().RunUntilIdle();
145 
146   ASSERT_TRUE(done);
147   ASSERT_TRUE(cancel_done);
148   ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, status);
149   ASSERT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, cancel_status);
150 }
151 
TEST_F(FileSystemOperationRunnerTest,InvalidURLErrorAndCancel)152 TEST_F(FileSystemOperationRunnerTest, InvalidURLErrorAndCancel) {
153   bool done = false;
154   bool cancel_done = false;
155   base::File::Error status = base::File::FILE_ERROR_FAILED;
156   base::File::Error cancel_status = base::File::FILE_ERROR_FAILED;
157 
158   // Call Truncate with invalid URL, and try to cancel it immediately
159   // after that (before its callback is fired).
160   FileSystemOperationRunner::OperationID id = operation_runner()->Truncate(
161       FileSystemURL(), 0, base::BindOnce(&GetStatus, &done, &status));
162   operation_runner()->Cancel(id, base::BindOnce(&GetCancelStatus, &done,
163                                                 &cancel_done, &cancel_status));
164 
165   ASSERT_FALSE(done);
166   ASSERT_FALSE(cancel_done);
167   base::RunLoop().RunUntilIdle();
168 
169   ASSERT_TRUE(done);
170   ASSERT_TRUE(cancel_done);
171   ASSERT_EQ(base::File::FILE_ERROR_INVALID_URL, status);
172   ASSERT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, cancel_status);
173 }
174 
TEST_F(FileSystemOperationRunnerTest,CancelWithInvalidId)175 TEST_F(FileSystemOperationRunnerTest, CancelWithInvalidId) {
176   const FileSystemOperationRunner::OperationID kInvalidId = -1;
177   bool done = true;  // The operation is not running.
178   bool cancel_done = false;
179   base::File::Error cancel_status = base::File::FILE_ERROR_FAILED;
180   operation_runner()->Cancel(
181       kInvalidId,
182       base::BindOnce(&GetCancelStatus, &done, &cancel_done, &cancel_status));
183 
184   ASSERT_TRUE(cancel_done);
185   ASSERT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, cancel_status);
186 }
187 
188 class MultiThreadFileSystemOperationRunnerTest : public testing::Test {
189  public:
MultiThreadFileSystemOperationRunnerTest()190   MultiThreadFileSystemOperationRunnerTest()
191       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}
192 
SetUp()193   void SetUp() override {
194     ASSERT_TRUE(base_.CreateUniqueTempDir());
195 
196     base::FilePath base_dir = base_.GetPath();
197     file_system_context_ = base::MakeRefCounted<FileSystemContext>(
198         base::ThreadTaskRunnerHandle::Get().get(),
199         base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}).get(),
200         storage::ExternalMountPoints::CreateRefCounted().get(),
201         base::MakeRefCounted<storage::MockSpecialStoragePolicy>().get(),
202         nullptr, std::vector<std::unique_ptr<storage::FileSystemBackend>>(),
203         std::vector<storage::URLRequestAutoMountHandler>(), base_dir,
204         storage::CreateAllowFileAccessOptions());
205 
206     // Disallow IO on the main loop.
207     base::ThreadRestrictions::SetIOAllowed(false);
208   }
209 
TearDown()210   void TearDown() override {
211     base::ThreadRestrictions::SetIOAllowed(true);
212     file_system_context_ = nullptr;
213   }
214 
URL(const std::string & path)215   FileSystemURL URL(const std::string& path) {
216     return file_system_context_->CreateCrackedFileSystemURL(
217         url::Origin::Create(GURL("http://example.com")),
218         storage::kFileSystemTypeTemporary,
219         base::FilePath::FromUTF8Unsafe(path));
220   }
221 
operation_runner()222   FileSystemOperationRunner* operation_runner() {
223     return file_system_context_->operation_runner();
224   }
225 
226  private:
227   base::ScopedTempDir base_;
228   content::BrowserTaskEnvironment task_environment_;
229   scoped_refptr<FileSystemContext> file_system_context_;
230 
231   DISALLOW_COPY_AND_ASSIGN(MultiThreadFileSystemOperationRunnerTest);
232 };
233 
TEST_F(MultiThreadFileSystemOperationRunnerTest,OpenAndShutdown)234 TEST_F(MultiThreadFileSystemOperationRunnerTest, OpenAndShutdown) {
235   // Call OpenFile and immediately shutdown the runner.
236   operation_runner()->OpenFile(
237       URL("foo"), base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE,
238       base::BindOnce(&DidOpenFile));
239   operation_runner()->Shutdown();
240 
241   // Wait until the task posted on the blocking thread is done.
242   base::ThreadPoolInstance::Get()->FlushForTesting();
243   // This should finish without thread assertion failure on debug build.
244 }
245 
246 }  // namespace content
247