1 // Copyright 2015 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 <stddef.h>
6 #include <stdint.h>
7 
8 #include <map>
9 #include <string>
10 
11 #include "base/macros.h"
12 #include "base/stl_util.h"
13 #include "base/test/task_environment.h"
14 #include "components/services/filesystem/directory_test_helper.h"
15 #include "components/services/filesystem/public/mojom/directory.mojom.h"
16 #include "mojo/public/cpp/bindings/pending_receiver.h"
17 #include "mojo/public/cpp/bindings/remote.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 namespace filesystem {
21 namespace {
22 
23 class DirectoryImplTest : public testing::Test {
24  public:
25   DirectoryImplTest() = default;
26 
CreateTempDir()27   mojo::Remote<mojom::Directory> CreateTempDir() {
28     return test_helper_.CreateTempDir();
29   }
30 
31  private:
32   base::test::TaskEnvironment task_environment_;
33   DirectoryTestHelper test_helper_;
34 
35   DISALLOW_COPY_AND_ASSIGN(DirectoryImplTest);
36 };
37 
38 constexpr char kData[] = "one two three";
39 
TEST_F(DirectoryImplTest,Read)40 TEST_F(DirectoryImplTest, Read) {
41   mojo::Remote<mojom::Directory> directory = CreateTempDir();
42   base::File::Error error;
43 
44   // Make some files.
45   const struct {
46     const char* name;
47     uint32_t open_flags;
48   } files_to_create[] = {
49       {"my_file1", mojom::kFlagRead | mojom::kFlagWrite | mojom::kFlagCreate},
50       {"my_file2", mojom::kFlagWrite | mojom::kFlagCreate},
51       {"my_file3", mojom::kFlagAppend | mojom::kFlagCreate}};
52   for (size_t i = 0; i < base::size(files_to_create); i++) {
53     error = base::File::Error::FILE_ERROR_FAILED;
54     bool handled =
55         directory->OpenFile(files_to_create[i].name, mojo::NullReceiver(),
56                             files_to_create[i].open_flags, &error);
57     ASSERT_TRUE(handled);
58     EXPECT_EQ(base::File::Error::FILE_OK, error);
59   }
60   // Make a directory.
61   error = base::File::Error::FILE_ERROR_FAILED;
62   bool handled = directory->OpenDirectory(
63       "my_dir", mojo::NullReceiver(),
64       mojom::kFlagRead | mojom::kFlagWrite | mojom::kFlagCreate, &error);
65   ASSERT_TRUE(handled);
66   EXPECT_EQ(base::File::Error::FILE_OK, error);
67 
68   error = base::File::Error::FILE_ERROR_FAILED;
69   base::Optional<std::vector<mojom::DirectoryEntryPtr>> directory_contents;
70   handled = directory->Read(&error, &directory_contents);
71   ASSERT_TRUE(handled);
72   EXPECT_EQ(base::File::Error::FILE_OK, error);
73   ASSERT_TRUE(directory_contents.has_value());
74 
75   // Expected contents of the directory.
76   std::map<std::string, mojom::FsFileType> expected_contents;
77   expected_contents["my_file1"] = mojom::FsFileType::REGULAR_FILE;
78   expected_contents["my_file2"] = mojom::FsFileType::REGULAR_FILE;
79   expected_contents["my_file3"] = mojom::FsFileType::REGULAR_FILE;
80   expected_contents["my_dir"] = mojom::FsFileType::DIRECTORY;
81   // Note: We don't expose ".." or ".".
82 
83   EXPECT_EQ(expected_contents.size(), directory_contents->size());
84   for (size_t i = 0; i < directory_contents->size(); i++) {
85     auto& item = directory_contents.value()[i];
86     ASSERT_TRUE(item);
87     auto it = expected_contents.find(item->name.AsUTF8Unsafe());
88     ASSERT_TRUE(it != expected_contents.end());
89     EXPECT_EQ(it->second, item->type);
90     expected_contents.erase(it);
91   }
92 }
93 
94 // TODO(vtl): Properly test OpenFile() and OpenDirectory() (including flags).
95 
TEST_F(DirectoryImplTest,BasicRenameDelete)96 TEST_F(DirectoryImplTest, BasicRenameDelete) {
97   mojo::Remote<mojom::Directory> directory = CreateTempDir();
98   base::File::Error error;
99 
100   // Create my_file.
101   error = base::File::Error::FILE_ERROR_FAILED;
102   bool handled =
103       directory->OpenFile("my_file", mojo::NullReceiver(),
104                           mojom::kFlagWrite | mojom::kFlagCreate, &error);
105   ASSERT_TRUE(handled);
106   EXPECT_EQ(base::File::Error::FILE_OK, error);
107 
108   // Opening my_file should succeed.
109   error = base::File::Error::FILE_ERROR_FAILED;
110   handled = directory->OpenFile("my_file", mojo::NullReceiver(),
111                                 mojom::kFlagRead | mojom::kFlagOpen, &error);
112   ASSERT_TRUE(handled);
113   EXPECT_EQ(base::File::Error::FILE_OK, error);
114 
115   // Rename my_file to my_new_file.
116   handled = directory->Rename("my_file", "my_new_file", &error);
117   ASSERT_TRUE(handled);
118   EXPECT_EQ(base::File::Error::FILE_OK, error);
119 
120   // Opening my_file should fail.
121 
122   error = base::File::Error::FILE_ERROR_FAILED;
123   handled = directory->OpenFile("my_file", mojo::NullReceiver(),
124                                 mojom::kFlagRead | mojom::kFlagOpen, &error);
125   ASSERT_TRUE(handled);
126   EXPECT_EQ(base::File::Error::FILE_ERROR_NOT_FOUND, error);
127 
128   // Opening my_new_file should succeed.
129   error = base::File::Error::FILE_ERROR_FAILED;
130   handled = directory->OpenFile("my_new_file", mojo::NullReceiver(),
131                                 mojom::kFlagRead | mojom::kFlagOpen, &error);
132   ASSERT_TRUE(handled);
133   EXPECT_EQ(base::File::Error::FILE_OK, error);
134 
135   // Delete my_new_file (no flags).
136   handled = directory->Delete("my_new_file", 0, &error);
137   ASSERT_TRUE(handled);
138   EXPECT_EQ(base::File::Error::FILE_OK, error);
139 
140   // Opening my_new_file should fail.
141   error = base::File::Error::FILE_ERROR_FAILED;
142   handled = directory->OpenFile("my_new_file", mojo::NullReceiver(),
143                                 mojom::kFlagRead | mojom::kFlagOpen, &error);
144   ASSERT_TRUE(handled);
145   EXPECT_EQ(base::File::Error::FILE_ERROR_NOT_FOUND, error);
146 }
147 
TEST_F(DirectoryImplTest,CantOpenDirectoriesAsFiles)148 TEST_F(DirectoryImplTest, CantOpenDirectoriesAsFiles) {
149   mojo::Remote<mojom::Directory> directory = CreateTempDir();
150   base::File::Error error;
151 
152   {
153     // Create a directory called 'my_file'
154     mojo::Remote<mojom::Directory> my_file_directory;
155     error = base::File::Error::FILE_ERROR_FAILED;
156     bool handled = directory->OpenDirectory(
157         "my_file", my_file_directory.BindNewPipeAndPassReceiver(),
158         mojom::kFlagRead | mojom::kFlagWrite | mojom::kFlagCreate, &error);
159     ASSERT_TRUE(handled);
160     EXPECT_EQ(base::File::Error::FILE_OK, error);
161   }
162 
163   {
164     // Attempt to open that directory as a file. This must fail!
165     mojo::Remote<mojom::File> file;
166     error = base::File::Error::FILE_ERROR_FAILED;
167     bool handled =
168         directory->OpenFile("my_file", file.BindNewPipeAndPassReceiver(),
169                             mojom::kFlagRead | mojom::kFlagOpen, &error);
170     ASSERT_TRUE(handled);
171     EXPECT_EQ(base::File::Error::FILE_ERROR_NOT_A_FILE, error);
172   }
173 }
174 
TEST_F(DirectoryImplTest,Clone)175 TEST_F(DirectoryImplTest, Clone) {
176   mojo::Remote<mojom::Directory> clone_one;
177   mojo::Remote<mojom::Directory> clone_two;
178   base::File::Error error;
179 
180   {
181     mojo::Remote<mojom::Directory> directory = CreateTempDir();
182     directory->Clone(clone_one.BindNewPipeAndPassReceiver());
183     directory->Clone(clone_two.BindNewPipeAndPassReceiver());
184 
185     // Original temporary directory goes out of scope here; shouldn't be
186     // deleted since it has clones.
187   }
188 
189   std::vector<uint8_t> data(kData, kData + strlen(kData));
190   {
191     bool handled = clone_one->WriteFile("data", data, &error);
192     ASSERT_TRUE(handled);
193     EXPECT_EQ(base::File::Error::FILE_OK, error);
194   }
195 
196   {
197     std::vector<uint8_t> file_contents;
198     bool handled = clone_two->ReadEntireFile("data", &error, &file_contents);
199     ASSERT_TRUE(handled);
200     EXPECT_EQ(base::File::Error::FILE_OK, error);
201 
202     EXPECT_EQ(data, file_contents);
203   }
204 }
205 
TEST_F(DirectoryImplTest,WriteFileReadFile)206 TEST_F(DirectoryImplTest, WriteFileReadFile) {
207   mojo::Remote<mojom::Directory> directory = CreateTempDir();
208   base::File::Error error;
209 
210   std::vector<uint8_t> data(kData, kData + strlen(kData));
211   {
212     bool handled = directory->WriteFile("data", data, &error);
213     ASSERT_TRUE(handled);
214     EXPECT_EQ(base::File::Error::FILE_OK, error);
215   }
216 
217   {
218     std::vector<uint8_t> file_contents;
219     bool handled = directory->ReadEntireFile("data", &error, &file_contents);
220     ASSERT_TRUE(handled);
221     EXPECT_EQ(base::File::Error::FILE_OK, error);
222 
223     EXPECT_EQ(data, file_contents);
224   }
225 }
226 
TEST_F(DirectoryImplTest,ReadEmptyFileIsNotFoundError)227 TEST_F(DirectoryImplTest, ReadEmptyFileIsNotFoundError) {
228   mojo::Remote<mojom::Directory> directory = CreateTempDir();
229   base::File::Error error;
230 
231   {
232     std::vector<uint8_t> file_contents;
233     bool handled =
234         directory->ReadEntireFile("doesnt_exist", &error, &file_contents);
235     ASSERT_TRUE(handled);
236     EXPECT_EQ(base::File::Error::FILE_ERROR_NOT_FOUND, error);
237   }
238 }
239 
TEST_F(DirectoryImplTest,CantReadEntireFileOnADirectory)240 TEST_F(DirectoryImplTest, CantReadEntireFileOnADirectory) {
241   mojo::Remote<mojom::Directory> directory = CreateTempDir();
242   base::File::Error error;
243 
244   // Create a directory
245   {
246     mojo::Remote<mojom::Directory> my_file_directory;
247     error = base::File::Error::FILE_ERROR_FAILED;
248     bool handled = directory->OpenDirectory(
249         "my_dir", my_file_directory.BindNewPipeAndPassReceiver(),
250         mojom::kFlagRead | mojom::kFlagWrite | mojom::kFlagCreate, &error);
251     ASSERT_TRUE(handled);
252     EXPECT_EQ(base::File::Error::FILE_OK, error);
253   }
254 
255   // Try to read it as a file
256   {
257     std::vector<uint8_t> file_contents;
258     bool handled = directory->ReadEntireFile("my_dir", &error, &file_contents);
259     ASSERT_TRUE(handled);
260     EXPECT_EQ(base::File::Error::FILE_ERROR_NOT_A_FILE, error);
261   }
262 }
263 
TEST_F(DirectoryImplTest,CantWriteFileOnADirectory)264 TEST_F(DirectoryImplTest, CantWriteFileOnADirectory) {
265   mojo::Remote<mojom::Directory> directory = CreateTempDir();
266   base::File::Error error;
267 
268   // Create a directory
269   {
270     mojo::Remote<mojom::Directory> my_file_directory;
271     error = base::File::Error::FILE_ERROR_FAILED;
272     bool handled = directory->OpenDirectory(
273         "my_dir", my_file_directory.BindNewPipeAndPassReceiver(),
274         mojom::kFlagRead | mojom::kFlagWrite | mojom::kFlagCreate, &error);
275     ASSERT_TRUE(handled);
276     EXPECT_EQ(base::File::Error::FILE_OK, error);
277   }
278 
279   {
280     std::vector<uint8_t> data(kData, kData + strlen(kData));
281     bool handled = directory->WriteFile("my_dir", data, &error);
282     ASSERT_TRUE(handled);
283     EXPECT_EQ(base::File::Error::FILE_ERROR_NOT_A_FILE, error);
284   }
285 }
286 
TEST_F(DirectoryImplTest,Flush)287 TEST_F(DirectoryImplTest, Flush) {
288   mojo::Remote<mojom::Directory> directory = CreateTempDir();
289   base::File::Error error;
290 
291   {
292     bool handled = directory->Flush(&error);
293     ASSERT_TRUE(handled);
294     EXPECT_EQ(base::File::Error::FILE_OK, error);
295   }
296 }
297 
298 }  // namespace
299 }  // namespace filesystem
300