xref: /haiku/src/tests/system/kernel/cache/block_cache_test.cpp (revision b28ed9e04a771e5de38be68abd08148c0bbafc56)
1 /*
2  * Copyright 2008-2012, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #define write_pos	block_cache_write_pos
8 #define read_pos	block_cache_read_pos
9 
10 #include "block_cache.cpp"
11 
12 #undef write_pos
13 #undef read_pos
14 
15 
16 #define MAX_BLOCKS					100
17 #define BLOCK_CHANGED_IN_MAIN		(1L << 16)
18 #define BLOCK_CHANGED_IN_SUB		(2L << 16)
19 #define BLOCK_CHANGED_IN_PREVIOUS	(4L << 16)
20 
21 #define TEST_BLOCKS(number, count) \
22 	test_blocks(number, count, __LINE__)
23 #define TEST_TRANSACTION(id, num, mainNum, subNum) \
24 	test_transaction(id, num, mainNum, subNum, __LINE__)
25 
26 #define TEST_BLOCK_DATA(block, number, type) \
27 	if ((block)->type ## _data != NULL && gBlocks[(number)]. type == 0) \
28 		error(line, "Block %lld: " #type " should be NULL!", (number)); \
29 	if ((block)->type ## _data != NULL && gBlocks[(number)]. type != 0 \
30 		&& *(int32*)(block)->type ## _data != gBlocks[(number)]. type) { \
31 		error(line, "Block %lld: " #type " wrong (0x%lx should be 0x%lx)!", \
32 			(number), *(int32*)(block)->type ## _data, \
33 			gBlocks[(number)]. type); \
34 	}
35 
36 #define TEST_ASSERT(statement) \
37 	if (!(statement)) { \
38 		error(__LINE__, "Assertion failed: " #statement); \
39 	}
40 
41 
42 struct test_block {
43 	int32	current;
44 	int32	original;
45 	int32	parent;
46 	int32	previous_transaction;
47 	int32	transaction;
48 	bool	unused;
49 	bool	is_dirty;
50 	bool	discard;
51 
52 	bool	write;
53 
54 	bool	read;
55 	bool	written;
56 	bool	present;
57 };
58 
59 test_block gBlocks[MAX_BLOCKS];
60 block_cache* gCache;
61 size_t gBlockSize;
62 int32 gTest;
63 int32 gSubTest;
64 const char* gTestName;
65 
66 
67 void
68 dump_cache()
69 {
70 	char cacheString[32];
71 	sprintf(cacheString, "%p", gCache);
72 	char* argv[4];
73 	argv[0] = "dump";
74 	argv[1] = "-bt";
75 	argv[2] = cacheString;
76 	argv[3] = NULL;
77 	dump_cache(3, argv);
78 }
79 
80 
81 void
82 error(int32 line, const char* format, ...)
83 {
84 	va_list args;
85 	va_start(args, format);
86 
87 	fprintf(stderr, "ERROR IN TEST LINE %ld: ", line);
88 	vfprintf(stderr, format, args);
89 	fprintf(stderr, "\n");
90 
91 	va_end(args);
92 
93 	dump_cache();
94 
95 	exit(1);
96 }
97 
98 
99 void
100 or_block(void* block, int32 value)
101 {
102 	int32* data = (int32*)block;
103 	*data |= value;
104 }
105 
106 
107 void
108 set_block(void* block, int32 value)
109 {
110 	int32* data = (int32*)block;
111 	*data = value;
112 }
113 
114 
115 void
116 reset_block(void* block, int32 index)
117 {
118 	int32* data = (int32*)block;
119 	*data = index + 1;
120 }
121 
122 
123 ssize_t
124 block_cache_write_pos(int fd, off_t offset, const void* buffer, size_t size)
125 {
126 	int32 index = offset / gBlockSize;
127 
128 	gBlocks[index].written = true;
129 	if (!gBlocks[index].write)
130 		error(__LINE__, "Block %ld should not be written!\n", index);
131 
132 	return size;
133 }
134 
135 
136 ssize_t
137 block_cache_read_pos(int fd, off_t offset, void* buffer, size_t size)
138 {
139 	int32 index = offset / gBlockSize;
140 
141 	memset(buffer, 0xcc, size);
142 	reset_block(buffer, index);
143 	if (!gBlocks[index].read)
144 		error(__LINE__, "Block %ld should not be read!\n", index);
145 
146 	return size;
147 }
148 
149 
150 void
151 init_test_blocks()
152 {
153 	memset(gBlocks, 0, sizeof(test_block) * MAX_BLOCKS);
154 
155 	for (uint32 i = 0; i < MAX_BLOCKS; i++) {
156 		gBlocks[i].current = i + 1;
157 		gBlocks[i].unused = true;
158 	}
159 }
160 
161 
162 void
163 test_transaction(int32 id, int32 numBlocks, int32 numMainBlocks,
164 	int32 numSubBlocks, int32 line)
165 {
166 	MutexLocker locker(&gCache->lock);
167 	cache_transaction* transaction = lookup_transaction(gCache, id);
168 
169 	if (numBlocks != transaction->num_blocks) {
170 		error(line, "Transaction %d has wrong num_blocks (is %d, should be "
171 			"%d)!", id, transaction->num_blocks, numBlocks);
172 	}
173 	if (numMainBlocks != transaction->main_num_blocks) {
174 		error(line, "Transaction %d has wrong num_blocks (is %d, should be "
175 			"%d)!", id, transaction->main_num_blocks, numMainBlocks);
176 	}
177 	if (numSubBlocks != transaction->sub_num_blocks) {
178 		error(line, "Transaction %d has wrong num_blocks (is %d, should be "
179 			"%d)!", id, transaction->sub_num_blocks, numSubBlocks);
180 	}
181 }
182 
183 
184 void
185 test_blocks(off_t number, int32 count, int32 line)
186 {
187 	printf("  %ld\n", gSubTest++);
188 
189 	for (int32 i = 0; i < count; i++, number++) {
190 		MutexLocker locker(&gCache->lock);
191 
192 		cached_block* block = (cached_block*)hash_lookup(gCache->hash, &number);
193 		if (block == NULL) {
194 			if (gBlocks[number].present)
195 				error(line, "Block %lld not found!", number);
196 			continue;
197 		}
198 		if (!gBlocks[number].present)
199 			error(line, "Block %lld is present, but should not!", number);
200 
201 		if (block->is_dirty != gBlocks[number].is_dirty) {
202 			error(line, "Block %lld: dirty bit differs (is %d should be %d)!",
203 				number, block->is_dirty, gBlocks[number].is_dirty);
204 		}
205 #if 0
206 		if (block->unused != gBlocks[number].unused) {
207 			error("Block %ld: unused bit differs (%d should be %d)!", number,
208 				block->unused, gBlocks[number].unused);
209 		}
210 #endif
211 		if (block->discard != gBlocks[number].discard) {
212 			error(line, "Block %lld: discard bit differs (is %d should be %d)!",
213 				number, block->discard, gBlocks[number].discard);
214 		}
215 		if (gBlocks[number].write && !gBlocks[number].written)
216 			error(line, "Block %lld: has not been written yet!", number);
217 
218 		TEST_BLOCK_DATA(block, number, current);
219 		TEST_BLOCK_DATA(block, number, original);
220 		TEST_BLOCK_DATA(block, number, parent);
221 	}
222 }
223 
224 
225 void
226 stop_test(void)
227 {
228 	if (gCache == NULL)
229 		return;
230 	TEST_BLOCKS(0, MAX_BLOCKS);
231 
232 //	dump_cache();
233 	block_cache_delete(gCache, true);
234 }
235 
236 
237 void
238 start_test(const char* name, bool init = true)
239 {
240 	if (init) {
241 		stop_test();
242 
243 		gBlockSize = 2048;
244 		gCache = (block_cache*)block_cache_create(-1, MAX_BLOCKS, gBlockSize,
245 			false);
246 
247 		init_test_blocks();
248 	}
249 
250 	gTest++;
251 	gTestName = name;
252 	gSubTest = 1;
253 
254 	printf("----------- Test %ld%s%s -----------\n", gTest,
255 		gTestName[0] ? " - " : "", gTestName);
256 }
257 
258 
259 /*!	Changes block 1 in main, block 2 if touchedInMain is \c true.
260 	Changes block 0 in sub, discards block 2.
261 	Performs two block tests.
262 */
263 void
264 basic_test_discard_in_sub(int32 id, bool touchedInMain)
265 {
266 	gBlocks[1].present = true;
267 	gBlocks[1].read = true;
268 	gBlocks[1].is_dirty = true;
269 	gBlocks[1].original = gBlocks[1].current;
270 	gBlocks[1].current |= BLOCK_CHANGED_IN_MAIN;
271 
272 	void* block = block_cache_get_writable(gCache, 1, id);
273 	or_block(block, BLOCK_CHANGED_IN_MAIN);
274 	block_cache_put(gCache, 1);
275 
276 	TEST_BLOCKS(0, 2);
277 
278 	if (touchedInMain) {
279 		gBlocks[2].present = true;
280 		gBlocks[2].is_dirty = true;
281 		gBlocks[2].current |= BLOCK_CHANGED_IN_MAIN;
282 
283 		block = block_cache_get_empty(gCache, 2, id);
284 		reset_block(block, 2);
285 		or_block(block, BLOCK_CHANGED_IN_MAIN);
286 		block_cache_put(gCache, 2);
287 	}
288 
289 	cache_start_sub_transaction(gCache, id);
290 
291 	gBlocks[0].present = true;
292 	gBlocks[0].read = true;
293 	gBlocks[0].is_dirty = true;
294 	if ((gBlocks[0].current & BLOCK_CHANGED_IN_MAIN) != 0)
295 		gBlocks[0].parent = gBlocks[0].current;
296 	else
297 		gBlocks[0].original = gBlocks[0].current;
298 	gBlocks[0].current |= BLOCK_CHANGED_IN_SUB;
299 
300 	gBlocks[1].parent = gBlocks[1].current;
301 	if (touchedInMain)
302 		gBlocks[2].parent = gBlocks[2].current;
303 
304 	block = block_cache_get_writable(gCache, 0, id);
305 	or_block(block, BLOCK_CHANGED_IN_SUB);
306 	block_cache_put(gCache, 0);
307 
308 	gBlocks[2].discard = true;
309 
310 	block_cache_discard(gCache, 2, 1);
311 
312 	TEST_BLOCKS(0, 2);
313 
314 	gBlocks[0].is_dirty = false;
315 	gBlocks[0].write = true;
316 	gBlocks[1].is_dirty = false;
317 	gBlocks[1].write = true;
318 	gBlocks[2].present = false;
319 	gBlocks[2].write = touchedInMain;
320 	gBlocks[2].is_dirty = false;
321 }
322 
323 
324 // #pragma mark - Tests
325 
326 
327 void
328 test_abort_transaction()
329 {
330 	start_test("Abort main");
331 
332 	int32 id = cache_start_transaction(gCache);
333 
334 	gBlocks[0].present = true;
335 	gBlocks[0].read = false;
336 	gBlocks[0].write = false;
337 	gBlocks[0].is_dirty = true;
338 	gBlocks[1].present = true;
339 	gBlocks[1].read = true;
340 	gBlocks[1].write = false;
341 	gBlocks[1].is_dirty = true;
342 
343 	void* block = block_cache_get_empty(gCache, 0, id);
344 	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
345 	gBlocks[0].current = BLOCK_CHANGED_IN_PREVIOUS;
346 
347 	block = block_cache_get_writable(gCache, 1, id);
348 	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
349 	gBlocks[1].original = gBlocks[1].current;
350 	gBlocks[1].current |= BLOCK_CHANGED_IN_PREVIOUS;
351 
352 	block_cache_put(gCache, 0);
353 	block_cache_put(gCache, 1);
354 
355 	cache_end_transaction(gCache, id, NULL, NULL);
356 	TEST_BLOCKS(0, 2);
357 
358 	id = cache_start_transaction(gCache);
359 
360 	block = block_cache_get_writable(gCache, 0, id);
361 	or_block(block, BLOCK_CHANGED_IN_MAIN);
362 
363 	block = block_cache_get_writable(gCache, 1, id);
364 	or_block(block, BLOCK_CHANGED_IN_MAIN);
365 
366 	block_cache_put(gCache, 0);
367 	block_cache_put(gCache, 1);
368 
369 	cache_abort_transaction(gCache, id);
370 
371 	gBlocks[0].write = true;
372 	gBlocks[0].is_dirty = false;
373 	gBlocks[1].write = true;
374 	gBlocks[1].is_dirty = false;
375 	cache_sync_transaction(gCache, id);
376 }
377 
378 
379 void
380 test_abort_sub_transaction()
381 {
382 	start_test("Abort sub");
383 
384 	int32 id = cache_start_transaction(gCache);
385 
386 	gBlocks[0].present = true;
387 	gBlocks[0].read = false;
388 	gBlocks[0].write = false;
389 	gBlocks[0].is_dirty = true;
390 	gBlocks[1].present = true;
391 	gBlocks[1].read = true;
392 	gBlocks[1].write = false;
393 	gBlocks[1].is_dirty = true;
394 
395 	void* block = block_cache_get_empty(gCache, 0, id);
396 	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
397 	gBlocks[0].current = BLOCK_CHANGED_IN_PREVIOUS;
398 
399 	block = block_cache_get_writable(gCache, 1, id);
400 	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
401 	gBlocks[1].original = gBlocks[1].current;
402 	gBlocks[1].current |= BLOCK_CHANGED_IN_PREVIOUS;
403 
404 	block_cache_put(gCache, 0);
405 	block_cache_put(gCache, 1);
406 
407 	cache_start_sub_transaction(gCache, id);
408 
409 	gBlocks[0].parent = gBlocks[0].current;
410 	gBlocks[1].parent = gBlocks[1].current;
411 	TEST_BLOCKS(0, 2);
412 
413 	block = block_cache_get_writable(gCache, 1, id);
414 	or_block(block, BLOCK_CHANGED_IN_MAIN);
415 
416 	block_cache_put(gCache, 1);
417 
418 	TEST_TRANSACTION(id, 2, 2, 1);
419 	cache_abort_sub_transaction(gCache, id);
420 	TEST_TRANSACTION(id, 2, 2, 0);
421 
422 	gBlocks[0].write = true;
423 	gBlocks[0].is_dirty = false;
424 	gBlocks[1].write = true;
425 	gBlocks[1].is_dirty = false;
426 
427 	cache_end_transaction(gCache, id, NULL, NULL);
428 	cache_sync_transaction(gCache, id);
429 
430 	start_test("Abort sub with empty block");
431 	id = cache_start_transaction(gCache);
432 
433 	gBlocks[1].present = true;
434 	gBlocks[1].read = true;
435 
436 	block = block_cache_get_writable(gCache, 1, id);
437 	or_block(block, BLOCK_CHANGED_IN_PREVIOUS);
438 	gBlocks[1].original = gBlocks[1].current;
439 	gBlocks[1].current |= BLOCK_CHANGED_IN_PREVIOUS;
440 
441 	block_cache_put(gCache, 1);
442 
443 	gBlocks[1].is_dirty = true;
444 	TEST_BLOCKS(1, 1);
445 
446 	TEST_TRANSACTION(id, 1, 0, 0);
447 	cache_start_sub_transaction(gCache, id);
448 	TEST_TRANSACTION(id, 1, 1, 0);
449 
450 	gBlocks[0].present = true;
451 
452 	block = block_cache_get_empty(gCache, 0, id);
453 	or_block(block, BLOCK_CHANGED_IN_SUB);
454 	gBlocks[0].current = BLOCK_CHANGED_IN_SUB;
455 
456 	block_cache_put(gCache, 0);
457 
458 	TEST_TRANSACTION(id, 2, 1, 1);
459 	cache_abort_sub_transaction(gCache, id);
460 	TEST_TRANSACTION(id, 1, 1, 0);
461 
462 	gBlocks[0].write = false;
463 	gBlocks[0].is_dirty = false;
464 	gBlocks[0].parent = 0;
465 	gBlocks[0].original = 0;
466 	TEST_BLOCKS(0, 1);
467 
468 	gBlocks[1].write = true;
469 	gBlocks[1].is_dirty = false;
470 	cache_end_transaction(gCache, id, NULL, NULL);
471 	cache_sync_transaction(gCache, id);
472 }
473 
474 
475 void
476 test_block_cache_discard()
477 {
478 	// Test transactions and block caches
479 
480 	start_test("Discard in main");
481 
482 	int32 id = cache_start_transaction(gCache);
483 
484 	gBlocks[0].present = true;
485 	gBlocks[0].read = true;
486 
487 	block_cache_get(gCache, 0);
488 	block_cache_put(gCache, 0);
489 
490 	gBlocks[1].present = true;
491 	gBlocks[1].read = true;
492 	gBlocks[1].write = true;
493 
494 	void* block = block_cache_get_writable(gCache, 1, id);
495 	block_cache_put(gCache, 1);
496 
497 	gBlocks[2].present = false;
498 
499 	TEST_TRANSACTION(id, 1, 0, 0);
500 	block = block_cache_get_empty(gCache, 2, id);
501 	TEST_TRANSACTION(id, 2, 0, 0);
502 	block_cache_discard(gCache, 2, 1);
503 	block_cache_put(gCache, 2);
504 
505 	cache_end_transaction(gCache, id, NULL, NULL);
506 	TEST_TRANSACTION(id, 1, 0, 0);
507 	cache_sync_transaction(gCache, id);
508 
509 	start_test("Discard in sub");
510 
511 	id = cache_start_transaction(gCache);
512 
513 	basic_test_discard_in_sub(id, false);
514 	TEST_ASSERT(cache_blocks_in_sub_transaction(gCache, id) == 1);
515 
516 	cache_end_transaction(gCache, id, NULL, NULL);
517 	cache_sync_transaction(gCache, id);
518 
519 	gBlocks[0].is_dirty = false;
520 	gBlocks[1].is_dirty = false;
521 
522 	start_test("Discard in sub, present in main");
523 
524 	id = cache_start_transaction(gCache);
525 
526 	basic_test_discard_in_sub(id, true);
527 
528 	cache_end_transaction(gCache, id, NULL, NULL);
529 	cache_sync_transaction(gCache, id);
530 
531 	gBlocks[0].is_dirty = false;
532 	gBlocks[1].is_dirty = false;
533 
534 	start_test("Discard in sub, changed in main, abort sub");
535 
536 	id = cache_start_transaction(gCache);
537 
538 	basic_test_discard_in_sub(id, true);
539 	TEST_ASSERT(cache_blocks_in_sub_transaction(gCache, id) == 1);
540 
541 	gBlocks[0].current &= ~BLOCK_CHANGED_IN_SUB;
542 	gBlocks[0].is_dirty = false;
543 	gBlocks[0].write = false;
544 	gBlocks[1].write = false;
545 	gBlocks[1].is_dirty = true;
546 	gBlocks[2].present = true;
547 	gBlocks[2].is_dirty = true;
548 	gBlocks[2].write = false;
549 	gBlocks[2].discard = false;
550 	cache_abort_sub_transaction(gCache, id);
551 	TEST_BLOCKS(0, 2);
552 
553 	gBlocks[1].is_dirty = false;
554 	gBlocks[1].write = true;
555 	gBlocks[2].is_dirty = false;
556 	gBlocks[2].write = true;
557 	cache_end_transaction(gCache, id, NULL, NULL);
558 	cache_sync_transaction(gCache, id);
559 
560 	start_test("Discard in sub, changed in main, new sub");
561 
562 	id = cache_start_transaction(gCache);
563 
564 	basic_test_discard_in_sub(id, true);
565 
566 	cache_start_sub_transaction(gCache, id);
567 	cache_end_transaction(gCache, id, NULL, NULL);
568 	cache_sync_transaction(gCache, id);
569 
570 	start_test("Discard in sub, changed in main, detach sub");
571 
572 	id = cache_start_transaction(gCache);
573 
574 	basic_test_discard_in_sub(id, true);
575 
576 	id = cache_detach_sub_transaction(gCache, id, NULL, NULL);
577 
578 	gBlocks[0].is_dirty = true;
579 	gBlocks[0].write = false;
580 	gBlocks[1].is_dirty = true;
581 	gBlocks[1].write = false;
582 
583 	TEST_BLOCKS(0, 2);
584 
585 	gBlocks[0].is_dirty = false;
586 	gBlocks[0].write = true;
587 	gBlocks[1].is_dirty = false;
588 	gBlocks[1].write = true;
589 
590 	cache_end_transaction(gCache, id, NULL, NULL);
591 	cache_sync_transaction(gCache, id);
592 
593 	start_test("Discard in sub, all changed in main, detach sub");
594 
595 	id = cache_start_transaction(gCache);
596 
597 	gBlocks[0].present = true;
598 	gBlocks[0].read = true;
599 	gBlocks[0].is_dirty = true;
600 	gBlocks[0].original = gBlocks[0].current;
601 	gBlocks[0].current |= BLOCK_CHANGED_IN_MAIN;
602 
603 	block = block_cache_get_writable(gCache, 0, id);
604 	or_block(block, BLOCK_CHANGED_IN_MAIN);
605 	block_cache_put(gCache, 0);
606 
607 	basic_test_discard_in_sub(id, true);
608 
609 	id = cache_detach_sub_transaction(gCache, id, NULL, NULL);
610 
611 	gBlocks[0].is_dirty = true;
612 	gBlocks[0].write = false;
613 	gBlocks[0].original |= BLOCK_CHANGED_IN_MAIN;
614 	gBlocks[1].is_dirty = true;
615 	gBlocks[1].write = false;
616 
617 	TEST_BLOCKS(0, 2);
618 
619 	gBlocks[0].is_dirty = false;
620 	gBlocks[0].write = true;
621 	gBlocks[1].is_dirty = false;
622 	gBlocks[1].write = true;
623 
624 	cache_end_transaction(gCache, id, NULL, NULL);
625 	cache_sync_transaction(gCache, id);
626 
627 	stop_test();
628 }
629 
630 
631 // #pragma mark -
632 
633 
634 int
635 main(int argc, char** argv)
636 {
637 	block_cache_init();
638 
639 	// TODO: test transaction-less block caches
640 	// TODO: test read-only block caches
641 	test_abort_transaction();
642 	test_abort_sub_transaction();
643 	test_block_cache_discard();
644 	return 0;
645 }
646