blob: bcb368f96eb3d18f0b3eaa3c62017b75f3ebfcdd [file] [log] [blame]
// Copyright 2019 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 <blobfs/writeback.h>
#include <blobfs/writeback-queue.h>
#include <unittest/unittest.h>
#include "utils.h"
namespace blobfs {
namespace {
constexpr uint32_t kBlockSize = 8192;
constexpr groupid_t kGroupID = 2;
constexpr uint32_t kDeviceBlockSize = 1024;
constexpr size_t kCapacity = 8;
class MockTransactionManager : public TransactionManager {
public:
~MockTransactionManager() {
if (writeback_) {
writeback_->Teardown();
}
}
bool Init() {
BEGIN_HELPER;
ASSERT_EQ(ZX_OK, WritebackQueue::Create(this, kCapacity, &writeback_));
END_HELPER;
}
uint32_t FsBlockSize() const final {
return kBlockSize;
}
groupid_t BlockGroupID() final {
return kGroupID;
}
uint32_t DeviceBlockSize() const final {
return kDeviceBlockSize;
}
zx_status_t Transaction(block_fifo_request_t* requests, size_t count) final {
// TODO(smklein): Improve validation.
return ZX_OK;
}
const Superblock& Info() const final {
return superblock_;
}
zx_status_t AddInodes(fzl::ResizeableVmoMapper* node_map) final {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t AddBlocks(size_t nblocks, RawBitmap* map) final {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t AttachVmo(const zx::vmo& vmo, vmoid_t* out) final {
*out = 2;
return ZX_OK;
}
zx_status_t DetachVmo(vmoid_t vmoid) final {
return ZX_OK;
}
BlobfsMetrics& LocalMetrics() final {
return metrics_;
}
size_t WritebackCapacity() const final {
return kCapacity;
}
zx_status_t CreateWork(fbl::unique_ptr<WritebackWork>* out, Blob* vnode) {
ZX_ASSERT(out != nullptr);
ZX_ASSERT(vnode == nullptr);
out->reset(new WritebackWork(this));
return ZX_OK;
}
zx_status_t EnqueueWork(fbl::unique_ptr<WritebackWork> work, EnqueueType type) {
ZX_ASSERT(type == EnqueueType::kData);
return writeback_->Enqueue(std::move(work));
}
private:
fbl::unique_ptr<WritebackQueue> writeback_{};
BlobfsMetrics metrics_{};
Superblock superblock_{};
};
// Enqueue a request which fits within writeback buffer.
bool EnqueuePaginatedSmallTest() {
BEGIN_TEST;
MockTransactionManager transaction_manager;
ASSERT_TRUE(transaction_manager.Init());
zx::vmo vmo;
fbl::unique_ptr<WritebackWork> work;
constexpr size_t kXferSize = kCapacity * kBlockSize;
ASSERT_EQ(ZX_OK, zx::vmo::create(kXferSize, 0, &vmo));
ASSERT_EQ(ZX_OK, transaction_manager.CreateWork(&work, nullptr));
ASSERT_EQ(ZX_OK, EnqueuePaginated(&work, &transaction_manager, nullptr,
vmo, 0, 0, kXferSize / kBlockSize));
ASSERT_EQ(ZX_OK, transaction_manager.EnqueueWork(std::move(work), EnqueueType::kData));
END_TEST;
}
// Enqueue a request which does not fit within writeback buffer.
bool EnqueuePaginatedLargeTest() {
BEGIN_TEST;
MockTransactionManager transaction_manager;
ASSERT_TRUE(transaction_manager.Init());
zx::vmo vmo;
fbl::unique_ptr<WritebackWork> work;
constexpr size_t kXferSize = kCapacity * kBlockSize;
ASSERT_EQ(ZX_OK, zx::vmo::create(kXferSize, 0, &vmo));
ASSERT_EQ(ZX_OK, transaction_manager.CreateWork(&work, nullptr));
ASSERT_EQ(ZX_OK, EnqueuePaginated(&work, &transaction_manager, nullptr,
vmo, 0, 0, kXferSize / kBlockSize));
ASSERT_EQ(ZX_OK, transaction_manager.EnqueueWork(std::move(work), EnqueueType::kData));
END_TEST;
}
// Enqueue multiple requests at once, which combine to be larger than the writeback buffer.
bool EnqueuePaginatedManyTest() {
BEGIN_TEST;
MockTransactionManager transaction_manager;
ASSERT_TRUE(transaction_manager.Init());
zx::vmo vmo;
fbl::unique_ptr<WritebackWork> work;
ASSERT_EQ(ZX_OK, zx::vmo::create(kCapacity * kBlockSize, 0, &vmo));
ASSERT_EQ(ZX_OK, transaction_manager.CreateWork(&work, nullptr));
constexpr size_t kSegments = 4;
constexpr size_t kBufferSizeBytes = kCapacity * kBlockSize;
static_assert(kBufferSizeBytes % 4 == 0, "Bad segment count");
constexpr size_t kXferSize = kBufferSizeBytes / kSegments;
for (size_t i = 0; i < kSegments; i++) {
ASSERT_EQ(ZX_OK, EnqueuePaginated(&work, &transaction_manager, nullptr,
vmo,
(i * kXferSize) / kBlockSize,
(i * kXferSize) / kBlockSize,
kXferSize / kBlockSize));
}
ASSERT_EQ(ZX_OK, transaction_manager.EnqueueWork(std::move(work), EnqueueType::kData));
END_TEST;
}
// Test that multiple completion callbacks may be added to a single WritebackWork.
bool WritebackWorkOrderTest() {
BEGIN_TEST;
MockTransactionManager transaction_manager;
ASSERT_TRUE(transaction_manager.Init());
zx::vmo vmo;
fbl::unique_ptr<WritebackWork> work;
ASSERT_EQ(ZX_OK, zx::vmo::create(kBlockSize, 0, &vmo));
ASSERT_EQ(ZX_OK, transaction_manager.CreateWork(&work, nullptr));
bool alpha_completion_done = false;
bool beta_completion_done = false;
// Current implementation documents that enqueueing "A, B" results in the completion of "B, A".
work->SetSyncCallback([&](zx_status_t status) {
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Unexpected callback status");
ZX_DEBUG_ASSERT_MSG(!alpha_completion_done, "Repeated completion");
ZX_DEBUG_ASSERT_MSG(beta_completion_done, "Bad completion order");
alpha_completion_done = true;
});
work->SetSyncCallback([&](zx_status_t status) {
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Unexpected callback status");
ZX_DEBUG_ASSERT_MSG(!alpha_completion_done, "Bad completion order");
ZX_DEBUG_ASSERT_MSG(!beta_completion_done, "Repeated completion");
beta_completion_done = true;
});
ASSERT_FALSE(alpha_completion_done);
ASSERT_FALSE(beta_completion_done);
work->MarkCompleted(ZX_OK);
ASSERT_TRUE(alpha_completion_done);
ASSERT_TRUE(beta_completion_done);
END_TEST;
}
} // namespace
} // namespace blobfs
BEGIN_TEST_CASE(blobfsWritebackTests)
RUN_TEST(blobfs::EnqueuePaginatedSmallTest)
RUN_TEST(blobfs::EnqueuePaginatedLargeTest)
RUN_TEST(blobfs::EnqueuePaginatedManyTest)
RUN_TEST(blobfs::WritebackWorkOrderTest)
END_TEST_CASE(blobfsWritebackTests)