1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "components/reporting/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 "components/reporting/util/status_macros.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.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_TRUE(remove_status.has_value()) << remove_status.error();
const auto read_status = MaybeReadFile(file_path, 0);
ASSERT_TRUE(read_status.has_value()) << 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.
// Don't test on Fuchsia: No file permission support. See
// base/files/file_util_unittest.cc for some similar tests being skipped.
#if !BUILDFLAG(IS_FUCHSIA)
{
// On Windows, we open the file to prevent it from being deleted. Otherwise,
// we modify the directory permission to prevent it from being deleted.
#if BUILDFLAG(IS_WIN)
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
ASSERT_TRUE(file.IsValid());
#else // BUILDFLAG(IS_WIN)
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));
#endif // BUILDFLAG(IS_WIN)
ASSERT_TRUE(base::PathExists(file_path));
ASSERT_FALSE(DeleteFileWarnIfFailed(file_path))
<< "Deletion of an existing file without permission should fail";
}
#endif // !BUILDFLAG(IS_FUCHSIA)
{
// 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,
FILE_PATH_LITERAL("*"))))
<< "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(FILE_PATH_LITERAL("subdir0"))));
ASSERT_TRUE(base::CreateDirectory(
dir_path.Append(FILE_PATH_LITERAL("subdir0/subdir1"))));
ASSERT_TRUE(base::CreateDirectory(
dir_path.Append(FILE_PATH_LITERAL("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(FILE_PATH_LITERAL("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_TRUE(read_status.has_value()) << 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_TRUE(read_status.has_value()) << 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_TRUE(read_status.has_value()) << 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_TRUE(read_status.has_value()) << 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
|