| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "common/util/file_util.h" | |
| #include <algorithm> | |
| #include <filesystem> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/string_view.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| using testing::HasSubstr; | |
| #undef EXPECT_OK | |
| #define EXPECT_OK(value) \ | |
| { \ | |
| auto s = (value); \ | |
| EXPECT_TRUE(s.ok()) << s; \ | |
| } | |
| #undef ASSERT_OK | |
| #define ASSERT_OK(value) \ | |
| { \ | |
| auto s = (value); \ | |
| ASSERT_TRUE(s.ok()) << s; \ | |
| } | |
| namespace verible { | |
| namespace util { | |
| namespace { | |
| using file::testing::ScopedTestFile; | |
| TEST(FileUtil, Basename) { | |
| EXPECT_EQ(file::Basename("/foo/bar/baz"), "baz"); | |
| EXPECT_EQ(file::Basename("foo/bar/baz"), "baz"); | |
| EXPECT_EQ(file::Basename("/foo/bar/"), ""); | |
| EXPECT_EQ(file::Basename("/"), ""); | |
| EXPECT_EQ(file::Basename(""), ""); | |
| } | |
| TEST(FileUtil, Dirname) { | |
| EXPECT_EQ(file::Dirname("/foo/bar/baz"), "/foo/bar"); | |
| EXPECT_EQ(file::Dirname("/foo/bar/baz.txt"), "/foo/bar"); | |
| EXPECT_EQ(file::Dirname("foo/bar/baz"), "foo/bar"); | |
| EXPECT_EQ(file::Dirname("/foo/bar/"), "/foo/bar"); | |
| EXPECT_EQ(file::Dirname("/"), ""); | |
| EXPECT_EQ(file::Dirname(""), ""); | |
| EXPECT_EQ(file::Dirname("."), "."); | |
| EXPECT_EQ(file::Dirname("./"), "."); | |
| } | |
| TEST(FileUtil, Stem) { | |
| EXPECT_EQ(file::Stem(""), ""); | |
| EXPECT_EQ(file::Stem("/foo/bar.baz"), "/foo/bar"); | |
| EXPECT_EQ(file::Stem("foo/bar.baz"), "foo/bar"); | |
| EXPECT_EQ(file::Stem("/foo/bar."), "/foo/bar"); | |
| EXPECT_EQ(file::Stem("/foo/bar"), "/foo/bar"); | |
| } | |
| // Returns the forward-slashed path in platform-specific notation. | |
| static std::string PlatformPath(const std::string& path) { | |
| return std::filesystem::path(path).lexically_normal().string(); | |
| } | |
| TEST(FileUtil, GetContentAsMemBlock) { | |
| auto result = file::GetContentAsMemBlock("non-existing-file"); | |
| EXPECT_FALSE(result.status().ok()); | |
| const std::string test_file = file::JoinPath(testing::TempDir(), "blockfile"); | |
| const absl::string_view test_content = "Some file content"; | |
| EXPECT_OK(file::SetContents(test_file, test_content)); | |
| result = file::GetContentAsMemBlock(test_file); | |
| EXPECT_OK(result.status()); | |
| auto& block = *result; | |
| EXPECT_EQ(block->AsStringView(), test_content); | |
| } | |
| TEST(FileUtil, JoinPath) { | |
| EXPECT_EQ(file::JoinPath("foo", ""), PlatformPath("foo/")); | |
| EXPECT_EQ(file::JoinPath("", "bar"), "bar"); | |
| EXPECT_EQ(file::JoinPath("foo", "bar"), PlatformPath("foo/bar")); | |
| // Assemble an absolute path | |
| EXPECT_EQ(file::JoinPath("", "bar"), "bar"); | |
| EXPECT_EQ(file::JoinPath("/", "bar"), PlatformPath("/bar")); | |
| // Lightly canonicalize multiple consecutive slashes | |
| EXPECT_EQ(file::JoinPath("foo/", "bar"), PlatformPath("foo/bar")); | |
| EXPECT_EQ(file::JoinPath("foo///", "bar"), PlatformPath("foo/bar")); | |
| EXPECT_EQ(file::JoinPath("foo/", "/bar"), PlatformPath("foo/bar")); | |
| EXPECT_EQ(file::JoinPath("foo/", "///bar"), PlatformPath("foo/bar")); | |
| #ifdef _WIN32 | |
| EXPECT_EQ(file::JoinPath("C:\\foo", "bar"), "C:\\foo\\bar"); | |
| #endif | |
| } | |
| TEST(FileUtil, CreateDir) { | |
| const std::string test_dir = file::JoinPath(testing::TempDir(), "test_dir"); | |
| const std::string test_file = file::JoinPath(test_dir, "foo"); | |
| const absl::string_view test_content = "directory create test"; | |
| EXPECT_OK(file::CreateDir(test_dir)); | |
| EXPECT_OK(file::CreateDir(test_dir)); // Creating twice should succeed | |
| EXPECT_OK(file::SetContents(test_file, test_content)); | |
| std::string read_back_content; | |
| EXPECT_OK(file::GetContents(test_file, &read_back_content)); | |
| EXPECT_EQ(test_content, read_back_content); | |
| } | |
| TEST(FileUtil, StatusErrorReporting) { | |
| std::string content; | |
| absl::Status status = file::GetContents("does-not-exist", &content); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kNotFound) << status; | |
| const std::string test_file = file::JoinPath(testing::TempDir(), "test-err"); | |
| unlink(test_file.c_str()); // Remove file if left from previous test. | |
| EXPECT_OK(file::SetContents(test_file, "foo")); | |
| EXPECT_OK(file::GetContents(test_file, &content)); | |
| EXPECT_EQ(content, "foo"); | |
| #ifndef _WIN32 | |
| // The following chmod() is not working on Win32. So let's not use | |
| // this test here. | |
| // TODO: Can we make permission-denied test that works on Windows ? | |
| // Issue #963 - if this test is run as root, the permission issue | |
| // will not manifest. Also if chmod() did not succeed. Skip in this case. | |
| if (geteuid() != 0 && chmod(test_file.c_str(), 0) == 0) { | |
| // Enforce a permission denied situation | |
| content.clear(); | |
| status = file::GetContents(test_file, &content); | |
| EXPECT_FALSE(status.ok()) << "Expected permission denied for " << test_file; | |
| EXPECT_EQ(status.code(), absl::StatusCode::kPermissionDenied) << status; | |
| EXPECT_TRUE(content.empty()) << "'" << content << "'"; | |
| } | |
| #endif | |
| } | |
| TEST(FileUtil, ScopedTestFile) { | |
| const absl::string_view test_content = "Hello World!"; | |
| ScopedTestFile test_file(testing::TempDir(), test_content); | |
| std::string read_back_content; | |
| EXPECT_OK(file::GetContents(test_file.filename(), &read_back_content)); | |
| EXPECT_EQ(test_content, read_back_content); | |
| } | |
| static ScopedTestFile TestFileGenerator(absl::string_view content) { | |
| return ScopedTestFile(testing::TempDir(), content); | |
| } | |
| static void TestFileConsumer(ScopedTestFile&& f) { | |
| ScopedTestFile temp(std::move(f)); | |
| } | |
| TEST(FileUtil, ScopedTestFileMove) { | |
| ScopedTestFile f(TestFileGenerator("barfoo")); | |
| std::string tmpname(f.filename()); | |
| EXPECT_TRUE(file::FileExists(tmpname).ok()); | |
| TestFileConsumer(std::move(f)); | |
| EXPECT_FALSE(file::FileExists(tmpname).ok()); | |
| } | |
| TEST(FileUtil, ScopedTestFileEmplace) { | |
| std::vector<std::string> names; | |
| { | |
| std::vector<ScopedTestFile> files; | |
| for (size_t i = 0; i < 10; ++i) { | |
| files.emplace_back(::testing::TempDir(), "zzz"); | |
| names.push_back(std::string(files.back().filename())); | |
| } | |
| for (const auto& name : names) { | |
| EXPECT_TRUE(file::FileExists(name).ok()); | |
| } | |
| } | |
| for (const auto& name : names) { | |
| EXPECT_FALSE(file::FileExists(name).ok()); | |
| } | |
| } | |
| TEST(FileUtil, FileExistsDirectoryErrorMessage) { | |
| absl::Status s; | |
| s = file::FileExists(testing::TempDir()); | |
| EXPECT_FALSE(s.ok()); | |
| EXPECT_THAT(s.message(), HasSubstr("is a directory")); | |
| } | |
| static bool CreateFsStructure(absl::string_view base_dir, | |
| const std::vector<absl::string_view>& tree) { | |
| for (absl::string_view path : tree) { | |
| const std::string full_path = file::JoinPath(base_dir, path); | |
| if (absl::EndsWith(path, "/")) { | |
| if (!file::CreateDir(full_path).ok()) return false; | |
| } else { | |
| if (!file::SetContents(full_path, "(content)").ok()) return false; | |
| } | |
| } | |
| return true; | |
| } | |
| TEST(FileUtil, UpwardFileSearchTest) { | |
| const std::string root_dir = file::JoinPath(testing::TempDir(), "up-search"); | |
| ASSERT_OK(file::CreateDir(root_dir)); | |
| ASSERT_TRUE(CreateFsStructure(root_dir, { | |
| "toplevel-file", | |
| "foo/", | |
| "foo/foo-file", | |
| "foo/bar/", | |
| "foo/bar/baz/", | |
| "foo/bar/baz/baz-file", | |
| })); | |
| std::string result; | |
| // Same directory | |
| EXPECT_OK(file::UpwardFileSearch(file::JoinPath(root_dir, "foo"), "foo-file", | |
| &result)); | |
| // We don't compare the full path, as the UpwardFileSearch() makes it an | |
| // realpath which might not entirely match our root_dir prefix anymore; but | |
| // we do know that the suffix should be the same. | |
| EXPECT_TRUE(absl::EndsWith(result, PlatformPath("up-search/foo/foo-file"))); | |
| // Somewhere below | |
| EXPECT_OK(file::UpwardFileSearch(file::JoinPath(root_dir, "foo/bar/baz"), | |
| "foo-file", &result)); | |
| EXPECT_TRUE(absl::EndsWith(result, PlatformPath("up-search/foo/foo-file"))); | |
| // Find toplevel file | |
| EXPECT_OK(file::UpwardFileSearch(file::JoinPath(root_dir, "foo/bar/baz"), | |
| "toplevel-file", &result)); | |
| EXPECT_TRUE(absl::EndsWith(result, PlatformPath("up-search/toplevel-file"))); | |
| // Negative test. | |
| auto status = file::UpwardFileSearch(file::JoinPath(root_dir, "foo/bar/baz"), | |
| "unknownfile", &result); | |
| EXPECT_FALSE(status.ok()); | |
| } | |
| TEST(FileUtil, ReadEmptyDirectory) { | |
| const std::string test_dir = file::JoinPath(testing::TempDir(), "empty_dir"); | |
| ASSERT_TRUE(file::CreateDir(test_dir).ok()); | |
| auto dir_or = file::ListDir(test_dir); | |
| ASSERT_TRUE(dir_or.ok()); | |
| const auto& dir = *dir_or; | |
| EXPECT_EQ(dir.path, test_dir); | |
| EXPECT_TRUE(dir.directories.empty()); | |
| EXPECT_TRUE(dir.files.empty()); | |
| } | |
| TEST(FileUtil, ReadNonexistentDirectory) { | |
| const std::string test_dir = | |
| file::JoinPath(testing::TempDir(), "dir_not_there"); | |
| auto dir_or = file::ListDir(test_dir); | |
| EXPECT_FALSE(dir_or.ok()); | |
| EXPECT_EQ(dir_or.status().code(), absl::StatusCode::kNotFound); | |
| } | |
| TEST(FileUtil, ListNotADirectory) { | |
| ScopedTestFile tempfile(testing::TempDir(), "O HAI, WRLD"); | |
| auto dir_or = file::ListDir(tempfile.filename()); | |
| EXPECT_FALSE(dir_or.ok()); | |
| EXPECT_EQ(dir_or.status().code(), absl::StatusCode::kInvalidArgument); | |
| } | |
| TEST(FileUtil, ReadDirectory) { | |
| const std::string test_dir = file::JoinPath(testing::TempDir(), "mixed_dir"); | |
| const std::string test_subdir1 = file::JoinPath(test_dir, "subdir1"); | |
| const std::string test_subdir2 = file::JoinPath(test_dir, "subdir2"); | |
| std::vector<std::string> test_directories{test_subdir1, test_subdir2}; | |
| ASSERT_TRUE(file::CreateDir(test_dir).ok()); | |
| ASSERT_TRUE(file::CreateDir(test_subdir1).ok()); | |
| ASSERT_TRUE(file::CreateDir(test_subdir2).ok()); | |
| const absl::string_view test_content = "Hello World!"; | |
| file::testing::ScopedTestFile test_file1(test_dir, test_content); | |
| file::testing::ScopedTestFile test_file2(test_dir, test_content); | |
| file::testing::ScopedTestFile test_file3(test_dir, test_content); | |
| std::vector<std::string> test_files; | |
| test_files.emplace_back(test_file1.filename()); | |
| test_files.emplace_back(test_file2.filename()); | |
| test_files.emplace_back(test_file3.filename()); | |
| std::sort(test_files.begin(), test_files.end()); | |
| auto dir_or = file::ListDir(test_dir); | |
| ASSERT_TRUE(dir_or.ok()) << dir_or.status().message(); | |
| const auto& dir = *dir_or; | |
| EXPECT_EQ(dir.path, test_dir); | |
| EXPECT_EQ(dir.directories.size(), test_directories.size()); | |
| EXPECT_EQ(dir.directories, test_directories); | |
| EXPECT_EQ(dir.files.size(), test_files.size()); | |
| EXPECT_EQ(dir.files, test_files); | |
| } | |
| } // namespace | |
| } // namespace util | |
| } // namespace verible |