From: Marc-AndrĂ© Lureau Add various unit tests for the RamDiscardManager multi-source aggregation functionality. The test uses a TestRamDiscardSource QOM object that tracks populated state via a bitmap, similar to RamBlockAttributes implementation. Signed-off-by: Marc-AndrĂ© Lureau --- tests/unit/test-ram-discard-manager-stubs.c | 48 + tests/unit/test-ram-discard-manager.c | 1313 +++++++++++++++++++ tests/unit/meson.build | 8 +- 3 files changed, 1368 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test-ram-discard-manager-stubs.c create mode 100644 tests/unit/test-ram-discard-manager.c diff --git a/tests/unit/test-ram-discard-manager-stubs.c b/tests/unit/test-ram-discard-manager-stubs.c new file mode 100644 index 00000000000..5daef09e49e --- /dev/null +++ b/tests/unit/test-ram-discard-manager-stubs.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "qemu/osdep.h" +#include "qom/object.h" +#include "glib.h" +#include "system/memory.h" + +RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr) +{ + return mr->rdm; +} + +int memory_region_add_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + if (!mr->rdm) { + mr->rdm = ram_discard_manager_new(mr); + } + return ram_discard_manager_add_source(mr->rdm, source); +} + +void memory_region_del_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + RamDiscardManager *rdm = mr->rdm; + + if (!rdm) { + return; + } + + ram_discard_manager_del_source(rdm, source); +} + +uint64_t memory_region_size(MemoryRegion *mr) +{ + return int128_get64(mr->size); +} + +MemoryRegionSection *memory_region_section_new_copy(MemoryRegionSection *s) +{ + MemoryRegionSection *copy = g_new(MemoryRegionSection, 1); + *copy = *s; + return copy; +} + +void memory_region_section_free_copy(MemoryRegionSection *s) +{ + g_free(s); +} diff --git a/tests/unit/test-ram-discard-manager.c b/tests/unit/test-ram-discard-manager.c new file mode 100644 index 00000000000..73ecb4f793f --- /dev/null +++ b/tests/unit/test-ram-discard-manager.c @@ -0,0 +1,1313 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "qemu/osdep.h" +#include "qemu/bitmap.h" +#include "qemu/module.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "qom/qom-qobject.h" +#include "glib.h" +#include "system/memory.h" + +#define TYPE_TEST_RAM_DISCARD_SOURCE "test-ram-discard-source" + +OBJECT_DECLARE_SIMPLE_TYPE(TestRamDiscardSource, TEST_RAM_DISCARD_SOURCE) + +struct TestRamDiscardSource { + Object parent; + + MemoryRegion *mr; + uint64_t granularity; + unsigned long *bitmap; + uint64_t bitmap_size; +}; + +static uint64_t test_rds_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + + g_assert(mr == src->mr); + return src->granularity; +} + +static bool test_rds_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + uint64_t offset = section->offset_within_region; + uint64_t size = int128_get64(section->size); + uint64_t first_bit = offset / src->granularity; + uint64_t last_bit = (offset + size - 1) / src->granularity; + unsigned long found; + + g_assert(section->mr == src->mr); + + /* Check if any bit in range is zero (discarded) */ + found = find_next_zero_bit(src->bitmap, last_bit + 1, first_bit); + return found > last_bit; +} + +static int test_rds_replay_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + uint64_t first_bit, last_bit; + uint64_t offset, size; + int ret = 0; + + g_assert(section->mr == src->mr); + + first_bit = section->offset_within_region / src->granularity; + first_bit = find_next_bit(src->bitmap, src->bitmap_size, first_bit); + + while (first_bit < src->bitmap_size) { + MemoryRegionSection tmp = *section; + + offset = first_bit * src->granularity; + last_bit = find_next_zero_bit(src->bitmap, src->bitmap_size, + first_bit + 1) - 1; + size = (last_bit - first_bit + 1) * src->granularity; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + break; + } + + ret = replay_fn(&tmp, opaque); + if (ret) { + break; + } + + first_bit = find_next_bit(src->bitmap, src->bitmap_size, last_bit + 2); + } + + return ret; +} + +static int test_rds_replay_discarded(const RamDiscardSource *rds, + const MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + uint64_t first_bit, last_bit; + uint64_t offset, size; + int ret = 0; + + g_assert(section->mr == src->mr); + + first_bit = section->offset_within_region / src->granularity; + first_bit = find_next_zero_bit(src->bitmap, src->bitmap_size, first_bit); + + while (first_bit < src->bitmap_size) { + MemoryRegionSection tmp = *section; + + offset = first_bit * src->granularity; + last_bit = find_next_bit(src->bitmap, src->bitmap_size, + first_bit + 1) - 1; + size = (last_bit - first_bit + 1) * src->granularity; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + break; + } + + ret = replay_fn(&tmp, opaque); + if (ret) { + break; + } + + first_bit = find_next_zero_bit(src->bitmap, src->bitmap_size, + last_bit + 2); + } + + return ret; +} + +static void test_rds_class_init(ObjectClass *klass, const void *data) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); + + rdsc->get_min_granularity = test_rds_get_min_granularity; + rdsc->is_populated = test_rds_is_populated; + rdsc->replay_populated = test_rds_replay_populated; + rdsc->replay_discarded = test_rds_replay_discarded; +} + +static const TypeInfo test_rds_info = { + .name = TYPE_TEST_RAM_DISCARD_SOURCE, + .parent = TYPE_OBJECT, + .instance_size = sizeof(TestRamDiscardSource), + .class_init = test_rds_class_init, + .interfaces = (const InterfaceInfo[]) { + { TYPE_RAM_DISCARD_SOURCE }, + { } + }, +}; + +static TestRamDiscardSource *test_source_new(MemoryRegion *mr, + uint64_t granularity) +{ + TestRamDiscardSource *src; + uint64_t region_size = memory_region_size(mr); + + src = TEST_RAM_DISCARD_SOURCE(object_new(TYPE_TEST_RAM_DISCARD_SOURCE)); + src->mr = mr; + src->granularity = granularity; + src->bitmap_size = DIV_ROUND_UP(region_size, granularity); + src->bitmap = bitmap_new(src->bitmap_size); + + return src; +} + +static void test_source_free(TestRamDiscardSource *src) +{ + g_free(src->bitmap); + object_unref(OBJECT(src)); +} + +static void test_source_populate(TestRamDiscardSource *src, + uint64_t offset, uint64_t size) +{ + uint64_t first_bit = offset / src->granularity; + uint64_t nbits = size / src->granularity; + + bitmap_set(src->bitmap, first_bit, nbits); +} + +static void test_source_discard(TestRamDiscardSource *src, + uint64_t offset, uint64_t size) +{ + uint64_t first_bit = offset / src->granularity; + uint64_t nbits = size / src->granularity; + + bitmap_clear(src->bitmap, first_bit, nbits); +} + +typedef struct TestListener { + RamDiscardListener rdl; + int populate_count; + int discard_count; + uint64_t last_populate_offset; + uint64_t last_populate_size; + uint64_t last_discard_offset; + uint64_t last_discard_size; + int fail_on_populate; /* Return error on Nth populate */ + int populate_call_num; +} TestListener; + +static int test_listener_populate(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl = container_of(rdl, TestListener, rdl); + + tl->populate_call_num++; + if (tl->fail_on_populate > 0 && + tl->populate_call_num >= tl->fail_on_populate) { + return -ENOMEM; + } + + tl->populate_count++; + tl->last_populate_offset = section->offset_within_region; + tl->last_populate_size = int128_get64(section->size); + return 0; +} + +static void test_listener_discard(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl = container_of(rdl, TestListener, rdl); + + tl->discard_count++; + tl->last_discard_offset = section->offset_within_region; + tl->last_discard_size = int128_get64(section->size); +} + +static void test_listener_init(TestListener *tl) +{ + ram_discard_listener_init(&tl->rdl, + test_listener_populate, + test_listener_discard); +} + +#define TEST_REGION_SIZE (16 * 1024 * 1024) /* 16 MB */ +#define GRANULARITY_4K (4 * 1024) +#define GRANULARITY_2M (2 * 1024 * 1024) + +static MemoryRegion *test_mr; + +static void test_setup(void) +{ + test_mr = g_new0(MemoryRegion, 1); + test_mr->size = int128_make64(TEST_REGION_SIZE); + test_mr->ram = true; +} + +static void test_teardown(void) +{ + g_clear_pointer(&test_mr->rdm, object_unref); + object_unparent(OBJECT(test_mr)); + g_free(test_mr); + test_mr = NULL; +} + +static void test_single_source_basic(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_null(rdm); + + /* Add source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + /* Initially all discarded */ + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate a range in source */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + + /* Now should be populated */ + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check larger section */ + section.size = int128_make64(GRANULARITY_4K * 4); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check section that spans populated and discarded */ + section.size = int128_make64(GRANULARITY_4K * 8); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + test_source_free(src); + test_teardown(); +} + +static void test_single_source_listener(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some ranges before adding listener */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about populated regions */ + g_assert_cmpint(tl.populate_count, ==, 2); + + /* Notify populate for new range */ + tl.populate_count = 0; + test_source_populate(src, GRANULARITY_4K * 16, GRANULARITY_4K * 2); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 16, + GRANULARITY_4K * 2); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 16); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 2); + + /* Notify discard */ + tl.discard_count = 0; + test_source_discard(src, 0, GRANULARITY_4K * 4); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, 0); + g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K * 4); + + /* Unregister listener */ + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +static void test_two_sources_same_granularity(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Add first source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + + /* Add second source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Check granularity */ + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + + /* Both discarded -> aggregated discarded */ + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in src1 only */ + test_source_populate(src1, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in src2 only */ + test_source_discard(src1, 0, GRANULARITY_4K); + test_source_populate(src2, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in both -> aggregated populated */ + test_source_populate(src1, 0, GRANULARITY_4K); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Remove sources */ + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Two sources with different granularities (4K and 2M). + * The aggregated granularity should be GCD(4K, 2M) = 4K. + */ +static void test_two_sources_different_granularity(void) +{ + TestRamDiscardSource *src_4k, *src_2m; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src_4k = test_source_new(test_mr, GRANULARITY_4K); + src_2m = test_source_new(test_mr, GRANULARITY_2M); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_4k)); + g_assert_cmpint(ret, ==, 0); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_2m)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + + /* Both discarded */ + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate 4K in src_4k, but src_2m still discarded the whole 2M block */ + test_source_populate(src_4k, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate 2M in src_2m (which includes the 4K block) */ + test_source_populate(src_2m, 0, GRANULARITY_2M); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check a 4K block at offset 4K (populated in src_2m but not in src_4k) */ + section.offset_within_region = GRANULARITY_4K; + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate it in src_4k */ + test_source_populate(src_4k, GRANULARITY_4K, GRANULARITY_4K); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_2m)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_4k)); + + test_source_free(src_2m); + test_source_free(src_4k); + test_teardown(); +} + +/* + * Test: Notification with two sources. + * Populate notification should only fire when all sources are populated. + */ +static void test_two_sources_notification(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* No populate notifications yet (all discarded) */ + g_assert_cmpint(tl.populate_count, ==, 0); + + /* Populate in src1 only - no notification (src2 still discarded) */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 0); + + /* Populate same range in src2 - now should notify */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src2), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + + /* Discard from src1 - should notify discard immediately */ + tl.discard_count = 0; + test_source_discard(src1, 0, GRANULARITY_4K * 2); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K * 2); + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Adding source with existing listener. + * When a new source is added, listeners should be notified about + * regions that become discarded. + */ +static void test_add_source_with_listener(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src1 */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about populated region */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpint(tl.last_populate_offset, ==, 0); + g_assert_cmpint(tl.last_populate_size, ==, GRANULARITY_4K * 8); + + /* src2 has part of the region populated, part discarded */ + /* src2 has 0-4 populated, 4-8 discarded */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + + /* Add src2 - listener should be notified about newly discarded regions */ + tl.discard_count = 0; + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + /* + * The range 4K*4 to 4K*8 was populated in src1 but discarded in src2, + * so it becomes aggregated-discarded. Listener should be notified. + * Only this range should trigger a discard notification - regions beyond + * 4K*8 were already discarded in src1, so adding src2 doesn't change them. + */ + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpint(tl.last_discard_offset, ==, GRANULARITY_4K * 4); + g_assert_cmpint(tl.last_discard_size, ==, GRANULARITY_4K * 4); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Removing source with existing listener. + * When a source is removed, listeners should be notified about + * regions that become populated. + */ +static void test_remove_source_with_listener(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* src1: all of first 8 blocks populated */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + /* src2: only first 4 blocks populated */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Only first 4 blocks are aggregated-populated */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Remove src2 - blocks 4-8 should become populated */ + tl.populate_count = 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + + /* Listener should be notified about newly populated region (4K*4 to 4K*8) */ + g_assert_cmpint(tl.populate_count, >=, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Add a source, register a listener, remove the source, then add it back. + * This checks the transition from 0 sources (all populated) to 1 source + * (partially discarded) with an active listener. + */ +static void test_readd_source_with_listener(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + /* 1. Add source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* 2. Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Listener notified about populated region (0 - 32K) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 8); + + /* 3. Remove source */ + tl.populate_count = 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + + /* + * With 0 sources, everything is populated. + * The range that was discarded in src (from 32K to end) becomes populated. + */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_populate_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8); + + /* 4. Add source back */ + tl.discard_count = 0; + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + /* + * Now we have 1 source again. The range (32K to end) is discarded again. + * Listener should be notified about this discard. + */ + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_discard_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Duplicate source registration should fail. + */ +static void test_duplicate_source(void) +{ + TestRamDiscardSource *src; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + /* Adding same source again should fail */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, -EBUSY); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Populate notification rollback on listener error. + */ +static void test_populate_rollback(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register two listeners */ + test_listener_init(&tl1); + test_listener_init(&tl2); + tl2.fail_on_populate = 1; /* Second listener fails on first populate */ + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + + /* + * Register tl2 first so it's visited second (QLIST_INSERT_HEAD reverses + * registration order). This ensures tl1 receives populate before tl2 + * fails. + */ + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion); + + /* Try to populate - should fail and roll back */ + test_source_populate(src, 0, GRANULARITY_4K); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, ==, -ENOMEM); + + /* First listener should have received populate then discard (rollback) */ + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl1.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Replay populated with two sources (intersection). + */ +static void test_replay_populated_intersection(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* + * src1: blocks 0-7 populated + * src2: blocks 4-11 populated + * Intersection: blocks 4-7 + */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + test_source_populate(src2, GRANULARITY_4K * 4, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener - should only get notified about intersection */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about blocks 4-7 (intersection) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 4); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Empty region (no sources). + */ +static void test_no_sources(void) +{ + test_setup(); + + /* No sources - should have no manager */ + g_assert_null(memory_region_get_ram_discard_manager(test_mr)); + g_assert_false(memory_region_has_ram_discard_manager(test_mr)); + + test_teardown(); +} + +static void test_redundant_discard(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Add sources */ + ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Populate intersection (0-4K) in both sources */ + test_source_populate(src1, 0, GRANULARITY_4K); + test_source_populate(src2, 0, GRANULARITY_4K); + + /* Notify populate src1 - should trigger listener populate (as src2 is also populated) */ + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1), 0, GRANULARITY_4K); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + + /* Now Discard src1 -> Aggregate Discarded */ + tl.discard_count = 0; + test_source_discard(src1, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), 0, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 1); + + /* Now Discard src2 -> Aggregate Discarded (Already Discarded!) */ + /* Listener should NOT receive another discard notification for the same range. */ + test_source_discard(src2, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src2), 0, GRANULARITY_4K); + + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Listener with partial section coverage. + * Listener should only receive notifications for its registered range. + */ +static void test_partial_listener_section(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate blocks 0-7 */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for only blocks 2-5 (not the full region) */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = GRANULARITY_4K * 2; + section.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should be notified only about blocks 2-5 (intersection) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 2); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Discard block 0 - outside listener's section, no notification */ + tl.discard_count = 0; + test_source_discard(src, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 0); + + /* Discard block 3 - inside listener's section */ + test_source_discard(src, GRANULARITY_4K * 3, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 3, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 3); + + /* Discard spanning boundary (blocks 5-6) - only block 5 in section */ + tl.discard_count = 0; + test_source_discard(src, GRANULARITY_4K * 5, GRANULARITY_4K * 2); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 5, GRANULARITY_4K * 2); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 5); + g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Multiple listeners with different (non-overlapping) sections. + */ +static void test_multiple_listeners_different_sections(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section1, section2; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-3 */ + test_listener_init(&tl1); + section1.mr = test_mr; + section1.offset_within_region = 0; + section1.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1); + + /* Listener 2: blocks 8-11 */ + test_listener_init(&tl2); + section2.mr = test_mr; + section2.offset_within_region = GRANULARITY_4K * 8; + section2.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2); + + /* Initially all discarded - no populate notifications */ + g_assert_cmpint(tl1.populate_count, ==, 0); + g_assert_cmpint(tl2.populate_count, ==, 0); + + /* Populate blocks 0-3 - only tl1 should be notified */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 0); + + /* Populate blocks 8-11 - only tl2 should be notified */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 4-7 (gap) - neither listener should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Multiple listeners with overlapping sections. + */ +static void test_overlapping_listener_sections(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section1, section2; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-7 */ + test_listener_init(&tl1); + section1.mr = test_mr; + section1.offset_within_region = 0; + section1.size = int128_make64(GRANULARITY_4K * 8); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1); + + /* Listener 2: blocks 4-11 (overlaps with tl1 on blocks 4-7) */ + test_listener_init(&tl2); + section2.mr = test_mr; + section2.offset_within_region = GRANULARITY_4K * 4; + section2.size = int128_make64(GRANULARITY_4K * 8); + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2); + + /* Populate blocks 4-7 (overlap region) - both should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 0-3 - only tl1 */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 2); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 8-11 - only tl2 */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 2); + g_assert_cmpint(tl2.populate_count, ==, 2); + + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Listener at exact memory region boundaries. + */ +static void test_boundary_section(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + uint64_t last_offset; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate last 4 blocks of the region */ + last_offset = TEST_REGION_SIZE - GRANULARITY_4K * 4; + test_source_populate(src, last_offset, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for exactly the last 4 blocks */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = last_offset; + section.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should receive notification for the populated range */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, last_offset); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Discard exactly at boundary */ + tl.discard_count = 0; + test_source_discard(src, last_offset, GRANULARITY_4K * 4); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + last_offset, GRANULARITY_4K * 4); + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +static int count_discarded_blocks(const MemoryRegionSection *section, + void *opaque) +{ + int *count = opaque; + *count += int128_get64(section->size) / GRANULARITY_4K; + return 0; +} + +/* + * Test: replay_discarded with two sources (union semantics). + */ +static void test_replay_discarded(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + int count = 0; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* + * src1: blocks 0-3 populated, rest discarded + * src2: blocks 2-5 populated, rest discarded + * Aggregated populated: blocks 2-3 (intersection) + * Aggregated discarded: blocks 0-1, 4-5, 6+ (union of discarded) + */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + test_source_populate(src2, GRANULARITY_4K * 2, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K * 8); + + /* Count discarded blocks */ + ret = ram_discard_manager_replay_discarded(rdm, §ion, + count_discarded_blocks, &count); + + g_assert_cmpint(ret, ==, 0); + /* Discarded: blocks 0-1 (2), blocks 4-5 (2), blocks 6-7 (2) = 6 blocks */ + g_assert_cmpint(count, ==, 6); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + module_call_init(MODULE_INIT_QOM); + type_register_static(&test_rds_info); + + g_test_add_func("/ram-discard-manager/single-source/basic", + test_single_source_basic); + g_test_add_func("/ram-discard-manager/single-source/listener", + test_single_source_listener); + g_test_add_func("/ram-discard-manager/two-sources/same-granularity", + test_two_sources_same_granularity); + g_test_add_func("/ram-discard-manager/two-sources/different-granularity", + test_two_sources_different_granularity); + g_test_add_func("/ram-discard-manager/two-sources/notification", + test_two_sources_notification); + g_test_add_func("/ram-discard-manager/dynamic/add-source-with-listener", + test_add_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/remove-source-with-listener", + test_remove_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/readd-source-with-listener", + test_readd_source_with_listener); + g_test_add_func("/ram-discard-manager/edge/duplicate-source", + test_duplicate_source); + g_test_add_func("/ram-discard-manager/edge/populate-rollback", + test_populate_rollback); + g_test_add_func("/ram-discard-manager/edge/replay-intersection", + test_replay_populated_intersection); + g_test_add_func("/ram-discard-manager/edge/no-sources", + test_no_sources); + g_test_add_func("/ram-discard-manager/multi-source/redundant-discard", + test_redundant_discard); + g_test_add_func("/ram-discard-manager/listener/partial-section", + test_partial_listener_section); + g_test_add_func("/ram-discard-manager/listener/multiple-different", + test_multiple_listeners_different_sections); + g_test_add_func("/ram-discard-manager/listener/overlapping", + test_overlapping_listener_sections); + g_test_add_func("/ram-discard-manager/edge/boundary-section", + test_boundary_section); + g_test_add_func("/ram-discard-manager/multi-source/replay-discarded", + test_replay_discarded); + + return g_test_run(); +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index bd580290601..e1f8061e98c 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -136,7 +136,13 @@ if have_system 'test-bufferiszero': [], 'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-smp.c'], 'test-vmstate': [migration, io], - 'test-yank': ['socket-helpers.c', qom, io, chardev] + 'test-yank': ['socket-helpers.c', qom, io, chardev], + 'test-ram-discard-manager': [ + 'test-ram-discard-manager.c', + 'test-ram-discard-manager-stubs.c', + meson.project_source_root() / 'system/ram-discard-manager.c', + genh, qemuutil, qom + ], } if config_host_data.get('CONFIG_INOTIFY1') tests += {'test-util-filemonitor': []} -- 2.52.0