| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
- // Exceptions. See /LICENSE for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- #include "common/filesystem.h"
- #include <gmock/gmock.h>
- #include <gtest/gtest.h>
- #include <concepts>
- #include <string>
- #include <utility>
- #include "common/error_test_helpers.h"
- namespace Carbon::Filesystem {
- namespace {
- using ::testing::_;
- using ::testing::Eq;
- using ::testing::HasSubstr;
- using Testing::IsError;
- using Testing::IsSuccess;
- class FilesystemTest : public ::testing::Test {
- public:
- explicit FilesystemTest() {
- auto result = MakeTmpDir();
- CARBON_CHECK(result.ok(), "{0}", result.error());
- dir_ = std::move(*result);
- }
- ~FilesystemTest() override {
- auto result = std::move(dir_).Remove();
- CARBON_CHECK(result.ok(), "{0}", result.error());
- }
- auto path() const -> const std::filesystem::path& { return dir_.abs_path(); }
- // The test's temp directory, deleted on destruction.
- RemovingDir dir_;
- };
- TEST_F(FilesystemTest, CreateOpenCloseAndUnlink) {
- auto unlink_result = dir_.Unlink("test");
- ASSERT_FALSE(unlink_result.ok());
- EXPECT_TRUE(unlink_result.error().no_entity());
- #if defined(_GNU_SOURCE) && \
- (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32))
- EXPECT_THAT(unlink_result, IsError(HasSubstr("ENOENT")));
- #endif
- EXPECT_THAT(unlink_result, IsError(HasSubstr("No such file")));
- auto f = dir_.OpenWriteOnly("test", CreationOptions::CreateNew);
- ASSERT_THAT(f, IsSuccess(_));
- auto result = (*std::move(f)).Close();
- EXPECT_THAT(result, IsSuccess(_));
- f = dir_.OpenWriteOnly("test", CreationOptions::CreateNew);
- ASSERT_FALSE(f.ok());
- EXPECT_TRUE(f.error().already_exists());
- #if defined(_GNU_SOURCE) && \
- (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32))
- EXPECT_THAT(f, IsError(HasSubstr("EEXIST")));
- #endif
- EXPECT_THAT(f, IsError(HasSubstr("File exists")));
- f = dir_.OpenWriteOnly("test");
- ASSERT_THAT(f, IsSuccess(_));
- result = std::move(*f).Close();
- EXPECT_THAT(result, IsSuccess(_));
- f = dir_.OpenWriteOnly("test");
- ASSERT_THAT(f, IsSuccess(_));
- result = std::move(*f).Close();
- EXPECT_THAT(result, IsSuccess(_));
- unlink_result = dir_.Unlink("test");
- EXPECT_THAT(unlink_result, IsSuccess(_));
- f = dir_.OpenWriteOnly("test");
- EXPECT_FALSE(f.ok());
- EXPECT_TRUE(f.error().no_entity());
- #if defined(_GNU_SOURCE) && \
- (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32))
- EXPECT_THAT(f, IsError(HasSubstr("ENOENT")));
- #endif
- EXPECT_THAT(f, IsError(HasSubstr("No such file")));
- f = dir_.OpenWriteOnly("test", CreationOptions::OpenAlways);
- ASSERT_THAT(f, IsSuccess(_));
- result = std::move(*f).Close();
- EXPECT_THAT(result, IsSuccess(_));
- unlink_result = dir_.Unlink("test");
- EXPECT_THAT(unlink_result, IsSuccess(_));
- }
- TEST_F(FilesystemTest, BasicWriteAndRead) {
- std::string content_str = "0123456789";
- {
- auto f = dir_.OpenWriteOnly("test", CreationOptions::CreateNew);
- ASSERT_THAT(f, IsSuccess(_));
- auto write_result = f->WriteFromString(content_str);
- EXPECT_THAT(write_result, IsSuccess(_));
- (*std::move(f)).Close().Check();
- }
- {
- auto f = dir_.OpenReadOnly("test");
- ASSERT_THAT(f, IsSuccess(_));
- auto read_result = f->ReadToString();
- EXPECT_THAT(read_result, IsSuccess(Eq(content_str)));
- }
- auto unlink_result = dir_.Unlink("test");
- EXPECT_THAT(unlink_result, IsSuccess(_));
- }
- TEST_F(FilesystemTest, CreateAndRemoveDirecotries) {
- auto d1 = Cwd().CreateDirectories(path() / "a" / "b" / "c" / "test1");
- ASSERT_THAT(d1, IsSuccess(_));
- auto d2 = Cwd().CreateDirectories(path() / "a" / "b" / "c" / "test2");
- ASSERT_THAT(d2, IsSuccess(_));
- auto d3 = Cwd().CreateDirectories(path() / "a" / "b" / "c" / "test3");
- ASSERT_THAT(d3, IsSuccess(_));
- // Get a directory object to use, this shouldn't cover much new.
- auto d4 = Cwd().CreateDirectories(path());
- EXPECT_THAT(d4, IsSuccess(_));
- // Single, present, relative component.
- auto d5 = d4->CreateDirectories("a");
- EXPECT_THAT(d5, IsSuccess(_));
- // Multiple, present, but relative components.
- auto d6 = d5->CreateDirectories(std::filesystem::path("b") / "c");
- EXPECT_THAT(d6, IsSuccess(_));
- // Single new component.
- auto d7 = d6->CreateDirectories("test4");
- ASSERT_THAT(d7, IsSuccess(_));
- // Two new relative components.
- auto d8 = d6->CreateDirectories(std::filesystem::path("test5") / "d");
- EXPECT_THAT(d8, IsSuccess(_));
- // Mixed relative components.
- auto d9 = d5->CreateDirectories(std::filesystem::path("b") / "test6");
- EXPECT_THAT(d9, IsSuccess(_));
- {
- auto f1 = d1->OpenWriteOnly("file1", CreateNew);
- ASSERT_THAT(f1, IsSuccess(_));
- auto f2 = d2->OpenWriteOnly("file2", CreateNew);
- ASSERT_THAT(f2, IsSuccess(_));
- auto f3 = d3->OpenWriteOnly("file3", CreateNew);
- ASSERT_THAT(f3, IsSuccess(_));
- auto f4 = d7->OpenWriteOnly("file4", CreateNew);
- ASSERT_THAT(f4, IsSuccess(_));
- (*std::move(f1)).Close().Check();
- (*std::move(f2)).Close().Check();
- (*std::move(f3)).Close().Check();
- (*std::move(f4)).Close().Check();
- }
- auto rm_result = Cwd().Rmtree(path() / "a");
- ASSERT_THAT(rm_result, IsSuccess(_));
- }
- TEST_F(FilesystemTest, StatAndAccess) {
- auto access_result = dir_.Access("test");
- ASSERT_FALSE(access_result.ok());
- EXPECT_TRUE(access_result.error().no_entity());
- // Make sure the flags and bit-or-ing them works in the boring case.
- access_result =
- dir_.Access("test", AccessCheckFlags::Read | AccessCheckFlags::Write |
- AccessCheckFlags::Execute);
- ASSERT_FALSE(access_result.ok());
- EXPECT_TRUE(access_result.error().no_entity());
- auto stat_result = dir_.Stat("test");
- ASSERT_FALSE(access_result.ok());
- EXPECT_TRUE(access_result.error().no_entity());
- // Create a file for testing, using very unusual and minimal permissions to
- // help us test. Hopefully this isn't modified on the usual `umask` tests run
- // under.
- std::string content_str = "0123456789";
- ModeType permissions = 0450;
- auto f = dir_.OpenWriteOnly("test", CreationOptions::CreateNew, permissions);
- ASSERT_THAT(f, IsSuccess(_));
- auto write_result = f->WriteFromString(content_str);
- EXPECT_THAT(write_result, IsSuccess(_));
- access_result = dir_.Access("test");
- EXPECT_THAT(access_result, IsSuccess(_));
- access_result = dir_.Access("test", AccessCheckFlags::Read);
- EXPECT_THAT(access_result, IsSuccess(_));
- // Neither write nor execute permission should be present though.
- access_result = dir_.Access("test", AccessCheckFlags::Write);
- ASSERT_FALSE(access_result.ok());
- EXPECT_TRUE(access_result.error().access_denied());
- access_result =
- dir_.Access("test", AccessCheckFlags::Read | AccessCheckFlags::Write |
- AccessCheckFlags::Execute);
- ASSERT_FALSE(access_result.ok());
- EXPECT_TRUE(access_result.error().access_denied());
- stat_result = dir_.Stat("test");
- ASSERT_THAT(stat_result, IsSuccess(_));
- EXPECT_TRUE(stat_result->is_file());
- EXPECT_FALSE(stat_result->is_dir());
- EXPECT_FALSE(stat_result->is_symlink());
- EXPECT_THAT(stat_result->size(), Eq(content_str.size()));
- EXPECT_THAT(stat_result->permissions(), Eq(permissions));
- // Directory instead of file.
- access_result =
- dir_.Access(".", AccessCheckFlags::Read | AccessCheckFlags::Write |
- AccessCheckFlags::Execute);
- EXPECT_THAT(access_result, IsSuccess(_));
- stat_result = dir_.Stat(".");
- ASSERT_THAT(stat_result, IsSuccess(_));
- EXPECT_FALSE(stat_result->is_file());
- EXPECT_TRUE(stat_result->is_dir());
- EXPECT_FALSE(stat_result->is_symlink());
- // Can remove file but still stat through the file.
- auto unlink_result = dir_.Unlink("test");
- ASSERT_THAT(unlink_result, IsSuccess(_));
- auto file_stat_result = f->Stat();
- ASSERT_THAT(file_stat_result, IsSuccess(_));
- EXPECT_TRUE(file_stat_result->is_file());
- EXPECT_FALSE(file_stat_result->is_dir());
- EXPECT_FALSE(file_stat_result->is_symlink());
- EXPECT_THAT(file_stat_result->size(), Eq(content_str.size()));
- EXPECT_THAT(file_stat_result->permissions(), Eq(permissions));
- (*std::move(f)).Close().Check();
- }
- TEST_F(FilesystemTest, Symlinks) {
- auto readlink_result = dir_.Readlink("test");
- ASSERT_FALSE(readlink_result.ok());
- EXPECT_TRUE(readlink_result.error().no_entity());
- auto lstat_result = dir_.Lstat("test");
- ASSERT_FALSE(lstat_result.ok());
- EXPECT_TRUE(lstat_result.error().no_entity());
- auto symlink_result = dir_.Symlink("test", "abc");
- EXPECT_THAT(symlink_result, IsSuccess(_));
- readlink_result = dir_.Readlink("test");
- EXPECT_THAT(readlink_result, IsSuccess(Eq("abc")));
- symlink_result = dir_.Symlink("test", "def");
- ASSERT_FALSE(symlink_result.ok());
- EXPECT_TRUE(symlink_result.error().already_exists());
- lstat_result = dir_.Lstat("test");
- ASSERT_THAT(lstat_result, IsSuccess(_));
- EXPECT_FALSE(lstat_result->is_file());
- EXPECT_FALSE(lstat_result->is_dir());
- EXPECT_TRUE(lstat_result->is_symlink());
- EXPECT_THAT(lstat_result->size(), Eq(strlen("abc")));
- auto unlink_result = dir_.Unlink("test");
- EXPECT_THAT(unlink_result, IsSuccess(_));
- readlink_result = dir_.Readlink("test");
- ASSERT_FALSE(readlink_result.ok());
- EXPECT_TRUE(readlink_result.error().no_entity());
- // Try a symlink with null bytes for fun. This demonstrates that the symlink
- // syscall only uses the leading C-string.
- symlink_result = dir_.Symlink("test", std::string("a\0b\0c", 5));
- EXPECT_THAT(symlink_result, IsSuccess(_));
- readlink_result = dir_.Readlink("test");
- EXPECT_THAT(readlink_result, IsSuccess(Eq("a")));
- }
- TEST_F(FilesystemTest, Chdir) {
- auto current_result = Cwd().OpenDir(".");
- ASSERT_THAT(current_result, IsSuccess(_));
- auto symlink_result = dir_.Symlink("test", "abc");
- EXPECT_THAT(symlink_result, IsSuccess(_));
- auto chdir_result = dir_.Chdir();
- EXPECT_THAT(chdir_result, IsSuccess(_));
- auto readlink_result = Cwd().Readlink("test");
- EXPECT_THAT(readlink_result, IsSuccess(Eq("abc")));
- auto chdir_path_result = dir_.Chdir("missing");
- ASSERT_FALSE(chdir_path_result.ok());
- EXPECT_TRUE(chdir_path_result.error().no_entity());
- // Dangling symlink.
- chdir_path_result = dir_.Chdir("test");
- ASSERT_FALSE(chdir_path_result.ok());
- EXPECT_TRUE(chdir_path_result.error().no_entity());
- // Create a regular file and try to chdir to that.
- auto f = dir_.OpenWriteOnly("test2", CreationOptions::CreateNew);
- ASSERT_THAT(f, IsSuccess(_));
- auto write_result = f->WriteFromString("test2");
- EXPECT_THAT(write_result, IsSuccess(_));
- chdir_path_result = dir_.Chdir("test2");
- ASSERT_FALSE(chdir_path_result.ok());
- EXPECT_TRUE(chdir_path_result.error().not_dir());
- auto d2_result = Cwd().OpenDir("test_d2", CreationOptions::CreateNew);
- ASSERT_THAT(d2_result, IsSuccess(_));
- symlink_result = d2_result->Symlink("test2", "def");
- EXPECT_THAT(symlink_result, IsSuccess(_));
- chdir_path_result = dir_.Chdir("test_d2");
- ASSERT_THAT(chdir_path_result, IsSuccess(_));
- readlink_result = Cwd().Readlink("test2");
- EXPECT_THAT(readlink_result, IsSuccess(Eq("def")));
- readlink_result = Cwd().Readlink("../test");
- EXPECT_THAT(readlink_result, IsSuccess(Eq("abc")));
- chdir_result = current_result->Chdir();
- ASSERT_THAT(chdir_result, IsSuccess(_));
- readlink_result = Cwd().Readlink("test");
- ASSERT_FALSE(readlink_result.ok());
- EXPECT_TRUE(readlink_result.error().no_entity());
- (*std::move(f)).Close().Check();
- }
- TEST_F(FilesystemTest, WriteStream) {
- std::string content_str = "0123456789";
- auto write = dir_.OpenWriteOnly("test", CreationOptions::CreateNew);
- ASSERT_THAT(write, IsSuccess(_));
- {
- llvm::raw_fd_ostream os = write->WriteStream();
- os << content_str;
- EXPECT_FALSE(os.has_error()) << os.error();
- }
- (*std::move(write)).Close().Check();
- EXPECT_THAT(dir_.ReadFileToString("test"), IsSuccess(Eq(content_str)));
- }
- TEST_F(FilesystemTest, Rename) {
- // Rename a file within a directory.
- ASSERT_THAT(dir_.WriteFileFromString("file1", "content1"), IsSuccess(_));
- EXPECT_THAT(dir_.Rename("file1", dir_, "file2"), IsSuccess(_));
- EXPECT_THAT(dir_.ReadFileToString("file2"), IsSuccess(Eq("content1")));
- auto read_missing = dir_.ReadFileToString("file1");
- EXPECT_FALSE(read_missing.ok());
- EXPECT_TRUE(read_missing.error().no_entity());
- // Rename a file between two directories.
- auto d1 = *dir_.CreateDirectories("subdir1");
- EXPECT_THAT(dir_.Rename("file2", d1, "file1"), IsSuccess(_));
- EXPECT_THAT(d1.ReadFileToString("file1"), IsSuccess(Eq("content1")));
- auto d2 = *dir_.CreateDirectories("subdir2");
- EXPECT_THAT(d1.Rename("file1", d2, "file1"), IsSuccess(_));
- EXPECT_THAT(d2.ReadFileToString("file1"), IsSuccess(Eq("content1")));
- // Close the first directory.
- d1 = Filesystem::Dir();
- EXPECT_THAT(dir_.Rmdir("subdir1"), IsSuccess(_))
- << "Directory should have bene empty!";
- // Rename directories.
- ASSERT_THAT(dir_.ReadFileToString(std::filesystem::path("subdir2") / "file1"),
- IsSuccess(Eq("content1")));
- EXPECT_THAT(dir_.Rename("subdir2", dir_, "subdir1"), IsSuccess(_));
- EXPECT_THAT(dir_.ReadFileToString(std::filesystem::path("subdir1") / "file1"),
- IsSuccess(Eq("content1")));
- // The open directory `d2` should survive the rename and point at the same
- // directory.
- EXPECT_THAT(d2.ReadFileToString("file1"), IsSuccess(Eq("content1")));
- EXPECT_THAT(d2.WriteFileFromString("file2", "content2"), IsSuccess(_));
- EXPECT_THAT(dir_.ReadFileToString(std::filesystem::path("subdir1") / "file2"),
- IsSuccess(Eq("content2")));
- // Rename over an existing file.
- EXPECT_THAT(d2.Rename("file2", d2, "file1"), IsSuccess(_));
- EXPECT_THAT(d2.ReadFileToString("file1"), IsSuccess(Eq("content2")));
- // Test error calls as well.
- auto result = dir_.Rename("missing1", dir_, "missing2");
- EXPECT_TRUE(result.error().no_entity()) << result.error();
- result = d2.Rename("file1", dir_,
- std::filesystem::path("missing_subdir") / "file2");
- EXPECT_TRUE(result.error().no_entity()) << result.error();
- // Note that `d2` was renamed `subdir1` above, which is why this creates
- // infinite subdirectories.
- result = dir_.Rename("subdir1", d2, "infinite_subdirs");
- EXPECT_THAT(result.error().unix_errnum(), EINVAL) << result.error();
- }
- } // namespace
- } // namespace Carbon::Filesystem
|