⛏️ index : haiku.git

/*
 * Copyright 2008-2012, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#define write_pos	block_cache_write_pos
#define read_pos	block_cache_read_pos

#include "block_cache.cpp"

#undef write_pos
#undef read_pos


#define MAX_BLOCKS					100
#define BLOCK_CHANGED_IN_MAIN		(1L << 16)
#define BLOCK_CHANGED_IN_SUB		(2L << 16)
#define BLOCK_CHANGED_IN_PREVIOUS	(4L << 16)

#define TEST_BLOCKS(number, count) \
	test_blocks(number, count, __LINE__)
#define TEST_TRANSACTION(id, num, mainNum, subNum) \
	test_transaction(id, num, mainNum, subNum, __LINE__)

#define TEST_BLOCK_DATA(block, number, type) \
	if ((block)->type ## _data != NULL && gBlocks[(number)]. type == 0) \
		error(line, "Block %lld: " #type " should be NULL!", (number)); \
	if ((block)->type ## _data != NULL && gBlocks[(number)]. type != 0 \
		&& *(int32*)(block)->type ## _data != gBlocks[(number)]. type) { \
		error(line, "Block %lld: " #type " wrong (0x%lx should be 0x%lx)!", \
			(number), *(int32*)(block)->type ## _data, \
			gBlocks[(number)]. type); \
	}

#define TEST_ASSERT(statement) \
	if (!(statement)) { \
		error(__LINE__, "Assertion failed: " #statement); \
	}


struct test_block {
	int32	current;
	int32	original;
	int32	parent;
	int32	previous_transaction;
	int32	transaction;
	bool	unused;
	bool	is_dirty;
	bool	discard;

	bool	write;

	bool	read;
	bool	written;
	bool	present;
};

test_block gBlocks[MAX_BLOCKS];
block_cache* gCache;
size_t gBlockSize;
int32 gTest;
int32 gSubTest;
const char* gTestName;


void
dump_cache()
{
	char cacheString[32];
	sprintf(cacheString, "%p", gCache);
	char* argv[4];
	argv[0] = "dump";
	argv[1] = "-bt";
	argv[2] = cacheString;
	argv[3] = NULL;
	dump_cache(3, argv);
}


void
error(int32 line, const char* format, ...)
{
	va_list args;
	va_start(args, format);

	fprintf(stderr, "ERROR IN TEST LINE %ld: ", line);
	vfprintf(stderr, format, args);
	fprintf(stderr, "\n");

	va_end(args);

	dump_cache();

	exit(1);
}


void
or_block(void* block, int32 value)
{
	int32* data = (int32*)block;
	*data |= value;
}


void
set_block(void* block, int32 value)
{
	int32* data = (int32*)block;
	*data = value;
}


void
reset_block(void* block, int32 index)
{
	int32* data = (int32*)block;
	*data = index + 1;
}


ssize_t
block_cache_write_pos(int fd, off_t offset, const void* buffer, size_t size)
{
	int32 index = offset / gBlockSize;

	gBlocks[index].written = true;
	if (!gBlocks[index].write)
		error(__LINE__, "Block %ld should not be written!\n", index);

	return size;
}


ssize_t
block_cache_read_pos(int fd, off_t offset, void* buffer, size_t size)
{
	int32 index = offset / gBlockSize;

	memset(buffer, 0xcc, size);
	reset_block(buffer, index);
	if (!gBlocks[index].read)
		error(__LINE__, "Block %ld should not be read!\n", index);

	return size;
}


void
init_test_blocks()
{
	memset(gBlocks, 0, sizeof(test_block) * MAX_BLOCKS);

	for (uint32 i = 0; i < MAX_BLOCKS; i++) {
		gBlocks[i].current = i + 1;
		gBlocks[i].unused = true;
	}
}


void
test_transaction(int32 id, int32 numBlocks, int32 numMainBlocks,
	int32 numSubBlocks, int32 line)
{
	MutexLocker locker(&gCache->lock);
	cache_transaction* transaction = lookup_transaction(gCache, id);

	if (numBlocks != transaction->num_blocks) {
		error(line, "Transaction %d has wrong num_blocks (is %d, should be "
			"%d)!", id, transaction->num_blocks, numBlocks);
	}
	if (numMainBlocks != transaction->main_num_blocks) {
		error(line, "Transaction %d has wrong num_blocks (is %d, should be "
			"%d)!", id, transaction->main_num_blocks, numMainBlocks);
	}
	if (numSubBlocks != transaction->sub_num_blocks) {
		error(line, "Transaction %d has wrong num_blocks (is %d, should be "
			"%d)!", id, transaction->sub_num_blocks, numSubBlocks);
	}
}


void
test_blocks(off_t number, int32 count, int32 line)
{
	printf("  %ld\n", gSubTest++);

	for (int32 i = 0; i < count; i++, number++) {
		MutexLocker locker(&gCache->lock);

		cached_block* block = (cached_block*)hash_lookup(gCache->hash, &number);
		if (block == NULL) {
			if (gBlocks[number].present)
				error(line, "Block %lld not found!", number);
			continue;
		}
		if (!gBlocks[number].present)
			error(line, "Block %lld is present, but should not!", number);

		if (block->is_dirty != gBlocks[number].is_dirty) {
			error(line, "Block %lld: dirty bit differs (is %d should be %d)!",
				number, block->is_dirty, gBlocks[number].is_dirty);
		}
#if 0
		if (block->unused != gBlocks[number].unused) {
			error("Block %ld: unused bit differs (%d should be %d)!", number,
				block->unused, gBlocks[number].unused);
		}
#endif
		if (block->discard != gBlocks[number].discard) {
			error(line, "Block %lld: discard bit differs (is %d should be %d)!",
				number, block->discard, gBlocks[number].discard);
		}
		if (gBlocks[number].write && !gBlocks[number].written)
			error(line, "Block %lld: has not been written yet!", number);

		TEST_BLOCK_DATA(block, number, current);
		TEST_BLOCK_DATA(block, number, original);
		TEST_BLOCK_DATA(block, number, parent);
	}
}


void
stop_test(void)
{
	if (gCache == NULL)
		return;
	TEST_BLOCKS(0, MAX_BLOCKS);

//	dump_cache();
	block_cache_delete(gCache, true);
}


void
start_test(const char* name, bool init = true)
{
	if (init) {
		stop_test();

		gBlockSize = 2048;
		gCache = (block_cache*)block_cache_create(-1, MAX_BLOCKS, gBlockSize,
			false);

		init_test_blocks();
	}

	gTest++;
	gTestName = name;
	gSubTest = 1;

	printf("----------- Test %ld%s%s -----------\n", gTest,
		gTestName[0] ? " - " : "", gTestName);
}


/*!	Changes block 1 in main, block 2 if touchedInMain is \c true.
	Changes block 0 in sub, discards block 2.
	Performs two block tests.
*/
void
basic_test_discard_in_sub(int32 id, bool touchedInMain)
{
	gBlocks[1].present = true;
	gBlocks[1].read = true;
	gBlocks[1].is_dirty = true;
	gBlocks[1].original = gBlocks[1].current;
	gBlocks[1].current |= BLOCK_CHANGED_IN_MAIN;

	void* block = block_cache_get_writable(gCache, 1, id);
	or_block(block, BLOCK_CHANGED_IN_MAIN);
	block_cache_put(gCache, 1);

	TEST_BLOCKS(0, 2);

	if (touchedInMain) {
		gBlocks[2].present = true;
		gBlocks[2].is_dirty = true;
		gBlocks[2].current |= BLOCK_CHANGED_IN_MAIN;

		block = block_cache_get_empty(gCache, 2, id);
		reset_block(block, 2);
		or_block(block, BLOCK_CHANGED_IN_MAIN);
		block_cache_put(gCache, 2);
	}

	cache_start_sub_transaction(gCache, id);

	gBlocks[0].present = true;
	gBlocks[0].read = true;
	gBlocks[0].is_dirty = true;
	if ((gBlocks[0].current & BLOCK_CHANGED_IN_MAIN) != 0)
		gBlocks[0].parent = gBlocks[0].current;
	else
		gBlocks[0].original = gBlocks[0].current;
	gBlocks[0].current |= BLOCK_CHANGED_IN_SUB;

	gBlocks[1].parent = gBlocks[1].current;
	if (touchedInMain)
		gBlocks[2].parent = gBlocks[2].current;

	block = block_cache_get_writable(gCache, 0, id);
	or_block(block, BLOCK_CHANGED_IN_SUB);
	block_cache_put(gCache, 0);

	gBlocks[2].discard = true;

	block_cache_discard(gCache, 2, 1);

	TEST_BLOCKS(0, 2);

	gBlocks[0].is_dirty = false;
	gBlocks[0].write = true;
	gBlocks[1].is_dirty = false;
	gBlocks[1].write = true;
	gBlocks[2].present = false;
	gBlocks[2].write = touchedInMain;
	gBlocks[2].is_dirty = false;
}


