off_t offset = 0; // Write scratch data to file that we can read back into.
EXPECT_TRUE(file.Write(ignore_prefix, sizeof(ignore_prefix), offset));
offset += sizeof(ignore_prefix);
EXPECT_TRUE(file.Write(read_suffix, sizeof(read_suffix), offset));
ASSERT_EQ(file.Flush(), 0);
// Reading at an offset should only produce 'bbbb...', since we ignore the 'aaa...' prefix. char buffer[sizeof(read_suffix)];
EXPECT_TRUE(file.PreadFully(buffer, sizeof(read_suffix), offset));
EXPECT_STREQ(&read_suffix[0], &buffer[0]);
constchar* test_string = "This is a test string";
size_t length = strlen(test_string) + 1; const size_t offset = 12;
std::unique_ptr<char[]> offset_read_string(newchar[length]);
std::unique_ptr<char[]> read_string(newchar[length]);
// Write scratch data to file that we can read back into.
EXPECT_TRUE(file.PwriteFully(test_string, length, offset));
ASSERT_EQ(file.Flush(), 0);
// Test reading both the offsets.
EXPECT_TRUE(file.PreadFully(&offset_read_string[0], length, offset));
EXPECT_STREQ(test_string, &offset_read_string[0]);
// Create a sparse file and return a pointer to it via the 'out_file' argument, necessary because // gtest assertions require the function to return void. void FdFileTest::CreateSparseSourceFile(size_t empty_prefix,
size_t empty_suffix, /*out*/ std::unique_ptr<art::ScratchFile>& out_file) { /* *Layoutofthesourcefile: *[optional<empty_prefix>emptyregion] *[<kChunkSize>datachunk]-\ *[<kChunkSize>emptychunk]| *[<kChunkSize>datachunk]| *[<kChunkSize>emptychunk]>(2*kNumChunks-1)kChunkSizechunks *[<kChunkSize>datachunk]| *[...]| *[<kChunkSize>datachunk]-/ *[optional<empty_suffix>emptyregion]
*/
out_file = std::make_unique<art::ScratchFile>();
FdFile* src = out_file->GetFile();
ASSERT_TRUE(src->IsOpened());
ASSERT_TRUE(src->WriteFully(data_buffer.data(), kChunkSize)); for (size_t i = 0; i < kNumChunks - 1; i++) { // Leave a chunk size of unwritten space between each data chunk.
ASSERT_GT(lseek(src->Fd(), kChunkSize, SEEK_CUR), 0);
ASSERT_TRUE(src->WriteFully(data_buffer.data(), kChunkSize));
}
ASSERT_EQ(src->SetLength(src->GetLength() + empty_suffix), 0);
ASSERT_EQ(src->Flush(), 0);
TEST_F(FdFileTest, Rename) { // To test that rename preserves sparsity (on systems that support file sparsity), create a sparse // source file.
std::unique_ptr<art::ScratchFile> src;
ASSERT_NO_FATAL_FAILURE(CreateSparseSourceFile(/*empty_prefix=*/0, /*empty_suffix=*/0, src));
// Move the file via a rename.
art::ScratchFile dest; const std::string& new_filename = dest.GetFilename(); const std::string& old_filename = src->GetFilename();
ASSERT_TRUE(src->GetFile()->Rename(new_filename));
// Confirm the FdFile path has correctly updated.
EXPECT_EQ(src->GetFile()->GetPath(), new_filename); // Check the offset of the moved file has not been modified.
EXPECT_EQ(lseek(src->GetFd(), /*offset=*/0, SEEK_CUR), src_offset);
// Test that the file no longer exists in the old location, and there is a file at the new // location with the expected length.
EXPECT_FALSE(art::OS::FileExists(old_filename.c_str()));
FdFile dest_file(new_filename, O_RDONLY, /*check_usage=*/false);
ASSERT_TRUE(dest_file.IsOpened());
EXPECT_EQ(dest_file.GetLength(), source_length);
// Confirm the file at the new location has the same number of allocated data blocks as the source // file. If the source file was a sparse file, this confirms that the sparsity was preserved // by the move. struct stat dest_stat;
ASSERT_EQ(fstat(dest_file.Fd(), &dest_stat), 0);
EXPECT_EQ(dest_stat.st_blocks, src_stat.st_blocks);
// And it is exactly the same file in the new location, with the same contents.
EXPECT_EQ(dest_stat.st_dev, src_stat.st_dev);
EXPECT_EQ(dest_stat.st_ino, src_stat.st_ino);
char src_data[] = "Some test data.";
ASSERT_TRUE(src.WriteFully(src_data, sizeof(src_data))); // Including the zero terminator.
ASSERT_EQ(0, src.Flush());
ASSERT_EQ(static_cast<int64_t>(sizeof(src_data)), src.GetLength());
#ifdef __linux__ // Test that the file created by FdFileTest::CreateSparseSourceFile is sparse on the test // environment.
TEST_F(FdFileTest, CopySparseCreateSparseFile) { // Disable on host as sparsity is filesystem dependent and some hosts may break test assumptions.
TEST_DISABLED_FOR_HOST();
// Create file with no empty prefix or suffix.
std::unique_ptr<art::ScratchFile> src1;
ASSERT_NO_FATAL_FAILURE(CreateSparseSourceFile(/*empty_prefix=*/0, /*empty_suffix=*/0, src1));
struct stat src1_stat;
ASSERT_EQ(fstat(src1->GetFd(), &src1_stat), 0);
// It has at least as many allocated blocks required to represent the data chunks.
EXPECT_GE(src1_stat.st_blocks * kStatBlockSize, kNumChunks * kChunkSize); // It is sparse: it has fewer allocated blocks than would be required if the whole file was data.
EXPECT_LT(src1_stat.st_blocks * kStatBlockSize, src1_stat.st_size);
// Create file with an empty prefix and empty suffix.
std::unique_ptr<art::ScratchFile> src2;
ASSERT_NO_FATAL_FAILURE(CreateSparseSourceFile(kChunkSize, kChunkSize, src2));
// File should have the same number of allocated blocks. struct stat src2_stat;
ASSERT_EQ(fstat(src2->GetFd(), &src2_stat), 0);
EXPECT_EQ(src2_stat.st_blocks, src1_stat.st_blocks);
}
// Test complete copies of the source file produced by FdFileTest::CreateSparseSourceFile.
TEST_F(FdFileTest, CopySparseFullCopy) { // Disable on host as sparsity is filesystem dependent and some hosts may break test assumptions.
TEST_DISABLED_FOR_HOST();
// Test output sparsity matches the input sparsity. struct stat src_stat, dest_stat;
ASSERT_EQ(fstat(src->GetFd(), &src_stat), 0);
ASSERT_EQ(fstat(dest.GetFd(), &dest_stat), 0);
EXPECT_EQ(dest_stat.st_blocks, src_stat.st_blocks);
// Test the resulting data in the destination is correct.
ASSERT_NO_FATAL_FAILURE(TestDataMatches(src->GetFile(),
dest.GetFile(), /*input_offset=*/0u, /*output_offset=*/0u,
copy_size));
};
// Test full copies using different offsets and outer skip regions of sizes [0, 128, 2048, 32768].
ASSERT_NO_FATAL_FAILURE(verify_fullcopy(0, 0)); for (size_t empty_region_size = 128;
empty_region_size <= kChunkSize / 2;
empty_region_size <<= 4) { // Empty prefix.
ASSERT_NO_FATAL_FAILURE(verify_fullcopy(/*empty_prefix=*/empty_region_size, /*empty_suffix=*/0u)); // Empty suffix.
ASSERT_NO_FATAL_FAILURE(verify_fullcopy(/*empty_prefix=*/0u, /*empty_suffix=*/empty_region_size)); // Both.
ASSERT_NO_FATAL_FAILURE(verify_fullcopy(/*empty_prefix=*/empty_region_size, /*empty_suffix=*/empty_region_size));
}
}
// Find the filesystem blocksize of the test environment by creating and calling fstat on a // temporary file.
size_t FdFileTest::GetFilesystemBlockSize() {
art::ScratchFile tmpfile; if (!tmpfile.GetFile()->IsOpened()) { return0;
} struct stat tmp_stat; if (fstat(tmpfile.GetFd(), &tmp_stat) != 0) { return0;
} return tmp_stat.st_blksize;
}
// Test partial copies of the source file produced by FdFileTest::CreateSparseSourceFile.
TEST_F(FdFileTest, CopySparsePartialCopy) { // Disable on host as sparsity is filesystem dependent and some hosts may break test assumptions.
TEST_DISABLED_FOR_HOST();
auto verify_partialcopy = [&](size_t empty_prefix,
size_t empty_suffix,
size_t copy_start_offset,
size_t copy_end_offset) { // The copy starts <copy_start_offset> from the start of the source file. // The copy ends <copy_end_offset> from the end of the source file.
SCOPED_TRACE(::testing::Message() << "prefix:" << empty_prefix << ", suffix:" << empty_suffix
<< ", copy_start_offset:" << copy_start_offset
<< ", copy_end_offset:" << copy_end_offset);
// Test output sparsity matches the input sparsity, accounting for any discarded blocks. // For simplicity, only reason about the sparsity when there is no empty prefix/suffix, and we // are discarding no more than the first and/or last chunk of data. if (empty_prefix == 0 && empty_suffix == 0 && copy_start_offset <= kChunkSize
&& copy_end_offset <= kChunkSize) { // Round down to whole filesystem blocks, then convert to fstat blocksize.
size_t discarded_blocks = (copy_start_offset / blocksize) + (copy_end_offset / blocksize);
discarded_blocks *= (blocksize / kStatBlockSize);
struct stat src_stat, dest_stat;
ASSERT_EQ(fstat(src->GetFd(), &src_stat), 0);
ASSERT_EQ(fstat(dest.GetFd(), &dest_stat), 0);
if (art::IsAlignedParam(copy_start_offset, blocksize)) { // We expect the sparsity to be preserved.
EXPECT_EQ(dest_stat.st_blocks, src_stat.st_blocks - discarded_blocks);
} else { // As all data chunks are aligned, an non-aligned copy can only decrease the sparsity.
EXPECT_GT(dest_stat.st_blocks, src_stat.st_blocks - discarded_blocks);
}
}
// Test the resulting data in the destination is correct.
ASSERT_NO_FATAL_FAILURE(TestDataMatches(src->GetFile(),
dest.GetFile(), /*input_offset=*/copy_start_offset, /*output_offset=*/0u,
copy_size));
};
// Test partial copies with outer skip regions.
std::vector<size_t> outer_regions = {0, 128, 2 * art::KB, 32 * art::KB}; for (constauto& empty : outer_regions) { for (size_t discard = 0; discard <= 8 * art::KB; discard += 1 * art::KB) { // Start copy <discard> bytes after the file start.
ASSERT_NO_FATAL_FAILURE(verify_partialcopy(/*empty_prefix=*/empty, /*empty_suffix=*/empty, /*copy_start_offset=*/discard, /*copy_end_offset=*/0u)); // End copy <discard> bytes before the file end.
ASSERT_NO_FATAL_FAILURE(verify_partialcopy(/*empty_prefix=*/empty, /*empty_suffix=*/empty, /*copy_start_offset=*/0u, /*copy_end_offset=*/discard)); // Both.
ASSERT_NO_FATAL_FAILURE(verify_partialcopy(/*empty_prefix=*/empty, /*empty_suffix=*/empty, /*copy_start_offset=*/discard, /*copy_end_offset=*/discard));
}
}
}
// Test the case where the destination file's FD offset is non-zero before the copy.
TEST_F(FdFileTest, CopySparseToNonZeroOffset) { // Disable on host as sparsity is filesystem dependent and some hosts may break test assumptions.
TEST_DISABLED_FOR_HOST();
// Test the copied data appended to the destination is correct.
ASSERT_NO_FATAL_FAILURE(TestDataMatches(src->GetFile(),
dest_file, /*input_offset=*/0u, /*output_offset=*/existing_length,
copy_size));
} #endif
FdFile file2(std::move(file));
EXPECT_FALSE(file.IsOpened()); // NOLINT - checking file is no longer opened after move
EXPECT_TRUE(file2.IsOpened());
EXPECT_EQ(old_fd, file2.Fd());
TEST_F(FdFileTest, EraseWithPathUnlinks) { // New scratch file, zero-length.
art::ScratchFile tmp;
std::string filename = tmp.GetFilename();
tmp.Close(); // This is required because of the unlink race between the scratch file and the // FdFile, which leads to close-guard breakage.
FdFile file(filename, O_RDWR, false);
ASSERT_TRUE(file.IsOpened());
EXPECT_GE(file.Fd(), 0);
uint8_t buffer[16] = { 0 };
EXPECT_TRUE(file.WriteFully(&buffer, sizeof(buffer)));
EXPECT_EQ(file.Flush(), 0);
TEST_F(FdFileTest, Compare) {
std::vector<uint8_t> buffer;
constexpr int64_t length = 17 * art::KB; for (size_t i = 0; i < length; ++i) {
buffer.push_back(static_cast<uint8_t>(i));
}
auto reset_compare = [&](art::ScratchFile& a, art::ScratchFile& b) {
a.GetFile()->ResetOffset();
b.GetFile()->ResetOffset(); return a.GetFile()->Compare(b.GetFile());
};
// Basic equality check.
tmp.GetFile()->ResetOffset();
tmp2.GetFile()->ResetOffset(); // Files should be the same.
EXPECT_EQ(reset_compare(tmp, tmp2), 0);
// Change a byte near the start.
++buffer[2];
art::ScratchFile tmp3;
EXPECT_TRUE(tmp3.GetFile()->WriteFully(&buffer[0], length));
--buffer[2];
EXPECT_NE(reset_compare(tmp, tmp3), 0);
// Change a byte near the middle.
++buffer[length / 2];
art::ScratchFile tmp4;
EXPECT_TRUE(tmp4.GetFile()->WriteFully(&buffer[0], length));
--buffer[length / 2];
EXPECT_NE(reset_compare(tmp, tmp4), 0);
// Change a byte near the end.
++buffer[length - 5];
art::ScratchFile tmp5;
EXPECT_TRUE(tmp5.GetFile()->WriteFully(&buffer[0], length));
--buffer[length - 5];
EXPECT_NE(reset_compare(tmp, tmp5), 0);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.