| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "missive/util/file.h" | |
| #include <string> | |
| #include <base/files/file.h> | |
| #include <base/files/scoped_temp_dir.h> | |
| #include <base/test/test_file_util.h> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "missive/util/status_macros.h" | |
| using ::testing::StrEq; | |
| namespace reporting { | |
| namespace { | |
| constexpr char kNewFile[] = "to_create.txt"; | |
| constexpr char kWriteDataOne[] = "hello world!"; | |
| constexpr char kWriteDataTwo[] = "bye world!"; | |
| constexpr char kMultiLineData[] = "12\n34\n56\n78\n"; | |
| constexpr size_t kMultiLineDataLineLength = 3; | |
| constexpr size_t kMultiLineDataLines = 4; | |
| constexpr size_t kOverFlowPos = 256; | |
| void RemoveAndTruncateTest(const base::FilePath& file_path, | |
| uint32_t pos, | |
| int expected_lines_removed) { | |
| const auto remove_status = RemoveAndTruncateLine(file_path, 0); | |
| ASSERT_OK(remove_status) << remove_status.error(); | |
| const auto read_status = MaybeReadFile(file_path, 0); | |
| ASSERT_OK(read_status) << read_status.error(); | |
| ASSERT_THAT( | |
| read_status.value(), | |
| StrEq( | |
| &kMultiLineData[expected_lines_removed * kMultiLineDataLineLength])); | |
| } | |
| TEST(FileTest, DeleteFileWarnIfFailed) { | |
| // This test briefly tests DeleteFileWarnIfFailed, as it mostly calls | |
| // DeleteFile(), which should be more extensively tested in base. | |
| base::ScopedTempDir temp_dir; | |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| const auto dir_path = temp_dir.GetPath(); | |
| ASSERT_TRUE(base::DirectoryExists(dir_path)); | |
| base::FilePath file_path; | |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(dir_path, &file_path)); | |
| // Delete an existing file with no permission. | |
| // Difference from the counterpart in components: We don't care about Windows | |
| // and Fuchsia here. | |
| { | |
| // we modify the directory permission to prevent it from being deleted. | |
| base::FilePermissionRestorer restore_permissions_for(dir_path); | |
| // Get rid of the write permission from temp_dir | |
| ASSERT_TRUE(base::MakeFileUnwritable(dir_path)); | |
| // Ensure no deletion permission | |
| ASSERT_FALSE(base::PathIsWritable(dir_path)); | |
| ASSERT_TRUE(base::PathExists(file_path)); | |
| ASSERT_FALSE(DeleteFileWarnIfFailed(file_path)) | |
| << "Deletion of an existing file without permission should fail"; | |
| } | |
| { | |
| // Delete with permission | |
| ASSERT_TRUE(base::PathIsWritable(dir_path)); // Ensure deletion permission | |
| ASSERT_TRUE(base::PathExists(file_path)); | |
| ASSERT_TRUE(DeleteFileWarnIfFailed(file_path)) | |
| << "Deletion of an existing file should succeed"; | |
| ASSERT_FALSE(base::PathExists(file_path)) << "File failed to be deleted"; | |
| } | |
| // Delete a non-existing file | |
| { | |
| ASSERT_FALSE(base::PathExists(file_path)); | |
| ASSERT_TRUE(DeleteFileWarnIfFailed(file_path)) | |
| << "Deletion of a nonexisting file should succeed"; | |
| } | |
| } | |
| TEST(FileTest, DeleteFilesWarnIfFailed) { | |
| base::ScopedTempDir temp_dir; | |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| const auto dir_path = temp_dir.GetPath(); | |
| ASSERT_TRUE(base::DirectoryExists(dir_path)); | |
| base::FilePath file_path; | |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(dir_path, &file_path)); | |
| // empty the directory | |
| ASSERT_TRUE(DeleteFilesWarnIfFailed(base::FileEnumerator( | |
| dir_path, /*recursive=*/false, base::FileEnumerator::FILES, "*"))) | |
| << "Failed to delete " << file_path.MaybeAsASCII(); | |
| ASSERT_FALSE(base::PathExists(file_path)) | |
| << "Deletion succeeds but " << file_path.MaybeAsASCII() | |
| << " still exists."; | |
| } | |
| TEST(FileTest, DeleteFilesWarnIfFailedSubSubDir) { | |
| base::ScopedTempDir temp_dir; | |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| const auto dir_path = temp_dir.GetPath(); | |
| ASSERT_TRUE(base::DirectoryExists(dir_path)); | |
| ASSERT_TRUE(base::CreateDirectory(dir_path.Append("subdir0"))); | |
| ASSERT_TRUE(base::CreateDirectory(dir_path.Append("subdir0/subdir1"))); | |
| ASSERT_TRUE( | |
| base::CreateDirectory(dir_path.Append("subdir0/subdir1/subdir2"))); | |
| // empty the directory | |
| ASSERT_TRUE(DeleteFilesWarnIfFailed(base::FileEnumerator( | |
| dir_path, /*recursive=*/true, | |
| base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES))); | |
| ASSERT_FALSE(base::PathExists(dir_path.Append("subdir0"))) | |
| << dir_path << " is not empty."; | |
| } | |
| TEST(FileTest, ReadWriteFile) { | |
| base::ScopedTempDir temp_dir; | |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| const auto dir_path = temp_dir.GetPath(); | |
| ASSERT_TRUE(base::DirectoryExists(dir_path)); | |
| base::FilePath file_path; | |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(dir_path, &file_path)); | |
| auto write_status = MaybeWriteFile(file_path, kWriteDataOne); | |
| ASSERT_OK(write_status) << write_status; | |
| auto read_status = MaybeReadFile(file_path, /*offset=*/0); | |
| ASSERT_OK(read_status) << read_status.error(); | |
| EXPECT_EQ(read_status.value(), kWriteDataOne); | |
| // Overwrite file. | |
| write_status = MaybeWriteFile(file_path, kWriteDataTwo); | |
| ASSERT_OK(write_status) << write_status; | |
| read_status = MaybeReadFile(file_path, /*offset=*/0); | |
| ASSERT_OK(read_status) << read_status.error(); | |
| EXPECT_EQ(read_status.value(), kWriteDataTwo); | |
| // Read file at an out of bounds index | |
| read_status = MaybeReadFile(file_path, kOverFlowPos); | |
| ASSERT_FALSE(read_status.has_value()); | |
| EXPECT_EQ(read_status.error().error_code(), error::DATA_LOSS); | |
| } | |
| TEST(FileTest, AppendLine) { | |
| base::ScopedTempDir temp_dir; | |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| const auto dir_path = temp_dir.GetPath(); | |
| ASSERT_TRUE(base::DirectoryExists(dir_path)); | |
| base::FilePath file_path; | |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(dir_path, &file_path)); | |
| // Create files. | |
| auto status = AppendLine(dir_path.AppendASCII(kNewFile), kWriteDataOne); | |
| ASSERT_OK(status) << status; | |
| status = AppendLine(file_path, kWriteDataOne); | |
| auto read_status = MaybeReadFile(file_path, /*offset=*/0); | |
| ASSERT_OK(read_status) << read_status.error(); | |
| ASSERT_EQ(read_status.value(), base::StrCat({kWriteDataOne, "\n"})); | |
| status = AppendLine(file_path, kWriteDataTwo); | |
| read_status = MaybeReadFile(file_path, /*offset=*/0); | |
| ASSERT_OK(read_status) << read_status.error(); | |
| ASSERT_EQ(read_status.value(), | |
| base::StrCat({kWriteDataOne, "\n", kWriteDataTwo, "\n"})); | |
| } | |
| TEST(FileTest, RemoveAndTruncateLine) { | |
| base::ScopedTempDir temp_dir; | |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| const auto dir_path = temp_dir.GetPath(); | |
| ASSERT_TRUE(base::DirectoryExists(dir_path)); | |
| base::FilePath file_path; | |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(dir_path, &file_path)); | |
| const auto write_status = MaybeWriteFile(file_path, kMultiLineData); | |
| ASSERT_OK(write_status) << write_status; | |
| // Load test data into string for substr method. | |
| const std::string multi_line_ref(kMultiLineData); | |
| int expected_lines_removed = 1; | |
| // Remove at beginning of line | |
| RemoveAndTruncateTest(file_path, 0, expected_lines_removed++); | |
| // Remove at middle of line | |
| RemoveAndTruncateTest(file_path, kMultiLineDataLineLength / 2, | |
| expected_lines_removed++); | |
| // Remove at end of line | |
| RemoveAndTruncateTest(file_path, kMultiLineDataLineLength - 1, | |
| expected_lines_removed++); | |
| // Remove at end of file | |
| const auto lines_left = kMultiLineDataLines - expected_lines_removed; | |
| RemoveAndTruncateTest(file_path, kMultiLineDataLineLength * lines_left - 1, | |
| expected_lines_removed); | |
| } | |
| } // namespace | |
| } // namespace reporting |