// #pragma mark - Tests


void
test_abort_transaction()
{
	start_test("Abort main");

	int32 id = cache_start_transaction(gCache);

	gBlocks[0].present = true;
	gBlocks[0].read = false;
	gBlocks[0].write = false;
	gBlocks[0].is_dirty = true;
	gBlocks[1].present = true;
	gBlocks[1].read = true;
	gBlocks[1].write = false;
	gBlocks[1].is_dirty = true;

	void* block = block_cache_get_empty(gCache, 0, id);
	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
	gBlocks[0].current = BLOCK_CHANGED_IN_PREVIOUS;

	block = block_cache_get_writable(gCache, 1, id);
	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
	gBlocks[1].original = gBlocks[1].current;
	gBlocks[1].current |= BLOCK_CHANGED_IN_PREVIOUS;

	block_cache_put(gCache, 0);
	block_cache_put(gCache, 1);

	cache_end_transaction(gCache, id, NULL, NULL);
	TEST_BLOCKS(0, 2);

	id = cache_start_transaction(gCache);

	block = block_cache_get_writable(gCache, 0, id);
	or_block(block, BLOCK_CHANGED_IN_MAIN);

	block = block_cache_get_writable(gCache, 1, id);
	or_block(block, BLOCK_CHANGED_IN_MAIN);

	block_cache_put(gCache, 0);
	block_cache_put(gCache, 1);

	cache_abort_transaction(gCache, id);

	gBlocks[0].write = true;
	gBlocks[0].is_dirty = false;
	gBlocks[1].write = true;
	gBlocks[1].is_dirty = false;
	cache_sync_transaction(gCache, id);
}


