|  | // Copyright 2013 The Flutter Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <cstring> | 
|  | #include <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #include "flutter/fml/build_config.h" | 
|  | #include "flutter/fml/file.h" | 
|  | #include "flutter/fml/mapping.h" | 
|  | #include "flutter/fml/paths.h" | 
|  | #include "flutter/fml/unique_fd.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | static bool WriteStringToFile(const fml::UniqueFD& fd, | 
|  | const std::string& contents) { | 
|  | if (!fml::TruncateFile(fd, contents.size())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kWrite}); | 
|  | if (mapping.GetSize() != contents.size()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (mapping.GetMutableMapping() == nullptr) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ::memmove(mapping.GetMutableMapping(), contents.data(), contents.size()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static std::string ReadStringFromFile(const fml::UniqueFD& fd) { | 
|  | fml::FileMapping mapping(fd); | 
|  |  | 
|  | if (mapping.GetMapping() == nullptr) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return {reinterpret_cast<const char*>(mapping.GetMapping()), | 
|  | mapping.GetSize()}; | 
|  | } | 
|  |  | 
|  | TEST(FileTest, CreateTemporaryAndUnlink) { | 
|  | auto dir_name = fml::CreateTemporaryDirectory(); | 
|  | ASSERT_NE(dir_name, ""); | 
|  | auto dir = | 
|  | fml::OpenDirectory(dir_name.c_str(), false, fml::FilePermission::kRead); | 
|  | ASSERT_TRUE(dir.is_valid()); | 
|  | dir.reset(); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir_name.c_str())); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, ScopedTempDirIsValid) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  | ASSERT_TRUE(dir.fd().is_valid()); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, CanOpenFileForWriting) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  | ASSERT_TRUE(dir.fd().is_valid()); | 
|  |  | 
|  | auto fd = | 
|  | fml::OpenFile(dir.fd(), "some.txt", true, fml::FilePermission::kWrite); | 
|  | ASSERT_TRUE(fd.is_valid()); | 
|  | fd.reset(); | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "some.txt")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, CanTruncateAndWrite) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  | ASSERT_TRUE(dir.fd().is_valid()); | 
|  |  | 
|  | std::string contents = "some contents here"; | 
|  |  | 
|  | // On the first iteration, this tests writing and then reading a file that | 
|  | // didn't exist yet. On the second iteration it tests truncating, writing, | 
|  | // and reading a file that already existed. | 
|  | for (int i = 0; i < 2; i++) { | 
|  | { | 
|  | auto fd = fml::OpenFile(dir.fd(), "some.txt", true, | 
|  | fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(fd.is_valid()); | 
|  |  | 
|  | ASSERT_TRUE(fml::TruncateFile(fd, contents.size())); | 
|  |  | 
|  | fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kWrite}); | 
|  | ASSERT_EQ(mapping.GetSize(), contents.size()); | 
|  | ASSERT_NE(mapping.GetMutableMapping(), nullptr); | 
|  |  | 
|  | ::memcpy(mapping.GetMutableMapping(), contents.data(), contents.size()); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto fd = fml::OpenFile(dir.fd(), "some.txt", false, | 
|  | fml::FilePermission::kRead); | 
|  | ASSERT_TRUE(fd.is_valid()); | 
|  |  | 
|  | fml::FileMapping mapping(fd); | 
|  | ASSERT_EQ(mapping.GetSize(), contents.size()); | 
|  |  | 
|  | ASSERT_EQ( | 
|  | 0, ::memcmp(mapping.GetMapping(), contents.data(), contents.size())); | 
|  | } | 
|  | } | 
|  |  | 
|  | fml::UnlinkFile(dir.fd(), "some.txt"); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, CreateDirectoryStructure) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | std::string contents = "These are my contents"; | 
|  | { | 
|  | auto sub = fml::CreateDirectory(dir.fd(), {"a", "b", "c"}, | 
|  | fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(sub.is_valid()); | 
|  | auto file = fml::OpenFile(sub, "my_contents", true, | 
|  | fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(file.is_valid()); | 
|  | ASSERT_TRUE(WriteStringToFile(file, contents)); | 
|  | } | 
|  |  | 
|  | const char* file_path = "a/b/c/my_contents"; | 
|  |  | 
|  | { | 
|  | auto contents_file = | 
|  | fml::OpenFile(dir.fd(), file_path, false, fml::FilePermission::kRead); | 
|  | ASSERT_EQ(ReadStringFromFile(contents_file), contents); | 
|  | } | 
|  |  | 
|  | // Cleanup. | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), file_path)); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, VisitFilesCanBeCalledTwice) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | { | 
|  | auto file = fml::OpenFile(dir.fd(), "my_contents", true, | 
|  | fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(file.is_valid()); | 
|  | } | 
|  |  | 
|  | int count; | 
|  | fml::FileVisitor count_visitor = [&count](const fml::UniqueFD& directory, | 
|  | const std::string& filename) { | 
|  | count += 1; | 
|  | return true; | 
|  | }; | 
|  | count = 0; | 
|  | fml::VisitFiles(dir.fd(), count_visitor); | 
|  | ASSERT_EQ(count, 1); | 
|  |  | 
|  | // Without `rewinddir` in `VisitFiles`, the following check would fail. | 
|  | count = 0; | 
|  | fml::VisitFiles(dir.fd(), count_visitor); | 
|  | ASSERT_EQ(count, 1); | 
|  |  | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "my_contents")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, CanListFilesRecursively) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | { | 
|  | auto c = fml::CreateDirectory(dir.fd(), {"a", "b", "c"}, | 
|  | fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(c.is_valid()); | 
|  | auto file1 = | 
|  | fml::OpenFile(c, "file1", true, fml::FilePermission::kReadWrite); | 
|  | auto file2 = | 
|  | fml::OpenFile(c, "file2", true, fml::FilePermission::kReadWrite); | 
|  | auto d = fml::CreateDirectory(c, {"d"}, fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(d.is_valid()); | 
|  | auto file3 = | 
|  | fml::OpenFile(d, "file3", true, fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(file1.is_valid()); | 
|  | ASSERT_TRUE(file2.is_valid()); | 
|  | ASSERT_TRUE(file3.is_valid()); | 
|  | } | 
|  |  | 
|  | std::set<std::string> names; | 
|  | fml::FileVisitor visitor = [&names](const fml::UniqueFD& directory, | 
|  | const std::string& filename) { | 
|  | names.insert(filename); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | fml::VisitFilesRecursively(dir.fd(), visitor); | 
|  | ASSERT_EQ(names, std::set<std::string>( | 
|  | {"a", "b", "c", "d", "file1", "file2", "file3"})); | 
|  |  | 
|  | // Cleanup. | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "a/b/c/d/file3")); | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "a/b/c/file1")); | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "a/b/c/file2")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c/d")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, CanStopVisitEarly) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | { | 
|  | auto d = fml::CreateDirectory(dir.fd(), {"a", "b", "c", "d"}, | 
|  | fml::FilePermission::kReadWrite); | 
|  | ASSERT_TRUE(d.is_valid()); | 
|  | } | 
|  |  | 
|  | std::set<std::string> names; | 
|  | fml::FileVisitor visitor = [&names](const fml::UniqueFD& directory, | 
|  | const std::string& filename) { | 
|  | names.insert(filename); | 
|  | return filename == "c" ? false : true;  // stop if c is found | 
|  | }; | 
|  |  | 
|  | // Check the d is not visited as we stop at c. | 
|  | ASSERT_FALSE(fml::VisitFilesRecursively(dir.fd(), visitor)); | 
|  | ASSERT_EQ(names, std::set<std::string>({"a", "b", "c"})); | 
|  |  | 
|  | // Cleanup. | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c/d")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b/c")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a/b")); | 
|  | ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), "a")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, AtomicWriteTest) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | const std::string contents = "These are my contents."; | 
|  |  | 
|  | auto data = std::make_unique<fml::DataMapping>( | 
|  | std::vector<uint8_t>{contents.begin(), contents.end()}); | 
|  |  | 
|  | // Write. | 
|  | ASSERT_TRUE(fml::WriteAtomically(dir.fd(), "precious_data", *data)); | 
|  |  | 
|  | // Read and verify. | 
|  | ASSERT_EQ(contents, | 
|  | ReadStringFromFile(fml::OpenFile(dir.fd(), "precious_data", false, | 
|  | fml::FilePermission::kRead))); | 
|  |  | 
|  | // Cleanup. | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "precious_data")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, IgnoreBaseDirWhenPathIsAbsolute) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | // Make an absolute path. | 
|  | std::string filename = "filename.txt"; | 
|  | std::string full_path = | 
|  | fml::paths::AbsolutePath(fml::paths::JoinPaths({dir.path(), filename})); | 
|  |  | 
|  | const std::string contents = "These are my contents."; | 
|  | auto data = std::make_unique<fml::DataMapping>( | 
|  | std::vector<uint8_t>{contents.begin(), contents.end()}); | 
|  |  | 
|  | // Write. | 
|  | ASSERT_TRUE(fml::WriteAtomically(dir.fd(), full_path.c_str(), *data)); | 
|  |  | 
|  | // Test existence. | 
|  | ASSERT_TRUE(fml::FileExists(dir.fd(), full_path.c_str())); | 
|  |  | 
|  | // Read and verify. | 
|  | ASSERT_EQ(contents, | 
|  | ReadStringFromFile(fml::OpenFile(dir.fd(), full_path.c_str(), false, | 
|  | fml::FilePermission::kRead))); | 
|  |  | 
|  | // Cleanup. | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), full_path.c_str())); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, EmptyMappingTest) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | { | 
|  | auto file = fml::OpenFile(dir.fd(), "my_contents", true, | 
|  | fml::FilePermission::kReadWrite); | 
|  |  | 
|  | fml::FileMapping mapping(file); | 
|  | ASSERT_TRUE(mapping.IsValid()); | 
|  | ASSERT_EQ(mapping.GetSize(), 0ul); | 
|  | ASSERT_EQ(mapping.GetMapping(), nullptr); | 
|  | } | 
|  |  | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "my_contents")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, MappingDontNeedSafeTest) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  |  | 
|  | { | 
|  | auto file = fml::OpenFile(dir.fd(), "my_contents", true, | 
|  | fml::FilePermission::kReadWrite); | 
|  | WriteStringToFile(file, "some content"); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto file = fml::OpenFile(dir.fd(), "my_contents", false, | 
|  | fml::FilePermission::kRead); | 
|  | fml::FileMapping mapping(file); | 
|  | ASSERT_TRUE(mapping.IsValid()); | 
|  | ASSERT_EQ(mapping.GetMutableMapping(), nullptr); | 
|  | ASSERT_TRUE(mapping.IsDontNeedSafe()); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto file = fml::OpenFile(dir.fd(), "my_contents", false, | 
|  | fml::FilePermission::kReadWrite); | 
|  | fml::FileMapping mapping(file, {fml::FileMapping::Protection::kRead, | 
|  | fml::FileMapping::Protection::kWrite}); | 
|  | ASSERT_TRUE(mapping.IsValid()); | 
|  | ASSERT_NE(mapping.GetMutableMapping(), nullptr); | 
|  | ASSERT_FALSE(mapping.IsDontNeedSafe()); | 
|  | } | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), "my_contents")); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, FileTestsWork) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  | ASSERT_TRUE(dir.fd().is_valid()); | 
|  | const char* filename = "some.txt"; | 
|  | auto fd = | 
|  | fml::OpenFile(dir.fd(), filename, true, fml::FilePermission::kWrite); | 
|  | ASSERT_TRUE(fd.is_valid()); | 
|  | fd.reset(); | 
|  | ASSERT_TRUE(fml::FileExists(dir.fd(), filename)); | 
|  | ASSERT_TRUE( | 
|  | fml::IsFile(fml::paths::JoinPaths({dir.path(), filename}).c_str())); | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), filename)); | 
|  | } | 
|  |  | 
|  | TEST(FileTest, FileTestsSupportsUnicode) { | 
|  | fml::ScopedTemporaryDirectory dir; | 
|  | ASSERT_TRUE(dir.fd().is_valid()); | 
|  | const char* filename = u8"äëïöüテスト☃"; | 
|  | auto fd = | 
|  | fml::OpenFile(dir.fd(), filename, true, fml::FilePermission::kWrite); | 
|  | ASSERT_TRUE(fd.is_valid()); | 
|  | fd.reset(); | 
|  | ASSERT_TRUE(fml::FileExists(dir.fd(), filename)); | 
|  | ASSERT_TRUE( | 
|  | fml::IsFile(fml::paths::JoinPaths({dir.path(), filename}).c_str())); | 
|  | ASSERT_TRUE(fml::UnlinkFile(dir.fd(), filename)); | 
|  | } |