| // 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 |