void
test_abort_sub_transaction()
{
	start_test("Abort sub");

	int32 id = cache_start_transaction(gCache);

	gBlocks[0].present = true;
	gBlocks[0].read = false;
	gBlocks[0].write = false;
	gBlocks[0].is_dirty = true;
	gBlocks[1].present = true;
	gBlocks[1].read = true;
	gBlocks[1].write = false;
	gBlocks[1].is_dirty = true;

	void* block = block_cache_get_empty(gCache, 0, id);
	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
	gBlocks[0].current = BLOCK_CHANGED_IN_PREVIOUS;

	block = block_cache_get_writable(gCache, 1, id);
	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
	gBlocks[1].original = gBlocks[1].current;
	gBlocks[1].current |= BLOCK_CHANGED_IN_PREVIOUS;

	block_cache_put(gCache, 0);
	block_cache_put(gCache, 1);

	cache_start_sub_transaction(gCache, id);

	gBlocks[0].parent = gBlocks[0].current;
	gBlocks[1].parent = gBlocks[1].current;
	TEST_BLOCKS(0, 2);

	block = block_cache_get_writable(gCache, 1, id);
	or_block(block, BLOCK_CHANGED_IN_MAIN);

	block_cache_put(gCache, 1);

	TEST_TRANSACTION(id, 2, 2, 1);
	cache_abort_sub_transaction(gCache, id);
	TEST_TRANSACTION(id, 2, 2, 0);

	gBlocks[0].write = true;
	gBlocks[0].is_dirty = false;
	gBlocks[1].write = true;
	gBlocks[1].is_dirty = false;

	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	start_test("Abort sub with empty block");
	id = cache_start_transaction(gCache);

	gBlocks[1].present = true;
	gBlocks[1].read = true;

	block = block_cache_get_writable(gCache, 1, id);
	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
	gBlocks[1].original = gBlocks[1].current;
	gBlocks[1].current |= BLOCK_CHANGED_IN_PREVIOUS;

	block_cache_put(gCache, 1);

	gBlocks[1].is_dirty = true;
	TEST_BLOCKS(1, 1);

	TEST_TRANSACTION(id, 1, 0, 0);
	cache_start_sub_transaction(gCache, id);
	TEST_TRANSACTION(id, 1, 1, 0);

	gBlocks[0].present = true;

	block = block_cache_get_empty(gCache, 0, id);
	or_block(block, BLOCK_CHANGED_IN_SUB);
	gBlocks[0].current = BLOCK_CHANGED_IN_SUB;

	block_cache_put(gCache, 0);

	TEST_TRANSACTION(id, 2, 1, 1);
	cache_abort_sub_transaction(gCache, id);
	TEST_TRANSACTION(id, 1, 1, 0);

	gBlocks[0].write = false;
	gBlocks[0].is_dirty = false;
	gBlocks[0].parent = 0;
	gBlocks[0].original = 0;
	TEST_BLOCKS(0, 1);

	gBlocks[1].write = true;
	gBlocks[1].is_dirty = false;
	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);
}


