From 461279a9ce31baa415bba79216aad7f67accd6d6 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 2 Jan 2023 17:41:18 +0100 Subject: platform/tests: add nmtstp_assert_platform() helper --- src/core/platform/tests/test-common.c | 316 ++++++++++++++++++++++++++++++++++ src/core/platform/tests/test-common.h | 4 + 2 files changed, 320 insertions(+) diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c index 4348323cbb..0ca84cd999 100644 --- a/src/core/platform/tests/test-common.c +++ b/src/core/platform/tests/test-common.c @@ -745,6 +745,322 @@ nmtstp_run_command(const char *format, ...) /*****************************************************************************/ +static int +_assert_platform_sort_objs(gconstpointer ptr_a, gconstpointer ptr_b) +{ + const NMPObject *a = *((const NMPObject *const *) ptr_a); + const NMPObject *b = *((const NMPObject *const *) ptr_b); + + g_assert(NMP_OBJECT_IS_VALID(a)); + g_assert(NMP_OBJECT_IS_VALID(b)); + g_assert(NMP_OBJECT_GET_TYPE(a) == NMP_OBJECT_GET_TYPE(b)); + + NM_CMP_RETURN(nmp_object_id_cmp(a, b)); + g_assert_not_reached(); + return 0; +} + +static void +_assert_platform_printarr(NMPObjectType obj_type, GPtrArray *arr1, GPtrArray *arr2) +{ + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + guint i; + + _LOGT("compare arrays of %s. In cache %u entries, fetched %u entries", + NMP_OBJECT_TYPE_NAME(obj_type), + nm_g_ptr_array_len(arr1), + nm_g_ptr_array_len(arr2)); + + for (i = 0; i < nm_g_ptr_array_len(arr1); i++) { + _LOGT("cache[%u] %s", + i, + nmp_object_to_string(arr1->pdata[i], NMP_OBJECT_TO_STRING_ALL, sbuf, sizeof(sbuf))); + } + for (i = 0; i < nm_g_ptr_array_len(arr2); i++) { + _LOGT("fetch[%u] %s", + i, + nmp_object_to_string(arr2->pdata[i], NMP_OBJECT_TO_STRING_ALL, sbuf, sizeof(sbuf))); + } + + switch (obj_type) { + case NMP_OBJECT_TYPE_LINK: + nmtstp_run_command("ip -d link"); + break; + case NMP_OBJECT_TYPE_IP4_ADDRESS: + nmtstp_run_command("ip -d -4 address"); + break; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + nmtstp_run_command("ip -d -6 address"); + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + nmtstp_run_command("ip -d -4 route show table all"); + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + nmtstp_run_command("ip -d -6 route show table all"); + break; + default: + g_assert_not_reached(); + break; + } +} + +static gboolean +_assert_platform_normalize_all(GPtrArray *arr) +{ + guint i; + gboolean normalized = FALSE; + + for (i = 0; i < nm_g_ptr_array_len(arr); i++) { + const NMPObject **ptr = (gpointer) &arr->pdata[i]; + NMPObject *new; + + switch (NMP_OBJECT_GET_TYPE(*ptr)) { + case NMP_OBJECT_TYPE_LINK: + new = nmp_object_clone(*ptr, FALSE); + new->link.rx_packets = 0; + new->link.rx_bytes = 0; + new->link.tx_packets = 0; + new->link.tx_bytes = 0; + nmp_object_ref_set(ptr, new); + nmp_object_unref(new); + normalized = TRUE; + default: + break; + } + } + return normalized; +} + +static void +_assert_platform_compare_arr(NMPObjectType obj_type, + const char *detail_type, + GPtrArray *arr1, + GPtrArray *arr2, + gboolean normalized, + gboolean share_multi_idx) +{ + const NMPClass *obj_class = nmp_class_from_type(obj_type); + char sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE]; + char sbuf2[NM_UTILS_TO_STRING_BUFFER_SIZE]; + int idx; + int idx_pointer_comp = -1; + + for (idx = 0; TRUE; idx++) { + if (nm_g_ptr_array_len(arr1) == idx && nm_g_ptr_array_len(arr2) == idx) + break; + if (idx >= nm_g_ptr_array_len(arr1)) { + _assert_platform_printarr(obj_type, arr1, arr2); + g_error("Comparing %s (%s) for platform fails. Platform now shows entry #%u which is " + "not in the cache but expected %s", + obj_class->obj_type_name, + detail_type, + idx, + nmp_object_to_string(arr2->pdata[idx], + NMP_OBJECT_TO_STRING_ALL, + sbuf1, + sizeof(sbuf1))); + } + if (idx >= nm_g_ptr_array_len(arr2)) { + _assert_platform_printarr(obj_type, arr1, arr2); + g_error("Comparing %s (%s) for platform fails. Platform has no more entry #%u which is " + "still in the cache as %s", + obj_class->obj_type_name, + detail_type, + idx, + nmp_object_to_string(arr1->pdata[idx], + NMP_OBJECT_TO_STRING_ALL, + sbuf1, + sizeof(sbuf1))); + } + if (!nmp_object_equal(arr1->pdata[idx], arr2->pdata[idx])) { + _assert_platform_printarr(obj_type, arr1, arr2); + g_error("Comparing %s (%s) for platform fails. Platform entry #%u is now %s but in " + "cache is %s", + obj_class->obj_type_name, + detail_type, + idx, + nmp_object_to_string(arr2->pdata[idx], + NMP_OBJECT_TO_STRING_ALL, + sbuf1, + sizeof(sbuf1)), + nmp_object_to_string(arr1->pdata[idx], + NMP_OBJECT_TO_STRING_ALL, + sbuf2, + sizeof(sbuf2))); + } + + if (!normalized && (share_multi_idx != (arr1->pdata[idx] == arr2->pdata[idx])) + && idx_pointer_comp == -1) + idx_pointer_comp = idx; + } + + if (idx_pointer_comp != -1) { + _assert_platform_printarr(obj_type, arr1, arr2); + g_error("Comparing %s (%s) for platform fails for pointer comparison. Platform entry " + "#%u is now %s but in cache is %s", + obj_class->obj_type_name, + detail_type, + idx_pointer_comp, + nmp_object_to_string(arr2->pdata[idx_pointer_comp], + NMP_OBJECT_TO_STRING_ALL, + sbuf1, + sizeof(sbuf1)), + nmp_object_to_string(arr1->pdata[idx_pointer_comp], + NMP_OBJECT_TO_STRING_ALL, + sbuf2, + sizeof(sbuf2))); + } +} + +void +nmtstp_assert_platform(NMPlatform *platform, guint32 obj_type_flags) +{ + static const NMPObjectType obj_types[] = { + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_LINK, + }; + gboolean obj_type_flags_all = (obj_type_flags == 0u); + gs_unref_object NMPlatform *platform2 = NULL; + int i_obj_types; + gboolean share_multi_idx = nmtst_get_rand_bool(); + + /* This test creates a new NMLinuxPlatform instance. This will fill + * the cache with a new dump. + * + * Then it compares the content with @platform and checks that they + * agree. This tests that @platform cache is consistent, as it was + * updated based on netlink events. */ + + g_assert(NM_IS_LINUX_PLATFORM(platform)); + + _LOGD("assert-platform: start"); + + nm_platform_process_events(platform); + + platform2 = nm_linux_platform_new(share_multi_idx ? nm_platform_get_multi_idx(platform) : NULL, + TRUE, + nmtst_get_rand_bool(), + nmtst_get_rand_bool()); + g_assert(NM_IS_LINUX_PLATFORM(platform2)); + + for (i_obj_types = 0; i_obj_types < (int) G_N_ELEMENTS(obj_types); i_obj_types++) { + const NMPObjectType obj_type = obj_types[i_obj_types]; + const guint32 i_obj_type_flags = nmp_object_type_to_flags(obj_type); + gs_unref_ptrarray GPtrArray *arr1 = NULL; + gs_unref_ptrarray GPtrArray *arr2 = NULL; + NMPLookup lookup; + gboolean check_unordered = TRUE; + guint idx; + gboolean normalized; + + if (!obj_type_flags_all) { + if (!NM_FLAGS_ANY(obj_type_flags, i_obj_type_flags)) + continue; + obj_type_flags = NM_FLAGS_UNSET(obj_type_flags, i_obj_type_flags); + } + + nmp_lookup_init_obj_type(&lookup, obj_type); + + arr1 = nm_platform_lookup_clone(platform, &lookup, NULL, NULL) ?: g_ptr_array_new(); + arr2 = nm_platform_lookup_clone(platform2, &lookup, NULL, NULL) ?: g_ptr_array_new(); + + normalized = _assert_platform_normalize_all(arr1); + normalized = _assert_platform_normalize_all(arr2); + + if (check_unordered) { + /* We need to sort the two lists. */ + g_ptr_array_sort(arr1, _assert_platform_sort_objs); + g_ptr_array_sort(arr2, _assert_platform_sort_objs); + } + + _assert_platform_compare_arr(obj_type, "main", arr1, arr2, normalized, share_multi_idx); + + if (NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)) { + /* For routes, the WEAK_ID needs to be sorted and match the expected order. Check that. */ + g_assert(!normalized); + for (idx = 0; idx < nm_g_ptr_array_len(arr1); idx++) { + const NMPObject *obj1 = arr1->pdata[idx]; + const NMPObject *obj2 = arr2->pdata[idx]; + gs_unref_ptrarray GPtrArray *arr1b = NULL; + gs_unref_ptrarray GPtrArray *arr2b = NULL; + gs_unref_ptrarray GPtrArray *arr1b_sorted = NULL; + gs_unref_ptrarray GPtrArray *arr2b_sorted = NULL; + guint found_obj1 = 0; + guint found_obj2 = 0; + guint i; + + nmp_lookup_init_route_by_weak_id(&lookup, obj1); + arr1b = + nm_platform_lookup_clone(platform, &lookup, NULL, NULL) ?: g_ptr_array_new(); + g_assert_cmpint(arr1b->len, >, 0u); + + nmp_lookup_init_route_by_weak_id(&lookup, obj2); + arr2b = + nm_platform_lookup_clone(platform2, &lookup, NULL, NULL) ?: g_ptr_array_new(); + g_assert_cmpint(arr2b->len, ==, arr1b->len); + + /* First check that the lists agree, if we sort them. The list of + * weak-ids was supposed to honor the sort order from `ip route show`, + * but as that is not the case (see blow), first check whether at + * least the same routes are in the list (with wrong sort order). */ + arr1b_sorted = nm_g_ptr_array_new_clone(arr1b, NULL, NULL, NULL); + arr2b_sorted = nm_g_ptr_array_new_clone(arr2b, NULL, NULL, NULL); + g_ptr_array_sort(arr1b_sorted, _assert_platform_sort_objs); + g_ptr_array_sort(arr2b_sorted, _assert_platform_sort_objs); + _assert_platform_compare_arr(obj_type, + "weak-id-sorted", + arr1b_sorted, + arr2b_sorted, + normalized, + share_multi_idx); + + if (obj_type == NMP_OBJECT_TYPE_IP6_ROUTE) { + /* For IPv6, the weak-ids are actually not sorted correctly. + * This is because IPv6 multihop/ECMP routes get split into + * multiple objects, and we don't get this right. + * + * This may be a bug. But we probably don't rely on this + * anymore, because the weak-id were used to find which + * route got replaced with `NLM_F_REPLACE`, but that anyway + * doesn't work. We now always request a new dump. */ + } else if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) { + /* For IPv4, it also does not reliably always work. This may + * be a bug we want to fix. For now, ignore the check. */ + } else { + /* Assert that also the original, not-sorted lists agree. */ + _assert_platform_compare_arr(obj_type, + "weak-id", + arr1b, + arr2b, + normalized, + share_multi_idx); + } + + for (i = 0; i < arr1b->len; i++) { + if (arr1b->pdata[i] == obj1) + found_obj1++; + if (arr2b->pdata[i] == obj2) + found_obj2++; + } + + g_assert_cmpint(found_obj1, ==, 1u); + g_assert_cmpint(found_obj2, ==, 1u); + } + } + } + + g_clear_object(&platform2); + + _LOGD("assert-platform: done"); + + g_assert_cmpint(obj_type_flags, ==, 0u); +} + +/*****************************************************************************/ + typedef struct { GMainLoop *loop; guint signal_counts; diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h index 825d3a4817..383a1fed47 100644 --- a/src/core/platform/tests/test-common.h +++ b/src/core/platform/tests/test-common.h @@ -139,6 +139,10 @@ int nmtstp_run_command(const char *format, ...) _nm_printf(1, 2); /*****************************************************************************/ +void nmtstp_assert_platform(NMPlatform *platform, guint32 obj_type_flags); + +/*****************************************************************************/ + guint nmtstp_wait_for_signal(NMPlatform *platform, gint64 timeout_msec); guint nmtstp_wait_for_signal_until(NMPlatform *platform, gint64 until_ms); const NMPlatformLink *nmtstp_wait_for_link(NMPlatform *platform, -- cgit v1.2.1