blob: 1192ef4499d972c9584403f5cf110f31bbf6bf75 [file] [log] [blame]
// 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