void
test_block_cache_discard()
{
	// Test transactions and block caches

	start_test("Discard in main");

	int32 id = cache_start_transaction(gCache);

	gBlocks[0].present = true;
	gBlocks[0].read = true;

	block_cache_get(gCache, 0);
	block_cache_put(gCache, 0);

	gBlocks[1].present = true;
	gBlocks[1].read = true;
	gBlocks[1].write = true;

	void* block = block_cache_get_writable(gCache, 1, id);
	block_cache_put(gCache, 1);

	gBlocks[2].present = false;

	TEST_TRANSACTION(id, 1, 0, 0);
	block = block_cache_get_empty(gCache, 2, id);
	TEST_TRANSACTION(id, 2, 0, 0);
	block_cache_discard(gCache, 2, 1);
	block_cache_put(gCache, 2);

	cache_end_transaction(gCache, id, NULL, NULL);
	TEST_TRANSACTION(id, 1, 0, 0);
	cache_sync_transaction(gCache, id);

	start_test("Discard in sub");

	id = cache_start_transaction(gCache);

	basic_test_discard_in_sub(id, false);
	TEST_ASSERT(cache_blocks_in_sub_transaction(gCache, id) == 1);

	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	gBlocks[0].is_dirty = false;
	gBlocks[1].is_dirty = false;

	start_test("Discard in sub, present in main");

	id = cache_start_transaction(gCache);

	basic_test_discard_in_sub(id, true);

	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	gBlocks[0].is_dirty = false;
	gBlocks[1].is_dirty = false;

	start_test("Discard in sub, changed in main, abort sub");

	id = cache_start_transaction(gCache);

	basic_test_discard_in_sub(id, true);
	TEST_ASSERT(cache_blocks_in_sub_transaction(gCache, id) == 1);

	gBlocks[0].current &= ~BLOCK_CHANGED_IN_SUB;
	gBlocks[0].is_dirty = false;
	gBlocks[0].write = false;
	gBlocks[1].write = false;
	gBlocks[1].is_dirty = true;
	gBlocks[2].present = true;
	gBlocks[2].is_dirty = true;
	gBlocks[2].write = false;
	gBlocks[2].discard = false;
	cache_abort_sub_transaction(gCache, id);
	TEST_BLOCKS(0, 2);

	gBlocks[1].is_dirty = false;
	gBlocks[1].write = true;
	gBlocks[2].is_dirty = false;
	gBlocks[2].write = true;
	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	start_test("Discard in sub, changed in main, new sub");

	id = cache_start_transaction(gCache);

	basic_test_discard_in_sub(id, true);

	cache_start_sub_transaction(gCache, id);
	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	start_test("Discard in sub, changed in main, detach sub");

	id = cache_start_transaction(gCache);

	basic_test_discard_in_sub(id, true);

	id = cache_detach_sub_transaction(gCache, id, NULL, NULL);

	gBlocks[0].is_dirty = true;
	gBlocks[0].write = false;
	gBlocks[1].is_dirty = true;
	gBlocks[1].write = false;

	TEST_BLOCKS(0, 2);

	gBlocks[0].is_dirty = false;
	gBlocks[0].write = true;
	gBlocks[1].is_dirty = false;
	gBlocks[1].write = true;

	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	start_test("Discard in sub, all changed in main, detach sub");

	id = cache_start_transaction(gCache);

	gBlocks[0].present = true;
	gBlocks[0].read = true;
	gBlocks[0].is_dirty = true;
	gBlocks[0].original = gBlocks[0].current;
	gBlocks[0].current |= BLOCK_CHANGED_IN_MAIN;

	block = block_cache_get_writable(gCache, 0, id);
	or_block(block, BLOCK_CHANGED_IN_MAIN);
	block_cache_put(gCache, 0);

	basic_test_discard_in_sub(id, true);

	id = cache_detach_sub_transaction(gCache, id, NULL, NULL);

	gBlocks[0].is_dirty = true;
	gBlocks[0].write = false;
	gBlocks[0].original |= BLOCK_CHANGED_IN_MAIN;
	gBlocks[1].is_dirty = true;
	gBlocks[1].write = false;

	TEST_BLOCKS(0, 2);

	gBlocks[0].is_dirty = false;
	gBlocks[0].write = true;
	gBlocks[1].is_dirty = false;
	gBlocks[1].write = true;

	cache_end_transaction(gCache, id, NULL, NULL);
	cache_sync_transaction(gCache, id);

	stop_test();
}


// #pragma mark -


int
main(int argc, char** argv)
{
	block_cache_init();

	// TODO: test transaction-less block caches
	// TODO: test read-only block caches
	test_abort_transaction();
	test_abort_sub_transaction();
	test_block_cache_discard();
	return 0;
}