blob: 09f93afaf3253f53ae21cf282237941d8f7ef7ea [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/fit/defer.h>
#include <lib/unittest/unittest.h>
#include <lib/unittest/user_memory.h>
#include <lib/user_copy/user_ptr.h>
#include <stdio.h>
#include "object/buffer_chain.h"
namespace {
using testing::UserMemory;
static bool alloc_free_basic() {
BEGIN_TEST;
// An empty chain requires one buffer
BufferChain* bc = BufferChain::Alloc(0);
ASSERT_NE(bc, nullptr);
ASSERT_FALSE(bc->buffers()->is_empty());
ASSERT_EQ(bc->buffers()->size_slow(), 1u);
BufferChain::Free(bc);
// One Buffer is enough to hold one byte.
bc = BufferChain::Alloc(1);
ASSERT_FALSE(bc->buffers()->is_empty());
ASSERT_EQ(bc->buffers()->size_slow(), 1u);
ASSERT_NE(bc, nullptr);
BufferChain::Free(bc);
// One Buffer is still enough.
bc = BufferChain::Alloc(BufferChain::kContig);
ASSERT_FALSE(bc->buffers()->is_empty());
ASSERT_EQ(bc->buffers()->size_slow(), 1u);
ASSERT_NE(bc, nullptr);
BufferChain::Free(bc);
// Two pages allocated, only one used for the buffer.
bc = BufferChain::Alloc(BufferChain::kContig + 1);
ASSERT_FALSE(bc->buffers()->is_empty());
ASSERT_EQ(bc->buffers()->size_slow(), 1u);
ASSERT_NE(bc, nullptr);
BufferChain::Free(bc);
// Several pages allocated, only one used for the buffer.
bc = BufferChain::Alloc(10000 * BufferChain::kRawDataSize);
ASSERT_FALSE(bc->buffers()->is_empty());
ASSERT_EQ(bc->buffers()->size_slow(), 1u);
ASSERT_NE(bc, nullptr);
BufferChain::Free(bc);
END_TEST;
}
static bool append_copy_out() {
BEGIN_TEST;
constexpr size_t kOffset = 24;
constexpr size_t kFirstCopy = BufferChain::kContig + 8;
constexpr size_t kSecondCopy = BufferChain::kRawDataSize + 16;
constexpr size_t kSize = kOffset + kFirstCopy + kSecondCopy;
fbl::AllocChecker ac;
auto buf = ktl::unique_ptr<char[]>(new (&ac) char[kSize]);
ASSERT_TRUE(ac.check());
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kSize);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
BufferChain* bc = BufferChain::Alloc(kSize);
ASSERT_NE(nullptr, bc);
auto free_bc = fit::defer([&bc]() { BufferChain::Free(bc); });
ASSERT_EQ(1u, bc->buffers()->size_slow());
bc->Skip(kOffset);
// Fill the chain with 'A'.
memset(buf.get(), 'A', kFirstCopy);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf.get(), kFirstCopy));
ASSERT_EQ(ZX_OK, bc->Append(mem_in, kFirstCopy));
// Verify it.
auto iter = bc->buffers()->begin();
for (size_t i = kOffset; i < BufferChain::kContig; ++i) {
ASSERT_EQ('A', iter->data()[i]);
}
++iter;
for (size_t i = 0; i < kOffset + kFirstCopy - BufferChain::kContig; ++i) {
ASSERT_EQ('A', iter->data()[i]);
}
// Write a chunk of 'B' straddling all three buffers.
memset(buf.get(), 'B', kSecondCopy);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf.get(), kSecondCopy));
ASSERT_EQ(ZX_OK, bc->Append(mem_in, kSecondCopy));
// Verify it.
iter = bc->buffers()->begin();
for (size_t i = kOffset; i < BufferChain::kContig; ++i) {
ASSERT_EQ('A', iter->data()[i]);
}
++iter;
for (size_t i = 0; i < kOffset + kFirstCopy - BufferChain::kContig; ++i) {
ASSERT_EQ('A', iter->data()[i]);
}
for (size_t i = kOffset + kFirstCopy - BufferChain::kContig; i < BufferChain::kRawDataSize; ++i) {
ASSERT_EQ('B', iter->data()[i]);
}
++iter;
for (size_t i = 0;
i < kOffset + kFirstCopy + kSecondCopy - BufferChain::kContig - BufferChain::kRawDataSize;
++i) {
if (iter->data()[i] != 'B') {
ASSERT_EQ(int(i), -1);
}
ASSERT_EQ('B', iter->data()[i]);
}
ASSERT_TRUE(++iter == bc->buffers()->end());
// Copy it all out.
memset(buf.get(), 0, kSize);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf.get(), kSize));
ASSERT_EQ(ZX_OK, bc->CopyOut(mem_out, 0, kSize));
// Verify it.
memset(buf.get(), 0, kSize);
ASSERT_EQ(ZX_OK, mem_in.copy_array_from_user(buf.get(), kSize));
size_t index = kOffset;
for (size_t i = 0; i < kFirstCopy; ++i) {
ASSERT_EQ('A', buf[index++]);
}
for (size_t i = 0; i < kSecondCopy; ++i) {
ASSERT_EQ('B', buf[index++]);
}
END_TEST;
}
static bool free_unused_pages() {
BEGIN_TEST;
constexpr size_t kSize = 8 * PAGE_SIZE;
constexpr size_t kWriteSize = BufferChain::kContig + 1;
fbl::AllocChecker ac;
auto buf = ktl::unique_ptr<char[]>(new (&ac) char[kWriteSize]);
ASSERT_TRUE(ac.check());
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteSize);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
BufferChain* bc = BufferChain::Alloc(kSize);
ASSERT_NE(nullptr, bc);
auto free_bc = fit::defer([&bc]() { BufferChain::Free(bc); });
ASSERT_EQ(1u, bc->buffers()->size_slow());
memset(buf.get(), 0, kWriteSize);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf.get(), kWriteSize));
ASSERT_EQ(ZX_OK, bc->Append(mem_in, kWriteSize));
ASSERT_EQ(2u, bc->buffers()->size_slow());
bc->FreeUnusedBuffers();
ASSERT_EQ(2u, bc->buffers()->size_slow());
END_TEST;
}
static bool append_more_than_allocated() {
BEGIN_TEST;
constexpr size_t kAllocSize = 2 * PAGE_SIZE;
constexpr size_t kWriteSize = 2 * kAllocSize;
fbl::AllocChecker ac;
auto buf = ktl::unique_ptr<char[]>(new (&ac) char[kWriteSize]);
ASSERT_TRUE(ac.check());
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteSize);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
BufferChain* bc = BufferChain::Alloc(kAllocSize);
ASSERT_NE(nullptr, bc);
auto free_bc = fit::defer([&bc]() { BufferChain::Free(bc); });
ASSERT_EQ(1u, bc->buffers()->size_slow());
memset(buf.get(), 0, kWriteSize);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf.get(), kWriteSize));
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, bc->Append(mem_in, kWriteSize));
END_TEST;
}
static bool append_after_fail_fails() {
BEGIN_TEST;
constexpr size_t kAllocSize = 2 * PAGE_SIZE;
constexpr size_t kWriteSize = PAGE_SIZE;
fbl::AllocChecker ac;
auto buf = ktl::unique_ptr<char[]>(new (&ac) char[kWriteSize]);
ASSERT_TRUE(ac.check());
ktl::unique_ptr<UserMemory> mem = UserMemory::Create(kWriteSize);
auto mem_in = mem->user_in<char>();
auto mem_out = mem->user_out<char>();
BufferChain* bc = BufferChain::Alloc(kAllocSize);
ASSERT_NE(nullptr, bc);
auto free_bc = fit::defer([&bc]() { BufferChain::Free(bc); });
ASSERT_EQ(1u, bc->buffers()->size_slow());
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
bc->Append(make_user_in_ptr(static_cast<const char*>(nullptr)), kWriteSize));
memset(buf.get(), 0, kWriteSize);
ASSERT_EQ(ZX_OK, mem_out.copy_array_to_user(buf.get(), kWriteSize));
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, bc->Append(mem_in, kWriteSize));
END_TEST;
}
} // namespace
UNITTEST_START_TESTCASE(buffer_chain_tests)
UNITTEST("alloc_free_basic", alloc_free_basic)
UNITTEST("append_copy_out", append_copy_out)
UNITTEST("free_unused_pages", free_unused_pages)
UNITTEST("append_more_than_allocated", append_more_than_allocated)
UNITTEST("append_after_fail_fails", append_after_fail_fails)
UNITTEST_END_TESTCASE(buffer_chain_tests, "buffer_chain", "BufferChain tests")