1 | // Copyright 2013 The Flutter 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 <memory> |
6 | #include <vector> |
7 | |
8 | #include "gtest/gtest.h" |
9 | |
10 | #include "flutter/fml/build_config.h" |
11 | #include "flutter/fml/file.h" |
12 | #include "flutter/fml/mapping.h" |
13 | #include "flutter/fml/paths.h" |
14 | #include "flutter/fml/unique_fd.h" |
15 | |
16 | static bool WriteStringToFile(const fml::UniqueFD& fd, |
17 | const std::string& contents) { |
18 | if (!fml::TruncateFile(fd, contents.size())) { |
19 | return false; |
20 | } |
21 | |
22 | fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kWrite}); |
23 | if (mapping.GetSize() != contents.size()) { |
24 | return false; |
25 | } |
26 | |
27 | if (mapping.GetMutableMapping() == nullptr) { |
28 | return false; |
29 | } |
30 | |
31 | ::memmove(mapping.GetMutableMapping(), contents.data(), contents.size()); |
32 | return true; |
33 | } |
34 | |
35 | static std::string ReadStringFromFile(const fml::UniqueFD& fd) { |
36 | fml::FileMapping mapping(fd); |
37 | |
38 | if (mapping.GetMapping() == nullptr) { |
39 | return nullptr; |
40 | } |
41 | |
42 | return {reinterpret_cast<const char*>(mapping.GetMapping()), |
43 | mapping.GetSize()}; |
44 | } |
45 | |
46 | TEST(FileTest, CreateTemporaryAndUnlink) { |
47 | auto dir_name = fml::CreateTemporaryDirectory(); |
48 | ASSERT_NE(dir_name, "" ); |
49 | auto dir = |
50 | fml::OpenDirectory(dir_name.c_str(), false, fml::FilePermission::kRead); |
51 | ASSERT_TRUE(dir.is_valid()); |
52 | dir.reset(); |
53 | ASSERT_TRUE(fml::UnlinkDirectory(dir_name.c_str())); |
54 | } |
55 | |
56 | TEST(FileTest, ScopedTempDirIsValid) { |
57 | fml::ScopedTemporaryDirectory dir; |
58 | ASSERT_TRUE(dir.fd().is_valid()); |
59 | } |
60 | |
61 | TEST(FileTest, CanOpenFileForWriting) { |
62 | fml::ScopedTemporaryDirectory dir; |
63 | ASSERT_TRUE(dir.fd().is_valid()); |
64 | |
65 | auto fd = |
66 | fml::OpenFile(dir.fd(), "some.txt" , true, fml::FilePermission::kWrite); |
67 | ASSERT_TRUE(fd.is_valid()); |
68 | fd.reset(); |
69 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "some.txt" )); |
70 | } |
71 | |
72 | TEST(FileTest, CanTruncateAndWrite) { |
73 | fml::ScopedTemporaryDirectory dir; |
74 | ASSERT_TRUE(dir.fd().is_valid()); |
75 | |
76 | std::string contents = "some contents here" ; |
77 | |
78 | { |
79 | auto fd = fml::OpenFile(dir.fd(), "some.txt" , true, |
80 | fml::FilePermission::kReadWrite); |
81 | ASSERT_TRUE(fd.is_valid()); |
82 | |
83 | ASSERT_TRUE(fml::TruncateFile(fd, contents.size())); |
84 | |
85 | fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kWrite}); |
86 | ASSERT_EQ(mapping.GetSize(), contents.size()); |
87 | ASSERT_NE(mapping.GetMutableMapping(), nullptr); |
88 | |
89 | ::memcpy(mapping.GetMutableMapping(), contents.data(), contents.size()); |
90 | } |
91 | |
92 | { |
93 | auto fd = |
94 | fml::OpenFile(dir.fd(), "some.txt" , false, fml::FilePermission::kRead); |
95 | ASSERT_TRUE(fd.is_valid()); |
96 | |
97 | fml::FileMapping mapping(fd); |
98 | ASSERT_EQ(mapping.GetSize(), contents.size()); |
99 | |
100 | ASSERT_EQ(0, |
101 | ::memcmp(mapping.GetMapping(), contents.data(), contents.size())); |
102 | } |
103 | |
104 | fml::UnlinkFile(dir.fd(), "some.txt" ); |
105 | } |
106 | |
107 | TEST(FileTest, CreateDirectoryStructure) { |
108 | fml::ScopedTemporaryDirectory dir; |
109 | |
110 | std::string contents = "These are my contents" ; |
111 | { |
112 | auto sub = fml::CreateDirectory(dir.fd(), {"a" , "b" , "c" }, |
113 | fml::FilePermission::kReadWrite); |
114 | ASSERT_TRUE(sub.is_valid()); |
115 | auto file = fml::OpenFile(sub, "my_contents" , true, |
116 | fml::FilePermission::kReadWrite); |
117 | ASSERT_TRUE(file.is_valid()); |
118 | ASSERT_TRUE(WriteStringToFile(file, contents)); |
119 | } |
120 | |
121 | const char* file_path = "a/b/c/my_contents" ; |
122 | |
123 | { |
124 | auto contents_file = |
125 | fml::OpenFile(dir.fd(), file_path, false, fml::FilePermission::kRead); |
126 | ASSERT_EQ(ReadStringFromFile(contents_file), contents); |
127 | } |
128 | |
129 | // Cleanup. |
130 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), file_path)); |
131 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c" )); |
132 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b" )); |
133 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a" )); |
134 | } |
135 | |
136 | TEST(FileTest, VisitFilesCanBeCalledTwice) { |
137 | fml::ScopedTemporaryDirectory dir; |
138 | |
139 | { |
140 | auto file = fml::OpenFile(dir.fd(), "my_contents" , true, |
141 | fml::FilePermission::kReadWrite); |
142 | ASSERT_TRUE(file.is_valid()); |
143 | } |
144 | |
145 | int count; |
146 | fml::FileVisitor count_visitor = [&count](const fml::UniqueFD& directory, |
147 | const std::string& filename) { |
148 | count += 1; |
149 | return true; |
150 | }; |
151 | count = 0; |
152 | fml::VisitFiles(dir.fd(), count_visitor); |
153 | ASSERT_EQ(count, 1); |
154 | |
155 | // Without `rewinddir` in `VisitFiles`, the following check would fail. |
156 | count = 0; |
157 | fml::VisitFiles(dir.fd(), count_visitor); |
158 | ASSERT_EQ(count, 1); |
159 | |
160 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "my_contents" )); |
161 | } |
162 | |
163 | TEST(FileTest, CanListFilesRecursively) { |
164 | fml::ScopedTemporaryDirectory dir; |
165 | |
166 | { |
167 | auto c = fml::CreateDirectory(dir.fd(), {"a" , "b" , "c" }, |
168 | fml::FilePermission::kReadWrite); |
169 | ASSERT_TRUE(c.is_valid()); |
170 | auto file1 = |
171 | fml::OpenFile(c, "file1" , true, fml::FilePermission::kReadWrite); |
172 | auto file2 = |
173 | fml::OpenFile(c, "file2" , true, fml::FilePermission::kReadWrite); |
174 | auto d = fml::CreateDirectory(c, {"d" }, fml::FilePermission::kReadWrite); |
175 | ASSERT_TRUE(d.is_valid()); |
176 | auto file3 = |
177 | fml::OpenFile(d, "file3" , true, fml::FilePermission::kReadWrite); |
178 | ASSERT_TRUE(file1.is_valid()); |
179 | ASSERT_TRUE(file2.is_valid()); |
180 | ASSERT_TRUE(file3.is_valid()); |
181 | } |
182 | |
183 | std::set<std::string> names; |
184 | fml::FileVisitor visitor = [&names](const fml::UniqueFD& directory, |
185 | const std::string& filename) { |
186 | names.insert(filename); |
187 | return true; |
188 | }; |
189 | |
190 | fml::VisitFilesRecursively(dir.fd(), visitor); |
191 | ASSERT_EQ(names, std::set<std::string>( |
192 | {"a" , "b" , "c" , "d" , "file1" , "file2" , "file3" })); |
193 | |
194 | // Cleanup. |
195 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "a/b/c/d/file3" )); |
196 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "a/b/c/file1" )); |
197 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "a/b/c/file2" )); |
198 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c/d" )); |
199 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c" )); |
200 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b" )); |
201 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a" )); |
202 | } |
203 | |
204 | TEST(FileTest, CanStopVisitEarly) { |
205 | fml::ScopedTemporaryDirectory dir; |
206 | |
207 | { |
208 | auto d = fml::CreateDirectory(dir.fd(), {"a" , "b" , "c" , "d" }, |
209 | fml::FilePermission::kReadWrite); |
210 | ASSERT_TRUE(d.is_valid()); |
211 | } |
212 | |
213 | std::set<std::string> names; |
214 | fml::FileVisitor visitor = [&names](const fml::UniqueFD& directory, |
215 | const std::string& filename) { |
216 | names.insert(filename); |
217 | return filename == "c" ? false : true; // stop if c is found |
218 | }; |
219 | |
220 | // Check the d is not visited as we stop at c. |
221 | ASSERT_FALSE(fml::VisitFilesRecursively(dir.fd(), visitor)); |
222 | ASSERT_EQ(names, std::set<std::string>({"a" , "b" , "c" })); |
223 | |
224 | // Cleanup. |
225 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c/d" )); |
226 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c" )); |
227 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b" )); |
228 | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a" )); |
229 | } |
230 | |
231 | #if OS_WIN |
232 | #define AtomicWriteTest DISABLED_AtomicWriteTest |
233 | #else |
234 | #define AtomicWriteTest AtomicWriteTest |
235 | #endif |
236 | TEST(FileTest, AtomicWriteTest) { |
237 | fml::ScopedTemporaryDirectory dir; |
238 | |
239 | const std::string contents = "These are my contents." ; |
240 | |
241 | auto data = std::make_unique<fml::DataMapping>( |
242 | std::vector<uint8_t>{contents.begin(), contents.end()}); |
243 | |
244 | // Write. |
245 | ASSERT_TRUE(fml::WriteAtomically(dir.fd(), "precious_data" , *data)); |
246 | |
247 | // Read and verify. |
248 | ASSERT_EQ(contents, |
249 | ReadStringFromFile(fml::OpenFile(dir.fd(), "precious_data" , false, |
250 | fml::FilePermission::kRead))); |
251 | |
252 | // Cleanup. |
253 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "precious_data" )); |
254 | } |
255 | |
256 | TEST(FileTest, EmptyMappingTest) { |
257 | fml::ScopedTemporaryDirectory dir; |
258 | |
259 | { |
260 | auto file = fml::OpenFile(dir.fd(), "my_contents" , true, |
261 | fml::FilePermission::kReadWrite); |
262 | |
263 | fml::FileMapping mapping(file); |
264 | ASSERT_TRUE(mapping.IsValid()); |
265 | ASSERT_EQ(mapping.GetSize(), 0ul); |
266 | ASSERT_EQ(mapping.GetMapping(), nullptr); |
267 | } |
268 | |
269 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "my_contents" )); |
270 | } |
271 | |
272 | TEST(FileTest, FileTestsWork) { |
273 | fml::ScopedTemporaryDirectory dir; |
274 | ASSERT_TRUE(dir.fd().is_valid()); |
275 | const char* filename = "some.txt" ; |
276 | auto fd = |
277 | fml::OpenFile(dir.fd(), filename, true, fml::FilePermission::kWrite); |
278 | ASSERT_TRUE(fd.is_valid()); |
279 | fd.reset(); |
280 | ASSERT_TRUE(fml::FileExists(dir.fd(), filename)); |
281 | ASSERT_TRUE( |
282 | fml::IsFile(fml::paths::JoinPaths({dir.path(), filename}).c_str())); |
283 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), filename)); |
284 | } |
285 | |
286 | TEST(FileTest, FileTestsSupportsUnicode) { |
287 | fml::ScopedTemporaryDirectory dir; |
288 | ASSERT_TRUE(dir.fd().is_valid()); |
289 | const char* filename = u8"äëïöüテスト☃" ; |
290 | auto fd = |
291 | fml::OpenFile(dir.fd(), filename, true, fml::FilePermission::kWrite); |
292 | ASSERT_TRUE(fd.is_valid()); |
293 | fd.reset(); |
294 | ASSERT_TRUE(fml::FileExists(dir.fd(), filename)); |
295 | ASSERT_TRUE( |
296 | fml::IsFile(fml::paths::JoinPaths({dir.path(), filename}).c_str())); |
297 | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), filename)); |
298 | } |
299 | |