// 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 #include #include #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(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 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( {"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 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({"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( std::vector{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( std::vector{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)); }