| // 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)); | |
| } |