master
c 1,235 lines 42.7 KB
Raw
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 #include "qemu/osdep.h"
3 #include "qemu/bitmap.h"
4 #include "qemu/module.h"
5 #include "qemu/main-loop.h"
6 #include "qapi/error.h"
7 #include "qom/object.h"
8 #include "qom/qom-qobject.h"
9 #include "glib.h"
10 #include "system/memory.h"
11
12 #define TYPE_TEST_RAM_DISCARD_SOURCE "test-ram-discard-source"
13
14 OBJECT_DECLARE_SIMPLE_TYPE(TestRamDiscardSource, TEST_RAM_DISCARD_SOURCE)
15
16 struct TestRamDiscardSource {
17 Object parent;
18
19 MemoryRegion *mr;
20 uint64_t granularity;
21 unsigned long *bitmap;
22 uint64_t bitmap_size;
23 };
24
25 static uint64_t test_rds_get_min_granularity(const RamDiscardSource *rds,
26 const MemoryRegion *mr)
27 {
28 TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds);
29
30 g_assert(mr == src->mr);
31 return src->granularity;
32 }
33
34 static bool test_rds_is_populated(const RamDiscardSource *rds,
35 const MemoryRegionSection *section)
36 {
37 TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds);
38 uint64_t offset = section->offset_within_region;
39 uint64_t size = int128_get64(section->size);
40 uint64_t first_bit = offset / src->granularity;
41 uint64_t last_bit = (offset + size - 1) / src->granularity;
42 unsigned long found;
43
44 g_assert(section->mr == src->mr);
45
46 /* Check if any bit in range is zero (discarded) */
47 found = find_next_zero_bit(src->bitmap, last_bit + 1, first_bit);
48 return found > last_bit;
49 }
50
51 static void test_rds_class_init(ObjectClass *klass, const void *data)
52 {
53 RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass);
54
55 rdsc->get_min_granularity = test_rds_get_min_granularity;
56 rdsc->is_populated = test_rds_is_populated;
57 }
58
59 static const TypeInfo test_rds_info = {
60 .name = TYPE_TEST_RAM_DISCARD_SOURCE,
61 .parent = TYPE_OBJECT,
62 .instance_size = sizeof(TestRamDiscardSource),
63 .class_init = test_rds_class_init,
64 .interfaces = (const InterfaceInfo[]) {
65 { TYPE_RAM_DISCARD_SOURCE },
66 { }
67 },
68 };
69
70 static TestRamDiscardSource *test_source_new(MemoryRegion *mr,
71 uint64_t granularity)
72 {
73 TestRamDiscardSource *src;
74 uint64_t region_size = memory_region_size(mr);
75
76 src = TEST_RAM_DISCARD_SOURCE(object_new(TYPE_TEST_RAM_DISCARD_SOURCE));
77 src->mr = mr;
78 src->granularity = granularity;
79 src->bitmap_size = DIV_ROUND_UP(region_size, granularity);
80 src->bitmap = bitmap_new(src->bitmap_size);
81
82 return src;
83 }
84
85 static void test_source_free(TestRamDiscardSource *src)
86 {
87 g_free(src->bitmap);
88 object_unref(OBJECT(src));
89 }
90
91 static void test_source_populate(TestRamDiscardSource *src,
92 uint64_t offset, uint64_t size)
93 {
94 uint64_t first_bit = offset / src->granularity;
95 uint64_t nbits = size / src->granularity;
96
97 bitmap_set(src->bitmap, first_bit, nbits);
98 }
99
100 static void test_source_discard(TestRamDiscardSource *src,
101 uint64_t offset, uint64_t size)
102 {
103 uint64_t first_bit = offset / src->granularity;
104 uint64_t nbits = size / src->granularity;
105
106 bitmap_clear(src->bitmap, first_bit, nbits);
107 }
108
109 typedef struct TestListener {
110 RamDiscardListener rdl;
111 int populate_count;
112 int discard_count;
113 uint64_t last_populate_offset;
114 uint64_t last_populate_size;
115 uint64_t last_discard_offset;
116 uint64_t last_discard_size;
117 int fail_on_populate; /* Return error on Nth populate */
118 int populate_call_num;
119 } TestListener;
120
121 static int test_listener_populate(RamDiscardListener *rdl,
122 const MemoryRegionSection *section)
123 {
124 TestListener *tl = container_of(rdl, TestListener, rdl);
125
126 tl->populate_call_num++;
127 if (tl->fail_on_populate > 0 &&
128 tl->populate_call_num >= tl->fail_on_populate) {
129 return -ENOMEM;
130 }
131
132 tl->populate_count++;
133 tl->last_populate_offset = section->offset_within_region;
134 tl->last_populate_size = int128_get64(section->size);
135 return 0;
136 }
137
138 static void test_listener_discard(RamDiscardListener *rdl,
139 const MemoryRegionSection *section)
140 {
141 TestListener *tl = container_of(rdl, TestListener, rdl);
142
143 tl->discard_count++;
144 tl->last_discard_offset = section->offset_within_region;
145 tl->last_discard_size = int128_get64(section->size);
146 }
147
148 static void test_listener_init(TestListener *tl)
149 {
150 ram_discard_listener_init(&tl->rdl,
151 test_listener_populate,
152 test_listener_discard);
153 }
154
155 #define TEST_REGION_SIZE (16 * 1024 * 1024) /* 16 MB */
156 #define GRANULARITY_4K (4 * 1024)
157 #define GRANULARITY_2M (2 * 1024 * 1024)
158
159 static MemoryRegion *test_mr;
160
161 static void test_setup(void)
162 {
163 test_mr = g_new0(MemoryRegion, 1);
164 test_mr->size = int128_make64(TEST_REGION_SIZE);
165 test_mr->ram = true;
166 }
167
168 static void test_teardown(void)
169 {
170 g_clear_pointer(&test_mr->rdm, object_unref);
171 object_unparent(OBJECT(test_mr));
172 g_free(test_mr);
173 test_mr = NULL;
174 }
175
176 static void test_single_source_basic(void)
177 {
178 TestRamDiscardSource *src;
179 RamDiscardManager *rdm;
180 MemoryRegionSection section;
181 int ret;
182
183 test_setup();
184
185 src = test_source_new(test_mr, GRANULARITY_4K);
186 rdm = memory_region_get_ram_discard_manager(test_mr);
187 g_assert_null(rdm);
188
189 /* Add source */
190 ret = memory_region_add_ram_discard_source(test_mr,
191 RAM_DISCARD_SOURCE(src));
192 g_assert_cmpint(ret, ==, 0);
193
194 rdm = memory_region_get_ram_discard_manager(test_mr);
195 g_assert_nonnull(rdm);
196
197 g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr),
198 ==, GRANULARITY_4K);
199
200 /* Initially all discarded */
201 section.mr = test_mr;
202 section.offset_within_region = 0;
203 section.size = int128_make64(GRANULARITY_4K);
204 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
205
206 /* Populate a range in source */
207 test_source_populate(src, 0, GRANULARITY_4K * 4);
208
209 /* Now should be populated */
210 g_assert_true(ram_discard_manager_is_populated(rdm, &section));
211
212 /* Check larger section */
213 section.size = int128_make64(GRANULARITY_4K * 4);
214 g_assert_true(ram_discard_manager_is_populated(rdm, &section));
215
216 /* Check section that spans populated and discarded */
217 section.size = int128_make64(GRANULARITY_4K * 8);
218 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
219
220 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
221
222 g_assert_true(ram_discard_manager_is_populated(rdm, &section));
223
224 test_source_free(src);
225 test_teardown();
226 }
227
228 static void test_single_source_listener(void)
229 {
230 TestRamDiscardSource *src;
231 RamDiscardManager *rdm;
232 MemoryRegionSection section;
233 TestListener tl = { 0, };
234 int ret;
235
236 test_setup();
237
238 src = test_source_new(test_mr, GRANULARITY_4K);
239
240 /* Populate some ranges before adding listener */
241 test_source_populate(src, 0, GRANULARITY_4K * 4);
242 test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4);
243
244 ret = memory_region_add_ram_discard_source(test_mr,
245 RAM_DISCARD_SOURCE(src));
246 g_assert_cmpint(ret, ==, 0);
247 rdm = memory_region_get_ram_discard_manager(test_mr);
248 g_assert_nonnull(rdm);
249
250 /* Register listener */
251 test_listener_init(&tl);
252 section.mr = test_mr;
253 section.offset_within_region = 0;
254 section.size = int128_make64(TEST_REGION_SIZE);
255
256 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
257
258 /* Should have been notified about populated regions */
259 g_assert_cmpint(tl.populate_count, ==, 2);
260
261 /* Notify populate for new range */
262 tl.populate_count = 0;
263 test_source_populate(src, GRANULARITY_4K * 16, GRANULARITY_4K * 2);
264 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
265 GRANULARITY_4K * 16,
266 GRANULARITY_4K * 2);
267 g_assert_cmpint(ret, ==, 0);
268 g_assert_cmpint(tl.populate_count, ==, 1);
269 g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 16);
270 g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 2);
271
272 /* Notify discard */
273 tl.discard_count = 0;
274 test_source_discard(src, 0, GRANULARITY_4K * 4);
275 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
276 0, GRANULARITY_4K * 4);
277 g_assert_cmpint(tl.discard_count, ==, 1);
278 g_assert_cmpuint(tl.last_discard_offset, ==, 0);
279 g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K * 4);
280
281 /* Unregister listener */
282 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
283
284 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
285 test_source_free(src);
286 test_teardown();
287 }
288
289 static void test_two_sources_same_granularity(void)
290 {
291 TestRamDiscardSource *src1, *src2;
292 RamDiscardManager *rdm;
293 MemoryRegionSection section;
294 int ret;
295
296 test_setup();
297
298 src1 = test_source_new(test_mr, GRANULARITY_4K);
299 src2 = test_source_new(test_mr, GRANULARITY_4K);
300
301 /* Add first source */
302 ret = memory_region_add_ram_discard_source(test_mr,
303 RAM_DISCARD_SOURCE(src1));
304 g_assert_cmpint(ret, ==, 0);
305
306 /* Add second source */
307 ret = memory_region_add_ram_discard_source(test_mr,
308 RAM_DISCARD_SOURCE(src2));
309 g_assert_cmpint(ret, ==, 0);
310
311 rdm = memory_region_get_ram_discard_manager(test_mr);
312 g_assert_nonnull(rdm);
313
314 /* Check granularity */
315 g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr),
316 ==, GRANULARITY_4K);
317
318 section.mr = test_mr;
319 section.offset_within_region = 0;
320 section.size = int128_make64(GRANULARITY_4K);
321
322 /* Both discarded -> aggregated discarded */
323 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
324
325 /* Populate in src1 only */
326 test_source_populate(src1, 0, GRANULARITY_4K);
327 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
328
329 /* Populate in src2 only */
330 test_source_discard(src1, 0, GRANULARITY_4K);
331 test_source_populate(src2, 0, GRANULARITY_4K);
332 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
333
334 /* Populate in both -> aggregated populated */
335 test_source_populate(src1, 0, GRANULARITY_4K);
336 g_assert_true(ram_discard_manager_is_populated(rdm, &section));
337
338 /* Remove sources */
339 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
340 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
341
342 test_source_free(src2);
343 test_source_free(src1);
344 test_teardown();
345 }
346
347 /*
348 * Test: Two sources with different granularities (4K and 2M).
349 * The aggregated granularity should be GCD(4K, 2M) = 4K.
350 */
351 static void test_two_sources_different_granularity(void)
352 {
353 TestRamDiscardSource *src_4k, *src_2m;
354 RamDiscardManager *rdm;
355 MemoryRegionSection section;
356 int ret;
357
358 test_setup();
359
360 src_4k = test_source_new(test_mr, GRANULARITY_4K);
361 src_2m = test_source_new(test_mr, GRANULARITY_2M);
362
363 ret = memory_region_add_ram_discard_source(test_mr,
364 RAM_DISCARD_SOURCE(src_4k));
365 g_assert_cmpint(ret, ==, 0);
366
367 ret = memory_region_add_ram_discard_source(test_mr,
368 RAM_DISCARD_SOURCE(src_2m));
369 g_assert_cmpint(ret, ==, 0);
370
371 rdm = memory_region_get_ram_discard_manager(test_mr);
372
373 g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr),
374 ==, GRANULARITY_4K);
375
376 section.mr = test_mr;
377 section.offset_within_region = 0;
378 section.size = int128_make64(GRANULARITY_4K);
379
380 /* Both discarded */
381 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
382
383 /* Populate 4K in src_4k, but src_2m still discarded the whole 2M block */
384 test_source_populate(src_4k, 0, GRANULARITY_4K);
385 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
386
387 /* Populate 2M in src_2m (which includes the 4K block) */
388 test_source_populate(src_2m, 0, GRANULARITY_2M);
389 g_assert_true(ram_discard_manager_is_populated(rdm, &section));
390
391 /* Check a 4K block at offset 4K (populated in src_2m but not in src_4k) */
392 section.offset_within_region = GRANULARITY_4K;
393 g_assert_false(ram_discard_manager_is_populated(rdm, &section));
394
395 /* Populate it in src_4k */
396 test_source_populate(src_4k, GRANULARITY_4K, GRANULARITY_4K);
397 g_assert_true(ram_discard_manager_is_populated(rdm, &section));
398
399 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_2m));
400 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_4k));
401
402 test_source_free(src_2m);
403 test_source_free(src_4k);
404 test_teardown();
405 }
406
407 /*
408 * Test: Notification with two sources.
409 * Populate notification should only fire when all sources are populated.
410 */
411 static void test_two_sources_notification(void)
412 {
413 TestRamDiscardSource *src1, *src2;
414 RamDiscardManager *rdm;
415 MemoryRegionSection section;
416 TestListener tl = { 0, };
417 int ret;
418
419 test_setup();
420
421 src1 = test_source_new(test_mr, GRANULARITY_4K);
422 src2 = test_source_new(test_mr, GRANULARITY_4K);
423
424 ret = memory_region_add_ram_discard_source(test_mr,
425 RAM_DISCARD_SOURCE(src1));
426 g_assert_cmpint(ret, ==, 0);
427 ret = memory_region_add_ram_discard_source(test_mr,
428 RAM_DISCARD_SOURCE(src2));
429 g_assert_cmpint(ret, ==, 0);
430
431 rdm = memory_region_get_ram_discard_manager(test_mr);
432
433 /* Register listener */
434 test_listener_init(&tl);
435 section.mr = test_mr;
436 section.offset_within_region = 0;
437 section.size = int128_make64(TEST_REGION_SIZE);
438 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
439
440 /* No populate notifications yet (all discarded) */
441 g_assert_cmpint(tl.populate_count, ==, 0);
442
443 /* Populate in src1 only - no notification (src2 still discarded) */
444 test_source_populate(src1, 0, GRANULARITY_4K * 4);
445 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1),
446 0, GRANULARITY_4K * 4);
447 g_assert_cmpint(ret, ==, 0);
448 g_assert_cmpint(tl.populate_count, ==, 0);
449
450 /* Populate same range in src2 - now should notify */
451 test_source_populate(src2, 0, GRANULARITY_4K * 4);
452 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src2),
453 0, GRANULARITY_4K * 4);
454 g_assert_cmpint(ret, ==, 0);
455 g_assert_cmpint(tl.populate_count, ==, 1);
456
457 /* Discard from src1 - should notify discard immediately */
458 tl.discard_count = 0;
459 test_source_discard(src1, 0, GRANULARITY_4K * 2);
460 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1),
461 0, GRANULARITY_4K * 2);
462 g_assert_cmpint(tl.discard_count, ==, 1);
463
464 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
465 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
466 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
467
468 test_source_free(src2);
469 test_source_free(src1);
470 test_teardown();
471 }
472
473 /*
474 * Test: Adding source with existing listener.
475 * When a new source is added, listeners should be notified about
476 * regions that become discarded.
477 */
478 static void test_add_source_with_listener(void)
479 {
480 TestRamDiscardSource *src1, *src2;
481 RamDiscardManager *rdm;
482 MemoryRegionSection section;
483 TestListener tl = { 0, };
484 int ret;
485
486 test_setup();
487
488 src1 = test_source_new(test_mr, GRANULARITY_4K);
489 src2 = test_source_new(test_mr, GRANULARITY_4K);
490
491 /* Populate some range in src1 */
492 test_source_populate(src1, 0, GRANULARITY_4K * 8);
493
494 ret = memory_region_add_ram_discard_source(test_mr,
495 RAM_DISCARD_SOURCE(src1));
496 g_assert_cmpint(ret, ==, 0);
497 rdm = memory_region_get_ram_discard_manager(test_mr);
498
499 /* Register listener */
500 test_listener_init(&tl);
501 section.mr = test_mr;
502 section.offset_within_region = 0;
503 section.size = int128_make64(TEST_REGION_SIZE);
504 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
505
506 /* Should have been notified about populated region */
507 g_assert_cmpint(tl.populate_count, ==, 1);
508 g_assert_cmpint(tl.last_populate_offset, ==, 0);
509 g_assert_cmpint(tl.last_populate_size, ==, GRANULARITY_4K * 8);
510
511 /* src2 has part of the region populated, part discarded */
512 /* src2 has 0-4 populated, 4-8 discarded */
513 test_source_populate(src2, 0, GRANULARITY_4K * 4);
514
515 /* Add src2 - listener should be notified about newly discarded regions */
516 tl.discard_count = 0;
517 ret = memory_region_add_ram_discard_source(test_mr,
518 RAM_DISCARD_SOURCE(src2));
519 g_assert_cmpint(ret, ==, 0);
520
521 /*
522 * The range 4K*4 to 4K*8 was populated in src1 but discarded in src2,
523 * so it becomes aggregated-discarded. Listener should be notified.
524 * Only this range should trigger a discard notification - regions beyond
525 * 4K*8 were already discarded in src1, so adding src2 doesn't change them.
526 */
527 g_assert_cmpint(tl.discard_count, ==, 1);
528 g_assert_cmpint(tl.last_discard_offset, ==, GRANULARITY_4K * 4);
529 g_assert_cmpint(tl.last_discard_size, ==, GRANULARITY_4K * 4);
530
531 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
532 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
533 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
534
535 test_source_free(src2);
536 test_source_free(src1);
537 test_teardown();
538 }
539
540 /*
541 * Test: Removing source with existing listener.
542 * When a source is removed, listeners should be notified about
543 * regions that become populated.
544 */
545 static void test_remove_source_with_listener(void)
546 {
547 TestRamDiscardSource *src1, *src2;
548 RamDiscardManager *rdm;
549 MemoryRegionSection section;
550 TestListener tl = { 0, };
551 int ret;
552
553 test_setup();
554
555 src1 = test_source_new(test_mr, GRANULARITY_4K);
556 src2 = test_source_new(test_mr, GRANULARITY_4K);
557
558 /* src1: all of first 8 blocks populated */
559 test_source_populate(src1, 0, GRANULARITY_4K * 8);
560 /* src2: only first 4 blocks populated */
561 test_source_populate(src2, 0, GRANULARITY_4K * 4);
562
563 ret = memory_region_add_ram_discard_source(test_mr,
564 RAM_DISCARD_SOURCE(src1));
565 g_assert_cmpint(ret, ==, 0);
566 ret = memory_region_add_ram_discard_source(test_mr,
567 RAM_DISCARD_SOURCE(src2));
568 g_assert_cmpint(ret, ==, 0);
569
570 rdm = memory_region_get_ram_discard_manager(test_mr);
571
572 /* Register listener */
573 test_listener_init(&tl);
574 section.mr = test_mr;
575 section.offset_within_region = 0;
576 section.size = int128_make64(TEST_REGION_SIZE);
577 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
578
579 /* Only first 4 blocks are aggregated-populated */
580 g_assert_cmpint(tl.populate_count, ==, 1);
581 g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
582
583 /* Remove src2 - blocks 4-8 should become populated */
584 tl.populate_count = 0;
585 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
586
587 /* Listener should be notified about newly populated region (4K*4 to 4K*8) */
588 g_assert_cmpint(tl.populate_count, >=, 1);
589
590 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
591 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
592
593 test_source_free(src2);
594 test_source_free(src1);
595 test_teardown();
596 }
597
598 /*
599 * Test: Add a source, register a listener, remove the source, then add it back.
600 * This checks the transition from 0 sources (all populated) to 1 source
601 * (partially discarded) with an active listener.
602 */
603 static void test_readd_source_with_listener(void)
604 {
605 TestRamDiscardSource *src;
606 RamDiscardManager *rdm;
607 MemoryRegionSection section;
608 TestListener tl = { 0, };
609 int ret;
610
611 test_setup();
612
613 src = test_source_new(test_mr, GRANULARITY_4K);
614
615 /* Populate some range in src */
616 test_source_populate(src, 0, GRANULARITY_4K * 8);
617
618 /* 1. Add source */
619 ret = memory_region_add_ram_discard_source(test_mr,
620 RAM_DISCARD_SOURCE(src));
621 g_assert_cmpint(ret, ==, 0);
622 rdm = memory_region_get_ram_discard_manager(test_mr);
623
624 /* 2. Register listener */
625 test_listener_init(&tl);
626 section.mr = test_mr;
627 section.offset_within_region = 0;
628 section.size = int128_make64(TEST_REGION_SIZE);
629 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
630
631 /* Listener notified about populated region (0 - 32K) */
632 g_assert_cmpint(tl.populate_count, ==, 1);
633 g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 8);
634
635 /* 3. Remove source */
636 tl.populate_count = 0;
637 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
638
639 /*
640 * With 0 sources, everything is populated.
641 * The range that was discarded in src (from 32K to end) becomes populated.
642 */
643 g_assert_cmpint(tl.populate_count, ==, 1);
644 g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 8);
645 g_assert_cmpuint(tl.last_populate_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8);
646
647 /* 4. Add source back */
648 tl.discard_count = 0;
649 ret = memory_region_add_ram_discard_source(test_mr,
650 RAM_DISCARD_SOURCE(src));
651 g_assert_cmpint(ret, ==, 0);
652
653 /*
654 * Now we have 1 source again. The range (32K to end) is discarded again.
655 * Listener should be notified about this discard.
656 */
657 g_assert_cmpint(tl.discard_count, ==, 1);
658 g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 8);
659 g_assert_cmpuint(tl.last_discard_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8);
660
661 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
662 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
663 test_source_free(src);
664 test_teardown();
665 }
666
667 /*
668 * Test: Duplicate source registration should fail.
669 */
670 static void test_duplicate_source(void)
671 {
672 TestRamDiscardSource *src;
673 int ret;
674
675 test_setup();
676
677 src = test_source_new(test_mr, GRANULARITY_4K);
678
679 ret = memory_region_add_ram_discard_source(test_mr,
680 RAM_DISCARD_SOURCE(src));
681 g_assert_cmpint(ret, ==, 0);
682
683 /* Adding same source again should fail */
684 ret = memory_region_add_ram_discard_source(test_mr,
685 RAM_DISCARD_SOURCE(src));
686 g_assert_cmpint(ret, ==, -EBUSY);
687
688 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
689 test_source_free(src);
690 test_teardown();
691 }
692
693 /*
694 * Test: Populate notification rollback on listener error.
695 */
696 static void test_populate_rollback(void)
697 {
698 TestRamDiscardSource *src;
699 RamDiscardManager *rdm;
700 MemoryRegionSection section;
701 TestListener tl1 = { 0, }, tl2 = { 0, };
702 int ret;
703
704 test_setup();
705
706 src = test_source_new(test_mr, GRANULARITY_4K);
707
708 ret = memory_region_add_ram_discard_source(test_mr,
709 RAM_DISCARD_SOURCE(src));
710 g_assert_cmpint(ret, ==, 0);
711 rdm = memory_region_get_ram_discard_manager(test_mr);
712
713 /* Register two listeners */
714 test_listener_init(&tl1);
715 test_listener_init(&tl2);
716 tl2.fail_on_populate = 1; /* Second listener fails on first populate */
717
718 section.mr = test_mr;
719 section.offset_within_region = 0;
720 section.size = int128_make64(TEST_REGION_SIZE);
721
722 /*
723 * Register tl2 first so it's visited second (QLIST_INSERT_HEAD reverses
724 * registration order). This ensures tl1 receives populate before tl2
725 * fails.
726 */
727 ram_discard_manager_register_listener(rdm, &tl2.rdl, &section);
728 ram_discard_manager_register_listener(rdm, &tl1.rdl, &section);
729
730 /* Try to populate - should fail and roll back */
731 test_source_populate(src, 0, GRANULARITY_4K);
732 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
733 0, GRANULARITY_4K);
734 g_assert_cmpint(ret, ==, -ENOMEM);
735
736 /* First listener should have received populate then discard (rollback) */
737 g_assert_cmpint(tl1.populate_count, ==, 1);
738 g_assert_cmpint(tl1.discard_count, ==, 1);
739
740 ram_discard_manager_unregister_listener(rdm, &tl1.rdl);
741 ram_discard_manager_unregister_listener(rdm, &tl2.rdl);
742 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
743 test_source_free(src);
744 test_teardown();
745 }
746
747 /*
748 * Test: Replay populated with two sources (intersection).
749 */
750 static void test_replay_populated_intersection(void)
751 {
752 TestRamDiscardSource *src1, *src2;
753 RamDiscardManager *rdm;
754 MemoryRegionSection section;
755 TestListener tl = { 0, };
756 int ret;
757
758 test_setup();
759
760 src1 = test_source_new(test_mr, GRANULARITY_4K);
761 src2 = test_source_new(test_mr, GRANULARITY_4K);
762
763 /*
764 * src1: blocks 0-7 populated
765 * src2: blocks 4-11 populated
766 * Intersection: blocks 4-7
767 */
768 test_source_populate(src1, 0, GRANULARITY_4K * 8);
769 test_source_populate(src2, GRANULARITY_4K * 4, GRANULARITY_4K * 8);
770
771 ret = memory_region_add_ram_discard_source(test_mr,
772 RAM_DISCARD_SOURCE(src1));
773 g_assert_cmpint(ret, ==, 0);
774 ret = memory_region_add_ram_discard_source(test_mr,
775 RAM_DISCARD_SOURCE(src2));
776 g_assert_cmpint(ret, ==, 0);
777
778 rdm = memory_region_get_ram_discard_manager(test_mr);
779
780 /* Register listener - should only get notified about intersection */
781 test_listener_init(&tl);
782 section.mr = test_mr;
783 section.offset_within_region = 0;
784 section.size = int128_make64(TEST_REGION_SIZE);
785 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
786
787 /* Should have been notified about blocks 4-7 (intersection) */
788 g_assert_cmpint(tl.populate_count, ==, 1);
789 g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 4);
790 g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
791
792 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
793 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
794 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
795
796 test_source_free(src2);
797 test_source_free(src1);
798 test_teardown();
799 }
800
801 /*
802 * Test: Empty region (no sources).
803 */
804 static void test_no_sources(void)
805 {
806 test_setup();
807
808 /* No sources - should have no manager */
809 g_assert_null(memory_region_get_ram_discard_manager(test_mr));
810 g_assert_false(memory_region_has_ram_discard_manager(test_mr));
811
812 test_teardown();
813 }
814
815 static void test_redundant_discard(void)
816 {
817 TestRamDiscardSource *src1, *src2;
818 RamDiscardManager *rdm;
819 MemoryRegionSection section;
820 TestListener tl = { 0, };
821 int ret;
822
823 test_setup();
824
825 src1 = test_source_new(test_mr, GRANULARITY_4K);
826 src2 = test_source_new(test_mr, GRANULARITY_4K);
827
828 /* Add sources */
829 ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
830 g_assert_cmpint(ret, ==, 0);
831 ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
832 g_assert_cmpint(ret, ==, 0);
833
834 rdm = memory_region_get_ram_discard_manager(test_mr);
835
836 /* Register listener */
837 test_listener_init(&tl);
838 section.mr = test_mr;
839 section.offset_within_region = 0;
840 section.size = int128_make64(TEST_REGION_SIZE);
841 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
842
843 /* Populate intersection (0-4K) in both sources */
844 test_source_populate(src1, 0, GRANULARITY_4K);
845 test_source_populate(src2, 0, GRANULARITY_4K);
846
847 /* Notify populate src1 - should trigger listener populate */
848 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1),
849 0, GRANULARITY_4K);
850 g_assert_cmpint(ret, ==, 0);
851 g_assert_cmpint(tl.populate_count, ==, 1);
852
853 /* Now Discard src1 -> Aggregate Discarded */
854 tl.discard_count = 0;
855 test_source_discard(src1, 0, GRANULARITY_4K);
856 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), 0, GRANULARITY_4K);
857 g_assert_cmpint(tl.discard_count, ==, 1);
858
859 /* Now Discard src2 -> Aggregate Discarded (Already Discarded!) */
860 /* Listener should NOT receive another discard notification for the same range. */
861 test_source_discard(src2, 0, GRANULARITY_4K);
862 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src2), 0, GRANULARITY_4K);
863
864 g_assert_cmpint(tl.discard_count, ==, 1);
865
866 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
867 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
868 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
869
870 test_source_free(src2);
871 test_source_free(src1);
872 test_teardown();
873 }
874
875 /*
876 * Test: Listener with partial section coverage.
877 * Listener should only receive notifications for its registered range.
878 */
879 static void test_partial_listener_section(void)
880 {
881 TestRamDiscardSource *src;
882 RamDiscardManager *rdm;
883 MemoryRegionSection section;
884 TestListener tl = { 0, };
885 int ret;
886
887 test_setup();
888
889 src = test_source_new(test_mr, GRANULARITY_4K);
890
891 /* Populate blocks 0-7 */
892 test_source_populate(src, 0, GRANULARITY_4K * 8);
893
894 ret = memory_region_add_ram_discard_source(test_mr,
895 RAM_DISCARD_SOURCE(src));
896 g_assert_cmpint(ret, ==, 0);
897 rdm = memory_region_get_ram_discard_manager(test_mr);
898
899 /* Register listener for only blocks 2-5 (not the full region) */
900 test_listener_init(&tl);
901 section.mr = test_mr;
902 section.offset_within_region = GRANULARITY_4K * 2;
903 section.size = int128_make64(GRANULARITY_4K * 4);
904 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
905
906 /* Should be notified only about blocks 2-5 (intersection) */
907 g_assert_cmpint(tl.populate_count, ==, 1);
908 g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 2);
909 g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
910
911 /* Discard block 0 - outside listener's section, no notification */
912 tl.discard_count = 0;
913 test_source_discard(src, 0, GRANULARITY_4K);
914 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
915 0, GRANULARITY_4K);
916 g_assert_cmpint(tl.discard_count, ==, 0);
917
918 /* Discard block 3 - inside listener's section */
919 test_source_discard(src, GRANULARITY_4K * 3, GRANULARITY_4K);
920 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
921 GRANULARITY_4K * 3, GRANULARITY_4K);
922 g_assert_cmpint(tl.discard_count, ==, 1);
923 g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 3);
924
925 /* Discard spanning boundary (blocks 5-6) - only block 5 in section */
926 tl.discard_count = 0;
927 test_source_discard(src, GRANULARITY_4K * 5, GRANULARITY_4K * 2);
928 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
929 GRANULARITY_4K * 5, GRANULARITY_4K * 2);
930 g_assert_cmpint(tl.discard_count, ==, 1);
931 g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 5);
932 g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K);
933
934 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
935 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
936 test_source_free(src);
937 test_teardown();
938 }
939
940 /*
941 * Test: Multiple listeners with different (non-overlapping) sections.
942 */
943 static void test_multiple_listeners_different_sections(void)
944 {
945 TestRamDiscardSource *src;
946 RamDiscardManager *rdm;
947 MemoryRegionSection section1, section2;
948 TestListener tl1 = { 0, }, tl2 = { 0, };
949 int ret;
950
951 test_setup();
952
953 src = test_source_new(test_mr, GRANULARITY_4K);
954
955 ret = memory_region_add_ram_discard_source(test_mr,
956 RAM_DISCARD_SOURCE(src));
957 g_assert_cmpint(ret, ==, 0);
958 rdm = memory_region_get_ram_discard_manager(test_mr);
959
960 /* Listener 1: blocks 0-3 */
961 test_listener_init(&tl1);
962 section1.mr = test_mr;
963 section1.offset_within_region = 0;
964 section1.size = int128_make64(GRANULARITY_4K * 4);
965 ram_discard_manager_register_listener(rdm, &tl1.rdl, &section1);
966
967 /* Listener 2: blocks 8-11 */
968 test_listener_init(&tl2);
969 section2.mr = test_mr;
970 section2.offset_within_region = GRANULARITY_4K * 8;
971 section2.size = int128_make64(GRANULARITY_4K * 4);
972 ram_discard_manager_register_listener(rdm, &tl2.rdl, &section2);
973
974 /* Initially all discarded - no populate notifications */
975 g_assert_cmpint(tl1.populate_count, ==, 0);
976 g_assert_cmpint(tl2.populate_count, ==, 0);
977
978 /* Populate blocks 0-3 - only tl1 should be notified */
979 test_source_populate(src, 0, GRANULARITY_4K * 4);
980 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
981 0, GRANULARITY_4K * 4);
982 g_assert_cmpint(ret, ==, 0);
983 g_assert_cmpint(tl1.populate_count, ==, 1);
984 g_assert_cmpint(tl2.populate_count, ==, 0);
985
986 /* Populate blocks 8-11 - only tl2 should be notified */
987 test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4);
988 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
989 GRANULARITY_4K * 8,
990 GRANULARITY_4K * 4);
991 g_assert_cmpint(ret, ==, 0);
992 g_assert_cmpint(tl1.populate_count, ==, 1);
993 g_assert_cmpint(tl2.populate_count, ==, 1);
994
995 /* Populate blocks 4-7 (gap) - neither listener should be notified */
996 test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4);
997 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
998 GRANULARITY_4K * 4,
999 GRANULARITY_4K * 4);
1000 g_assert_cmpint(ret, ==, 0);
1001 g_assert_cmpint(tl1.populate_count, ==, 1);
1002 g_assert_cmpint(tl2.populate_count, ==, 1);
1003
1004 ram_discard_manager_unregister_listener(rdm, &tl2.rdl);
1005 ram_discard_manager_unregister_listener(rdm, &tl1.rdl);
1006 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
1007 test_source_free(src);
1008 test_teardown();
1009 }
1010
1011 /*
1012 * Test: Multiple listeners with overlapping sections.
1013 */
1014 static void test_overlapping_listener_sections(void)
1015 {
1016 TestRamDiscardSource *src;
1017 RamDiscardManager *rdm;
1018 MemoryRegionSection section1, section2;
1019 TestListener tl1 = { 0, }, tl2 = { 0, };
1020 int ret;
1021
1022 test_setup();
1023
1024 src = test_source_new(test_mr, GRANULARITY_4K);
1025
1026 ret = memory_region_add_ram_discard_source(test_mr,
1027 RAM_DISCARD_SOURCE(src));
1028 g_assert_cmpint(ret, ==, 0);
1029 rdm = memory_region_get_ram_discard_manager(test_mr);
1030
1031 /* Listener 1: blocks 0-7 */
1032 test_listener_init(&tl1);
1033 section1.mr = test_mr;
1034 section1.offset_within_region = 0;
1035 section1.size = int128_make64(GRANULARITY_4K * 8);
1036 ram_discard_manager_register_listener(rdm, &tl1.rdl, &section1);
1037
1038 /* Listener 2: blocks 4-11 (overlaps with tl1 on blocks 4-7) */
1039 test_listener_init(&tl2);
1040 section2.mr = test_mr;
1041 section2.offset_within_region = GRANULARITY_4K * 4;
1042 section2.size = int128_make64(GRANULARITY_4K * 8);
1043 ram_discard_manager_register_listener(rdm, &tl2.rdl, &section2);
1044
1045 /* Populate blocks 4-7 (overlap region) - both should be notified */
1046 test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4);
1047 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
1048 GRANULARITY_4K * 4,
1049 GRANULARITY_4K * 4);
1050 g_assert_cmpint(ret, ==, 0);
1051 g_assert_cmpint(tl1.populate_count, ==, 1);
1052 g_assert_cmpint(tl2.populate_count, ==, 1);
1053
1054 /* Populate blocks 0-3 - only tl1 */
1055 test_source_populate(src, 0, GRANULARITY_4K * 4);
1056 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
1057 0, GRANULARITY_4K * 4);
1058 g_assert_cmpint(ret, ==, 0);
1059 g_assert_cmpint(tl1.populate_count, ==, 2);
1060 g_assert_cmpint(tl2.populate_count, ==, 1);
1061
1062 /* Populate blocks 8-11 - only tl2 */
1063 test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4);
1064 ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
1065 GRANULARITY_4K * 8,
1066 GRANULARITY_4K * 4);
1067 g_assert_cmpint(ret, ==, 0);
1068 g_assert_cmpint(tl1.populate_count, ==, 2);
1069 g_assert_cmpint(tl2.populate_count, ==, 2);
1070
1071 ram_discard_manager_unregister_listener(rdm, &tl2.rdl);
1072 ram_discard_manager_unregister_listener(rdm, &tl1.rdl);
1073 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
1074 test_source_free(src);
1075 test_teardown();
1076 }
1077
1078 /*
1079 * Test: Listener at exact memory region boundaries.
1080 */
1081 static void test_boundary_section(void)
1082 {
1083 TestRamDiscardSource *src;
1084 RamDiscardManager *rdm;
1085 MemoryRegionSection section;
1086 TestListener tl = { 0, };
1087 uint64_t last_offset;
1088 int ret;
1089
1090 test_setup();
1091
1092 src = test_source_new(test_mr, GRANULARITY_4K);
1093
1094 /* Populate last 4 blocks of the region */
1095 last_offset = TEST_REGION_SIZE - GRANULARITY_4K * 4;
1096 test_source_populate(src, last_offset, GRANULARITY_4K * 4);
1097
1098 ret = memory_region_add_ram_discard_source(test_mr,
1099 RAM_DISCARD_SOURCE(src));
1100 g_assert_cmpint(ret, ==, 0);
1101 rdm = memory_region_get_ram_discard_manager(test_mr);
1102
1103 /* Register listener for exactly the last 4 blocks */
1104 test_listener_init(&tl);
1105 section.mr = test_mr;
1106 section.offset_within_region = last_offset;
1107 section.size = int128_make64(GRANULARITY_4K * 4);
1108 ram_discard_manager_register_listener(rdm, &tl.rdl, &section);
1109
1110 /* Should receive notification for the populated range */
1111 g_assert_cmpint(tl.populate_count, ==, 1);
1112 g_assert_cmpuint(tl.last_populate_offset, ==, last_offset);
1113 g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
1114
1115 /* Discard exactly at boundary */
1116 tl.discard_count = 0;
1117 test_source_discard(src, last_offset, GRANULARITY_4K * 4);
1118 ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
1119 last_offset, GRANULARITY_4K * 4);
1120 g_assert_cmpint(tl.discard_count, ==, 1);
1121
1122 ram_discard_manager_unregister_listener(rdm, &tl.rdl);
1123 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
1124 test_source_free(src);
1125 test_teardown();
1126 }
1127
1128 static int count_discarded_blocks(const MemoryRegionSection *section,
1129 void *opaque)
1130 {
1131 int *count = opaque;
1132 *count += int128_get64(section->size) / GRANULARITY_4K;
1133 return 0;
1134 }
1135
1136 /*
1137 * Test: replay_discarded with two sources (union semantics).
1138 */
1139 static void test_replay_discarded(void)
1140 {
1141 TestRamDiscardSource *src1, *src2;
1142 RamDiscardManager *rdm;
1143 MemoryRegionSection section;
1144 int count = 0;
1145 int ret;
1146
1147 test_setup();
1148
1149 src1 = test_source_new(test_mr, GRANULARITY_4K);
1150 src2 = test_source_new(test_mr, GRANULARITY_4K);
1151
1152 /*
1153 * src1: blocks 0-3 populated, rest discarded
1154 * src2: blocks 2-5 populated, rest discarded
1155 * Aggregated populated: blocks 2-3 (intersection)
1156 * Aggregated discarded: blocks 0-1, 4-5, 6+ (union of discarded)
1157 */
1158 test_source_populate(src1, 0, GRANULARITY_4K * 4);
1159 test_source_populate(src2, GRANULARITY_4K * 2, GRANULARITY_4K * 4);
1160
1161 ret = memory_region_add_ram_discard_source(test_mr,
1162 RAM_DISCARD_SOURCE(src1));
1163 g_assert_cmpint(ret, ==, 0);
1164 ret = memory_region_add_ram_discard_source(test_mr,
1165 RAM_DISCARD_SOURCE(src2));
1166 g_assert_cmpint(ret, ==, 0);
1167
1168 rdm = memory_region_get_ram_discard_manager(test_mr);
1169
1170 section.mr = test_mr;
1171 section.offset_within_region = 0;
1172 section.size = int128_make64(GRANULARITY_4K * 8);
1173
1174 /* Count discarded blocks */
1175 ret = ram_discard_manager_replay_discarded(rdm, &section,
1176 count_discarded_blocks, &count);
1177
1178 g_assert_cmpint(ret, ==, 0);
1179 /* Discarded: blocks 0-1 (2), blocks 4-5 (2), blocks 6-7 (2) = 6 blocks */
1180 g_assert_cmpint(count, ==, 6);
1181
1182 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
1183 memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
1184
1185 test_source_free(src2);
1186 test_source_free(src1);
1187 test_teardown();
1188 }
1189
1190 int main(int argc, char **argv)
1191 {
1192 g_test_init(&argc, &argv, NULL);
1193
1194 module_call_init(MODULE_INIT_QOM);
1195 type_register_static(&test_rds_info);
1196
1197 g_test_add_func("/ram-discard-manager/single-source/basic",
1198 test_single_source_basic);
1199 g_test_add_func("/ram-discard-manager/single-source/listener",
1200 test_single_source_listener);
1201 g_test_add_func("/ram-discard-manager/two-sources/same-granularity",
1202 test_two_sources_same_granularity);
1203 g_test_add_func("/ram-discard-manager/two-sources/different-granularity",
1204 test_two_sources_different_granularity);
1205 g_test_add_func("/ram-discard-manager/two-sources/notification",
1206 test_two_sources_notification);
1207 g_test_add_func("/ram-discard-manager/dynamic/add-source-with-listener",
1208 test_add_source_with_listener);
1209 g_test_add_func("/ram-discard-manager/dynamic/remove-source-with-listener",
1210 test_remove_source_with_listener);
1211 g_test_add_func("/ram-discard-manager/dynamic/readd-source-with-listener",
1212 test_readd_source_with_listener);
1213 g_test_add_func("/ram-discard-manager/edge/duplicate-source",
1214 test_duplicate_source);
1215 g_test_add_func("/ram-discard-manager/edge/populate-rollback",
1216 test_populate_rollback);
1217 g_test_add_func("/ram-discard-manager/edge/replay-intersection",
1218 test_replay_populated_intersection);
1219 g_test_add_func("/ram-discard-manager/edge/no-sources",
1220 test_no_sources);
1221 g_test_add_func("/ram-discard-manager/multi-source/redundant-discard",
1222 test_redundant_discard);
1223 g_test_add_func("/ram-discard-manager/listener/partial-section",
1224 test_partial_listener_section);
1225 g_test_add_func("/ram-discard-manager/listener/multiple-different",
1226 test_multiple_listeners_different_sections);
1227 g_test_add_func("/ram-discard-manager/listener/overlapping",
1228 test_overlapping_listener_sections);
1229 g_test_add_func("/ram-discard-manager/edge/boundary-section",
1230 test_boundary_section);
1231 g_test_add_func("/ram-discard-manager/multi-source/replay-discarded",
1232 test_replay_discarded);
1233
1234 return g_test_run();
1235 }