gtestutils: Improve g_assert_cmpuint

While x86_64 has enough precision in long double to do a round trip
from guint64 to long double and back, this is platform-specific, and
is a disservice to users trying to debug failing unit tests on other
architectures where it loses precision for g_assert_cmp{int,uint,hex}.
See also https://bugzilla.gnome.org/show_bug.cgi?id=788385 which
mentions having to add casts to specifically silence the compiler on
platforms where the precision loss occurs.

Meanwhile, g_assert_cmpuint() does an unsigned comparison, but outputs
signed values if the comparison fails, which is confusing.

Fix both issues by introducing a new g_assertion_message_cmpint()
function with a new 'u' numtype.  For backwards compatibility, the
macros still call into the older g_assertion_message_cmpnum() when not
targetting 2.78, and that function still works when passed 'i' and 'x'
types even though code compiled for 2.78 and later will never invoke
it with numtype anything other than 'f'.  Note that g_assert_cmpmem
can also take advantage of the new code, even though in practice,
comparison between two size_t values representing array lengths that
can actually be compiled is unlikely to have ever hit the precision
loss.  The macros in signals.c test code does not have to worry about
versioning, since it is not part of the glib library proper.

Closes #2997
Signed-off-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
Eric Blake 2023-05-08 19:46:31 -05:00
parent 1461c29301
commit 2ab2ce57e6
4 changed files with 81 additions and 4 deletions

View File

@ -3401,6 +3401,7 @@ g_test_trap_assertions
g_assertion_message g_assertion_message
g_assertion_message_expr g_assertion_message_expr
g_assertion_message_cmpstr g_assertion_message_cmpstr
g_assertion_message_cmpint
g_assertion_message_cmpnum g_assertion_message_cmpnum
g_assertion_message_error g_assertion_message_error
g_test_assert_expected_messages_internal g_test_assert_expected_messages_internal

View File

