iBoot/lib/blockdev/tests.c

471 lines
16 KiB
C

#include <unittest.h>
#include <lib/blockdev.h>
#include <string.h>
#include <stdlib.h>
struct mock_blockdev {
struct blockdev bdev;
/* local data */
void *ptr;
};
static int mock_blockdev_read_block(struct blockdev *bdev, void *ptr, block_addr block, uint32_t count)
{
struct mock_blockdev *mdev = (struct mock_blockdev *)bdev;
// no overflow
TEST_ASSERT_GTE(block + count, block);
// buffer must be properly aligned
TEST_ASSERT_EQ((uintptr_t)ptr & (bdev->alignment - 1), 0);
if (block >= mdev->bdev.block_count)
return 0;
if ((block + count) > mdev->bdev.block_count)
count = mdev->bdev.block_count - block;
memcpy(ptr, (uint8_t *)mdev->ptr + (block << mdev->bdev.block_shift), count << mdev->bdev.block_shift);
return count;
}
static int mock_blockdev_write_block(struct blockdev *bdev, const void *ptr, block_addr block, uint32_t count)
{
struct mock_blockdev *mdev = (struct mock_blockdev *)bdev;
// no overflow
TEST_ASSERT_GTE(block + count, block);
// buffer must be properly aligned
TEST_ASSERT_EQ((uintptr_t)ptr & (bdev->alignment - 1), 0);
if (block >= mdev->bdev.block_count)
return 0;
if ((block + count) > mdev->bdev.block_count)
count = mdev->bdev.block_count - block;
memcpy(mdev->ptr + (block << mdev->bdev.block_shift), ptr, count << mdev->bdev.block_shift);
return count;
}
struct blockdev *create_mock_blockdev(const char *name, void *ptr, uint64_t len, uint32_t block_size)
{
struct mock_blockdev *mdev;
mdev = malloc(sizeof(struct mock_blockdev));
construct_blockdev(&mdev->bdev, name, len, block_size);
mdev->ptr = ptr;
mdev->bdev.read_block_hook = &mock_blockdev_read_block;
mdev->bdev.write_block_hook = &mock_blockdev_write_block;
return &mdev->bdev;
}
static void do_blockdev_read_tests(struct blockdev *bdev, uint8_t *backing, size_t backing_size, uint32_t block_size)
{
uint8_t *readback;
uint8_t *zeroes;
int result;
readback = malloc(backing_size + 1);
TEST_ASSERT_NOT_NULL(readback);
zeroes = malloc(backing_size);
TEST_ASSERT_NOT_NULL(zeroes);
memset(zeroes, 0, backing_size);
// Reading entire buffer should work
memset(readback, 0, backing_size);
result = blockdev_read(bdev, readback, 0, backing_size);
TEST_ASSERT_EQ(result, backing_size);
TEST_ASSERT_MEM_EQ(backing, readback, backing_size);
// Reading entire buffer unaligned should work
memset(readback + 1, 0, backing_size);
result = blockdev_read(bdev, readback + 1, 0, backing_size);
TEST_ASSERT_EQ(result, backing_size);
TEST_ASSERT_MEM_EQ(backing, readback + 1, backing_size);
// Reading past the end results in a short read
memset(readback, 0, backing_size);
result = blockdev_read(bdev, readback, backing_size / 2, backing_size);
TEST_ASSERT_EQ(result, backing_size / 2);
// first half of buffer should have result of read
TEST_ASSERT_MEM_EQ(readback, backing + backing_size / 2, backing_size / 2);
// second half should be untouched
TEST_ASSERT_MEM_EQ(readback + backing_size / 2, zeroes, backing_size / 2);
// Reading totally past the end results in no read
memset(readback, 0, backing_size);
result = blockdev_read(bdev, readback, backing_size + 2, backing_size);
TEST_ASSERT_EQ(result, 0);
// buffer should be totally untouched
TEST_ASSERT_MEM_EQ(readback, zeroes, backing_size);
// Reads that are weird size should work
uint32_t read_sizes[] = {1, 2, 3, 13, 127, 129, 1023, 1025, 4094, 4097, block_size, block_size - 1, block_size - 2, backing_size - 2, backing_size - 1};
for (uint32_t i = 0; i < sizeof(read_sizes) / sizeof(read_sizes[0]); i++) {
uint32_t read_size = read_sizes[i];
if (read_size == 0 || read_size >= backing_size)
continue;
uint32_t read_offsets[] = {0, 1, 2, 3, 13, 127, 129, 1023, 1025, 4094, 4097, block_size, block_size - 1, block_size - 2, backing_size - 2, backing_size - 1};
for (uint32_t j = 0; j < sizeof(read_offsets) / sizeof(read_offsets[0]); j++) {
uint32_t read_offset = read_offsets[j];
if (read_offset >= backing_size || read_offset + read_size >= backing_size)
continue;
tprintf(TEST_INFO, "doing offset=%u size=%u\n", read_offset, read_size);
// make sure the read succeeds
memset(readback + 1, 0, backing_size);
result = blockdev_read(bdev, readback, read_offset, read_size);
TEST_ASSERT_EQ(result, read_size);
TEST_ASSERT_MEM_EQ(readback, backing + read_offset, read_size);
TEST_ASSERT_MEM_EQ(readback + read_size, zeroes, backing_size - read_size);
// make sure read succeeds with alignment forced off
memset(readback + 1, 0, backing_size);
result = blockdev_read(bdev, readback + 1, read_offset, read_size);
TEST_ASSERT_EQ(result, read_size);
TEST_ASSERT_MEM_EQ(readback + 1, backing + read_offset, read_size);
TEST_ASSERT_MEM_EQ(readback + 1 + read_size, zeroes, backing_size - read_size);
}
}
free(readback);
free(zeroes);
}
static void do_blockdev_read_block_tests(struct blockdev *bdev, uint8_t *backing, size_t backing_size, uint32_t block_size)
{
uint8_t *readback;
uint8_t *filler;
int result;
uint32_t blocks = backing_size / block_size;
readback = malloc(backing_size);
TEST_ASSERT_NOT_NULL(readback);
filler = malloc(backing_size + 1);
TEST_ASSERT_NOT_NULL(filler);
memset(filler, 0xee, backing_size);
// Reading entire buffer should work
memcpy(readback, filler, backing_size);
result = blockdev_read_block(bdev, readback, 0, blocks);
TEST_ASSERT_EQ(result, blocks);
TEST_ASSERT_MEM_EQ(backing, readback, backing_size);
// Reading past the end results in a short read
memcpy(readback, filler, backing_size);
result = blockdev_read_block(bdev, readback, blocks / 2, blocks);
TEST_ASSERT_EQ(result, blocks / 2);
// first half of buffer should have result of read
TEST_ASSERT_MEM_EQ(readback, backing + backing_size / 2, backing_size / 2);
// second half should be untouched
TEST_ASSERT_MEM_EQ(readback + backing_size / 2, filler, backing_size / 2);
// Reading totally past the end results in no read
memcpy(readback, filler, backing_size);
result = blockdev_read_block(bdev, readback, blocks + 2, blocks);
TEST_ASSERT_EQ(result, 0);
// buffer should be totally untouched
TEST_ASSERT_MEM_EQ(readback, filler, backing_size);
free(readback);
free(filler);
}
void test_mem_blockdev(uintptr_t param)
{
struct blockdev *bdev;
uint8_t *backing;
size_t backing_size = 8192;
uint32_t block_size = param;
backing = malloc(backing_size);
TEST_ASSERT_NOT_NULL(backing);
for (uint32_t i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val = i + 0xaabbccdd;
memcpy(backing + i * sizeof(uint32_t), &val, sizeof(uint32_t));
}
bdev = create_mem_blockdev("dev", backing, backing_size, block_size);
do_blockdev_read_tests(bdev, backing, backing_size, block_size);
do_blockdev_read_block_tests(bdev, backing, backing_size, block_size);
// make sure backing wasn't touched during all the tests
for (unsigned i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val;
memcpy(&val, backing + i * sizeof(uint32_t), sizeof(uint32_t));
TEST_ASSERT_EQ(val, i + 0xaabbccdd);
}
free(backing);
}
void test_blockdev_read_block(uintptr_t param)
{
struct blockdev *bdev;
uint8_t *backing;
size_t backing_size = 8192;
uint32_t block_size = param;
backing = malloc(backing_size);
TEST_ASSERT_NOT_NULL(backing);
for (uint32_t i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val = i + 0xaabbccdd;
memcpy(backing + i * sizeof(uint32_t), &val, sizeof(uint32_t));
}
bdev = create_mock_blockdev("dev1", backing, backing_size, block_size);
do_blockdev_read_block_tests(bdev, backing, backing_size, block_size);
// make sure backing wasn't touched during all the tests
for (unsigned i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val;
memcpy(&val, backing + i * sizeof(uint32_t), sizeof(uint32_t));
TEST_ASSERT_EQ(val, i + 0xaabbccdd);
}
free(backing);
}
void test_blockdev_read(uintptr_t param)
{
struct blockdev *bdev;
uint8_t *backing;
size_t backing_size = 8192;
uint32_t block_size = param;
backing = malloc(backing_size);
TEST_ASSERT_NOT_NULL(backing);
for (uint32_t i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val = i + 0xaabbccdd;
memcpy(backing + i * sizeof(uint32_t), &val, sizeof(uint32_t));
}
bdev = create_mock_blockdev("dev1", backing, backing_size, block_size);
do_blockdev_read_tests(bdev, backing, backing_size, block_size);
// make sure backing wasn't touched during all the tests
for (unsigned i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val;
memcpy(&val, backing + i * sizeof(uint32_t), sizeof(uint32_t));
TEST_ASSERT_EQ(val, i + 0xaabbccdd);
}
free(backing);
}
void test_blockdev_set_buffer_alignment_read(uintptr_t param)
{
struct blockdev *bdev;
uint8_t *backing;
size_t backing_size = 8192;
uint32_t block_size = param;
backing = malloc(backing_size);
TEST_ASSERT_NOT_NULL(backing);
for (uint32_t i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val = i + 0xaabbccdd;
memcpy(backing + i * sizeof(uint32_t), &val, sizeof(uint32_t));
}
bdev = create_mock_blockdev("dev1", backing, backing_size, block_size);
blockdev_set_buffer_alignment(bdev, block_size);
do_blockdev_read_tests(bdev, backing, backing_size, block_size);
if (block_size > 2) {
bdev = create_mock_blockdev("dev2", backing, backing_size, block_size);
blockdev_set_buffer_alignment(bdev, block_size / 2);
do_blockdev_read_tests(bdev, backing, backing_size, block_size);
}
// make sure backing wasn't touched during all the tests
for (unsigned i = 0; i < backing_size / sizeof(uint32_t); i++) {
uint32_t val;
memcpy(&val, backing + i * sizeof(uint32_t), sizeof(uint32_t));
TEST_ASSERT_EQ(val, i + 0xaabbccdd);
}
free(backing);
}
static void do_blockdev_write_tests(struct blockdev *bdev, uint8_t *backing, size_t backing_size, uint32_t block_size)
{
uint8_t *data;
uint8_t *filler;
int result;
// need some source data we can write to the blockdev with
// easily recognizable patterns
data = malloc(backing_size + sizeof(uint32_t));
TEST_ASSERT_NOT_NULL(data);
for (uint32_t i = 0; i < backing_size / sizeof(uint32_t) + 1; i++) {
uint32_t val = i + 0xaabbccdd;
memcpy(data + i * sizeof(uint32_t), &val, sizeof(uint32_t));
}
filler = malloc(backing_size);
TEST_ASSERT_NOT_NULL(filler);
memset(filler, 0xee, backing_size);
// Writing entire buffer should work
memcpy(backing, filler, backing_size);
result = blockdev_write(bdev, data, 0, backing_size);
TEST_ASSERT_EQ(result, backing_size);
TEST_ASSERT_MEM_EQ(backing, data, backing_size);
// Writing entire buffer unaligned should work
memcpy(backing, filler, backing_size);
result = blockdev_write(bdev, data + 1, 0, backing_size);
TEST_ASSERT_EQ(result, backing_size);
TEST_ASSERT_MEM_EQ(backing, data + 1, backing_size);
// Writing past the end results in a short write
memcpy(backing, filler, backing_size);
result = blockdev_write(bdev, data, backing_size / 2, backing_size);
TEST_ASSERT_EQ(result, backing_size / 2);
// first half of backing should be untouched
TEST_ASSERT_MEM_EQ(backing, filler, backing_size / 2);
// second half of buffer should have result of write
TEST_ASSERT_MEM_EQ(backing + backing_size / 2, data, backing_size / 2);
// Reading totally past the end results in no write
memcpy(backing, filler, backing_size);
result = blockdev_write(bdev, data, backing_size + 2, backing_size);
TEST_ASSERT_EQ(result, 0);
// buffer should be totally untouched
TEST_ASSERT_MEM_EQ(backing, filler, backing_size);
// weird size writes should work
uint32_t write_sizes[] = {1, 2, 3, 13, 127, 129, 1023, 1025, 4094, 4097, block_size, block_size - 1, block_size - 2, backing_size - 2, backing_size - 1};
for (uint32_t i = 0; i < sizeof(write_sizes) / sizeof(write_sizes[0]); i++) {
uint32_t write_size = write_sizes[i];
if (write_size == 0 || write_size >= backing_size)
continue;
uint32_t write_offsets[] = {0, 1, 2, 3, 13, 127, 129, 1023, 1025, 4094, 4097, block_size, block_size - 1, block_size - 2, backing_size - 2, backing_size - 1};
for (uint32_t j = 0; j < sizeof(write_offsets) / sizeof(write_offsets[0]); j++) {
uint32_t write_offset = write_offsets[j];
if (write_offset >= backing_size || write_offset + write_size >= backing_size)
continue;
tprintf(TEST_INFO, "doing offset=%u size=%u\n", write_offset, write_size);
memcpy(backing, filler, backing_size);
result = blockdev_write(bdev, data, write_offset, write_size);
TEST_ASSERT_EQ(result, write_size);
TEST_ASSERT_MEM_EQ(backing + write_offset, data, write_size);
// data before and after write should be untouched
TEST_ASSERT_MEM_EQ(backing, filler, write_offset);
TEST_ASSERT_MEM_EQ(backing + write_offset + write_size, filler, backing_size - write_size - write_offset);
}
}
free(data);
free(filler);
}
void test_blockdev_write(uintptr_t param)
{
struct blockdev *bdev;
uint8_t *backing;
size_t backing_size = 8192;
uint32_t block_size = param;
backing = malloc(backing_size);
bdev = create_mock_blockdev("dev1", backing, backing_size, block_size);
do_blockdev_write_tests(bdev, backing, backing_size, block_size);
if (block_size > 1) {
bdev = create_mock_blockdev("dev2", backing, backing_size, block_size);
blockdev_set_buffer_alignment(bdev, block_size);
do_blockdev_write_tests(bdev, backing, backing_size, block_size);
}
if (block_size > 2) {
bdev = create_mock_blockdev("dev3", backing, backing_size, block_size);
blockdev_set_buffer_alignment(bdev, block_size / 2);
do_blockdev_write_tests(bdev, backing, backing_size, block_size);
}
free(backing);
}
void test_register_blockdev(uintptr_t param)
{
static uint8_t backing;
struct blockdev *bdev0;
struct blockdev *bdev1;
uint32_t found = 0;
bdev0 = create_mock_blockdev("lookup-bdev0", &backing, 1, 1);
TEST_ASSERT_NOT_NULL(bdev0);
bdev1 = create_mock_blockdev("lookup-bdev1", &backing, 1, 1);
TEST_ASSERT_NOT_NULL(bdev1);
TEST_ASSERT_NULL(lookup_blockdev("lookup-bdev0"));
TEST_ASSERT_NULL(lookup_blockdev("lookup-bdev1"));
TEST_ASSERT_NULL(first_blockdev());
register_blockdev(bdev0);
TEST_ASSERT_PTR_EQ(lookup_blockdev("lookup-bdev0"), bdev0);
TEST_ASSERT_PTR_EQ(first_blockdev(), bdev0);
TEST_ASSERT_NULL(lookup_blockdev("lookup-bdev1"));
register_blockdev(bdev1);
TEST_ASSERT_PTR_EQ(lookup_blockdev("lookup-bdev0"), bdev0);
TEST_ASSERT_PTR_EQ(lookup_blockdev("lookup-bdev1"), bdev1);
for (struct blockdev *bdev = first_blockdev(); bdev != NULL; bdev = next_blockdev(bdev)) {
if (bdev == bdev0)
found |= 1;
if (bdev == bdev1)
found |= 2;
}
TEST_ASSERT_EQ(found, 3);
}
struct test_suite blockdev_suite = {
.name = "blockdev",
.test_cases = {
{ "register_blockdev", test_register_blockdev, 0, "" },
{ "mem_blockdev,1", test_mem_blockdev, 1, "" },
{ "mem_blockdev,2", test_mem_blockdev, 2, "" },
{ "mem_blockdev,4", test_mem_blockdev, 4, "" },
{ "mem_blockdev,1024", test_mem_blockdev, 1024, "" },
{ "blockdev_read_block,1", test_blockdev_read_block, 1, "" },
{ "blockdev_read_block,2", test_blockdev_read_block, 2, "" },
{ "blockdev_read_block,4", test_blockdev_read_block, 4, "" },
{ "blockdev_read_block,1024", test_blockdev_read_block, 1024, "" },
{ "blockdev_read_block,4096", test_blockdev_read_block, 4096, "" },
{ "blockdev_read,1", test_blockdev_read, 1, "" },
{ "blockdev_read,2", test_blockdev_read, 2, "" },
{ "blockdev_read,4", test_blockdev_read, 4, "" },
{ "blockdev_read,1024", test_blockdev_read, 1024, "" },
{ "blockdev_read,4096", test_blockdev_read, 4096, "" },
{ "blockdev_read,8192", test_blockdev_read, 8192, "" },
{ "blockdev_write,1", test_blockdev_write, 1, "" },
{ "blockdev_write,2", test_blockdev_write, 2, "" },
{ "blockdev_write,4", test_blockdev_write, 4, "" },
{ "blockdev_write,1024", test_blockdev_write, 1024, "" },
{ "blockdev_write,4096", test_blockdev_write, 4096, "" },
{ "blockdev_write,8192", test_blockdev_write, 8192, "" },
{ "blockdev_set_buffer_alignment_read,2", test_blockdev_set_buffer_alignment_read, 2, "" },
{ "blockdev_set_buffer_alignment_read,8", test_blockdev_set_buffer_alignment_read, 8, "" },
{ "blockdev_set_buffer_alignment_read,1024", test_blockdev_set_buffer_alignment_read, 1024, "" },
{ "blockdev_set_buffer_alignment_read,4096", test_blockdev_set_buffer_alignment_read, 4096, "" },
TEST_CASE_LAST
}
};
TEST_SUITE(blockdev_suite);