| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- // 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();
- }
- } // namespace
- } // namespace Carbon::Filesystem
|