@ -3486,6 +3486,29 @@ g_assertion_message_expr (const char *domain,
g_abort (); g_abort ();
} }
void
g_assertion_message_cmpint (const char *domain,
const char *file,
int line,
const char *func,
const char *expr,
guint64 arg1,
const char *cmp,
guint64 arg2,
char numtype)
{
char *s = NULL;
switch (numtype)
{
case 'i': s = g_strdup_printf ("assertion failed (%s): (%" G_GINT64_MODIFIER "i %s %" G_GINT64_MODIFIER "i)", expr, (gint64) arg1, cmp, (gint64) arg2); break;
case 'u': s = g_strdup_printf ("assertion failed (%s): (%" G_GINT64_MODIFIER "u %s %" G_GINT64_MODIFIER "u)", expr, arg1, cmp, arg2); break;
case 'x': s = g_strdup_printf ("assertion failed (%s): (0x%08" G_GINT64_MODIFIER "x %s 0x%08" G_GINT64_MODIFIER "x)", expr, arg1, cmp, arg2); break;
}
g_assertion_message (domain, file, line, func, s);
g_free (s);
}
void void
g_assertion_message_cmpnum (const char *domain, g_assertion_message_cmpnum (const char *domain,
const char *file, const char *file,
@ -3501,8 +3524,10 @@ g_assertion_message_cmpnum (const char *domain,
switch (numtype) switch (numtype)
{ {
case 'i': s = g_strdup_printf ("assertion failed (%s): (%" G_GINT64_MODIFIER "i %s %" G_GINT64_MODIFIER "i)", expr, (gint64) arg1, cmp, (gint64) arg2); break; case 'i':
case 'x': s = g_strdup_printf ("assertion failed (%s): (0x%08" G_GINT64_MODIFIER "x %s 0x%08" G_GINT64_MODIFIER "x)", expr, (guint64) arg1, cmp, (guint64) arg2); break; case 'x':
/* Backwards compatibility to apps compiled before 2.78 */
g_assertion_message_cmpint (domain, file, line, func, expr, (guint64) arg1, cmp, (guint64) arg2, numtype); break;
case 'f': s = g_strdup_printf ("assertion failed (%s): (%.9g %s %.9g)", expr, (double) arg1, cmp, (double) arg2); break; case 'f': s = g_strdup_printf ("assertion failed (%s): (%.9g %s %.9g)", expr, (double) arg1, cmp, (double) arg2); break;
/* ideally use: floats=%.7g double=%.17g */ /* ideally use: floats=%.7g double=%.17g */
} }

View File

@ -49,6 +49,26 @@ typedef void (*GTestFixtureFunc) (gpointer fixture,
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#s1 " " #cmp " " #s2, __s1, #cmp, __s2); \ #s1 " " #cmp " " #s2, __s1, #cmp, __s2); \
} G_STMT_END } G_STMT_END
#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_78
#define g_assert_cmpint(n1, cmp, n2) G_STMT_START { \
gint64 __n1 = (n1), __n2 = (n2); \
if (__n1 cmp __n2) ; else \
g_assertion_message_cmpint (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'i'); \
} G_STMT_END
#define g_assert_cmpuint(n1, cmp, n2) G_STMT_START { \
guint64 __n1 = (n1), __n2 = (n2); \
if (__n1 cmp __n2) ; else \
g_assertion_message_cmpint (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'u'); \
} G_STMT_END
#define g_assert_cmphex(n1, cmp, n2) G_STMT_START { \
guint64 __n1 = (n1), __n2 = (n2); \
if (__n1 cmp __n2) ; else \
g_assertion_message_cmpint (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'x'); \
} G_STMT_END
#else /* GLIB_VERSION_MIN_REQUIRED < GLIB_VERSION_2_78 */
#define g_assert_cmpint(n1, cmp, n2) G_STMT_START { \ #define g_assert_cmpint(n1, cmp, n2) G_STMT_START { \
gint64 __n1 = (n1), __n2 = (n2); \ gint64 __n1 = (n1), __n2 = (n2); \
if (__n1 cmp __n2) ; else \ if (__n1 cmp __n2) ; else \
@ -67,6 +87,7 @@ typedef void (*GTestFixtureFunc) (gpointer fixture,
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, (long double) __n1, #cmp, (long double) __n2, 'x'); \ #n1 " " #cmp " " #n2, (long double) __n1, #cmp, (long double) __n2, 'x'); \
} G_STMT_END } G_STMT_END
#endif /* GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_78 */
#define g_assert_cmpfloat(n1,cmp,n2) G_STMT_START { \ #define g_assert_cmpfloat(n1,cmp,n2) G_STMT_START { \
long double __n1 = (long double) (n1), __n2 = (long double) (n2); \ long double __n1 = (long double) (n1), __n2 = (long double) (n2); \
if (__n1 cmp __n2) ; else \ if (__n1 cmp __n2) ; else \
@ -80,6 +101,25 @@ typedef void (*GTestFixtureFunc) (gpointer fixture,
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " == " #n2 " (+/- " #epsilon ")", __n1, "==", __n2, 'f'); \ #n1 " == " #n2 " (+/- " #epsilon ")", __n1, "==", __n2, 'f'); \
} G_STMT_END } G_STMT_END
#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_78
#define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\
gconstpointer __m1 = m1, __m2 = m2; \
size_t __l1 = (size_t) l1, __l2 = (size_t) l2; \
if (__l1 != 0 && __m1 == NULL) \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
"assertion failed (" #l1 " == 0 || " #m1 " != NULL)"); \
else if (__l2 != 0 && __m2 == NULL) \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
"assertion failed (" #l2 " == 0 || " #m2 " != NULL)"); \
else if (__l1 != __l2) \
g_assertion_message_cmpint (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#l1 " (len(" #m1 ")) == " #l2 " (len(" #m2 "))", \
__l1, "==", __l2, 'u'); \
else if (__l1 != 0 && __m2 != NULL && memcmp (__m1, __m2, __l1) != 0) \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
"assertion failed (" #m1 " == " #m2 ")"); \
} G_STMT_END
#else /* GLIB_VERSION_MIN_REQUIRED < GLIB_VERSION_2_78 */
#define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ #define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\
gconstpointer __m1 = m1, __m2 = m2; \ gconstpointer __m1 = m1, __m2 = m2; \
size_t __l1 = (size_t) l1, __l2 = (size_t) l2; \ size_t __l1 = (size_t) l1, __l2 = (size_t) l2; \
@ -97,6 +137,7 @@ typedef void (*GTestFixtureFunc) (gpointer fixture,
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
"assertion failed (" #m1 " == " #m2 ")"); \ "assertion failed (" #m1 " == " #m2 ")"); \
} G_STMT_END } G_STMT_END
#endif /* GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_78 */
#define g_assert_cmpvariant(v1, v2) \ #define g_assert_cmpvariant(v1, v2) \
G_STMT_START \ G_STMT_START \
{ \ { \
@ -567,6 +608,16 @@ void g_assertion_message_cmpstrv (const char *domain,
const char * const *arg1, const char * const *arg1,
const char * const *arg2, const char * const *arg2,
gsize first_wrong_idx) G_ANALYZER_NORETURN; gsize first_wrong_idx) G_ANALYZER_NORETURN;
GLIB_AVAILABLE_IN_2_78
void g_assertion_message_cmpint (const char *domain,
const char *file,
int line,
const char *func,
const char *expr,
guint64 arg1,
const char *cmp,
guint64 arg2,
char numtype) G_ANALYZER_NORETURN;
GLIB_AVAILABLE_IN_ALL GLIB_AVAILABLE_IN_ALL
void g_assertion_message_cmpnum (const char *domain, void g_assertion_message_cmpnum (const char *domain,
const char *file, const char *file,

View File

@ -4,13 +4,13 @@
#define g_assert_cmpflags(type,n1, cmp, n2) G_STMT_START { \ #define g_assert_cmpflags(type,n1, cmp, n2) G_STMT_START { \
type __n1 = (n1), __n2 = (n2); \ type __n1 = (n1), __n2 = (n2); \
if (__n1 cmp __n2) ; else \ if (__n1 cmp __n2) ; else \
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpint (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'i'); \ #n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'i'); \
} G_STMT_END } G_STMT_END
#define g_assert_cmpenum(type,n1, cmp, n2) G_STMT_START { \ #define g_assert_cmpenum(type,n1, cmp, n2) G_STMT_START { \
type __n1 = (n1), __n2 = (n2); \ type __n1 = (n1), __n2 = (n2); \
if (__n1 cmp __n2) ; else \ if (__n1 cmp __n2) ; else \
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpint (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'i'); \ #n1 " " #cmp " " #n2, __n1, #cmp, __n2, 'i'); \
} G_STMT_END } G_STMT_END