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
16static 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
35static 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
46TEST(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
56TEST(FileTest, ScopedTempDirIsValid) {
57 fml::ScopedTemporaryDirectory dir;
58 ASSERT_TRUE(dir.fd().is_valid());
59}
60
61TEST(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
72TEST(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
107TEST(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
136TEST(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
163TEST(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
204TEST(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
236TEST(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
256TEST(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
272TEST(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
286TEST(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