blob: d5b079956231938f03d2c063b5ef0de3b87d38da [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <iostream>
#include <tuple>
#include <vector>
#include <fbl/unique_fd.h>
#include "src/storage/fs_test/fs_test_fixture.h"
namespace fs_test {
namespace {
using ParamType =
std::tuple<TestFilesystemOptions,
std::tuple</*write_offset=*/size_t, /*read_offset=*/size_t, /*write_size=*/size_t>>;
class SparseTest : public BaseFilesystemTest, public testing::WithParamInterface<ParamType> {
public:
SparseTest() : BaseFilesystemTest(std::get<0>(GetParam())) {}
size_t write_offset() const { return std::get<0>(std::get<1>(GetParam())); }
size_t read_offset() const { return std::get<1>(std::get<1>(GetParam())); }
size_t write_size() const { return std::get<2>(std::get<1>(GetParam())); }
};
TEST_P(SparseTest, ReadAfterSparseWriteReturnsCorrectData) {
const std::string my_file = GetPath("my_file");
fbl::unique_fd fd(open(my_file.c_str(), O_RDWR | O_CREAT, 0644));
ASSERT_TRUE(fd);
// Create a random write buffer of data
std::vector<uint8_t> wbuf(write_size());
unsigned int seed = static_cast<unsigned int>(zx_ticks_get());
std::cout << "Sparse test using seed: " << seed << std::endl;
for (size_t i = 0; i < write_size(); i++) {
wbuf[i] = (uint8_t)rand_r(&seed);
}
// Dump write buffer to file
ASSERT_EQ(pwrite(fd.get(), wbuf.data(), write_size(), write_offset()),
static_cast<ssize_t>(write_size()));
// Reopen file
ASSERT_EQ(close(fd.release()), 0);
fd.reset(open(my_file.c_str(), O_RDWR, 0644));
ASSERT_TRUE(fd);
// Access read buffer from file
const size_t file_size = write_offset() + write_size();
const size_t bytes_to_read =
(file_size - read_offset()) > write_size() ? write_size() : (file_size - read_offset());
ASSERT_GT(bytes_to_read, 0ul) << "We want to test writing AND reading";
std::vector<uint8_t> rbuf(bytes_to_read);
ASSERT_EQ(pread(fd.get(), rbuf.data(), bytes_to_read, read_offset()),
static_cast<ssize_t>(bytes_to_read));
const size_t sparse_length = (read_offset() < write_offset())
? std::min(bytes_to_read, write_offset() - read_offset())
: 0;
if (sparse_length > 0) {
for (size_t i = 0; i < sparse_length; i++) {
ASSERT_EQ(rbuf[i], 0) << "This portion of file should be sparse; but isn't";
}
}
const size_t wbuf_offset = (read_offset() < write_offset()) ? 0 : read_offset() - write_offset();
const size_t valid_length = bytes_to_read - sparse_length;
if (valid_length > 0) {
for (size_t i = 0; i < valid_length; i++) {
ASSERT_EQ(rbuf[sparse_length + i], wbuf[wbuf_offset + i]);
}
}
// Clean up
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink(my_file.c_str()), 0);
}
std::string GetParamDescription(const testing::TestParamInfo<ParamType>& param) {
std::stringstream s;
s << std::get<0>(param.param) << "WithWriteOffset" << std::get<0>(std::get<1>(param.param))
<< "ReadOffset" << std::get<1>(std::get<1>(param.param)) << "WriteSize"
<< std::get<2>(std::get<1>(param.param));
return s.str();
}
std::vector<TestFilesystemOptions> AllTestFilesystemsThatSupportSparseFilesWithCustomDisk() {
std::vector<TestFilesystemOptions> filesystems;
for (TestFilesystemOptions options : AllTestFilesystems()) {
if (!options.filesystem->GetTraits().supports_sparse_files)
continue;
options.device_block_count = 1LLU << 24;
options.device_block_size = 1LLU << 9;
options.fvm_slice_size = 1LLU << 23;
options.zero_fill = true;
filesystems.push_back(options);
}
return filesystems;
}
constexpr size_t kBlockSize = 8192;
constexpr size_t kDirectBlocks = 16;
INSTANTIATE_TEST_SUITE_P(
/*no prefix*/, SparseTest,
testing::Combine(
testing::ValuesIn(AllTestFilesystemsThatSupportSparseFilesWithCustomDisk()),
testing::Values(std::make_tuple(0, 0, kBlockSize),
std::make_tuple(kBlockSize / 2, 0, kBlockSize),
std::make_tuple(kBlockSize / 2, kBlockSize, kBlockSize),
std::make_tuple(kBlockSize, 0, kBlockSize),
std::make_tuple(kBlockSize, kBlockSize / 2, kBlockSize),
std::make_tuple(kBlockSize* kDirectBlocks,
kBlockSize* kDirectBlocks - kBlockSize, kBlockSize * 2),
std::make_tuple(kBlockSize* kDirectBlocks,
kBlockSize* kDirectBlocks - kBlockSize, kBlockSize * 32),
std::make_tuple(kBlockSize* kDirectBlocks + kBlockSize,
kBlockSize* kDirectBlocks - kBlockSize, kBlockSize * 32),
std::make_tuple(kBlockSize* kDirectBlocks + kBlockSize,
kBlockSize* kDirectBlocks + 2 * kBlockSize,
kBlockSize * 32))),
GetParamDescription);
// For filesystems that don't support sparse files, run with a reduced set of tests because
// they will run more slowly.
INSTANTIATE_TEST_SUITE_P(
NonSparseFilesystems, SparseTest,
testing::Combine(
testing::ValuesIn(MapAndFilterAllTestFilesystems(
[](const TestFilesystemOptions& options) -> std::optional<TestFilesystemOptions> {
if (options.filesystem->GetTraits().supports_sparse_files) {
return std::nullopt;
} else {
return options;
}
})),
testing::Values(std::make_tuple(0, 0, 100), std::make_tuple(500, 100, 100),
std::make_tuple(500, 550, 100), std::make_tuple(0, 0, 10000),
std::make_tuple(5000, 0, 10000), std::make_tuple(5000, 10000, 10000),
std::make_tuple(10000, 0, 10000), std::make_tuple(10000, 5000, 10000))),
GetParamDescription);
} // namespace
} // namespace fs_test