From 9a0e6fd00473ad2c529ec86249b48d88e893d165 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 27 Jul 2022 15:24:37 +0100 Subject: testlib: Be compatible with ancient GLib by using glnx_close_fd This is an enabler for testing a backport of GTest helpers (g_test_skip(), etc.) in the oldest environment that I have conveniently available (Steam Runtime 1 'scout' with GLib 2.32). Signed-off-by: Simon McVittie --- tests/libglnx-testlib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/libglnx-testlib.c b/tests/libglnx-testlib.c index 37b3ece..3eb2ba1 100644 --- a/tests/libglnx-testlib.c +++ b/tests/libglnx-testlib.c @@ -67,8 +67,7 @@ _glnx_test_auto_temp_dir_leave (_GLnxTestAutoTempDir *dir) glnx_tmpdir_delete (&dir->temp_dir, NULL, &error); g_assert_no_error (error); - g_close (dir->old_cwd_fd, &error); - g_assert_no_error (error); + glnx_close_fd (&dir->old_cwd_fd); g_free (dir->old_cwd); g_free (dir); -- cgit v1.2.1 From 5a60c0bca5d91ba701ca4a2f9bcfae9eec65253b Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sat, 2 Jul 2022 17:33:56 +0100 Subject: Move assertion/test utilities to a separate header A lot of recent convenience APIs can easily be backported, but they'll clutter glnx-backports.h. In preparation, split these out. Another reason to separate these is that when we start adding backports of test utilities that need implementation code in the libglnx static library, we'll want their implementations to be in a separate translation unit, so that they don't get linked into production executables, only into tests. Signed-off-by: Simon McVittie --- Makefile-libglnx.am | 1 + glnx-backport-testutils.h | 43 +++++++++++++++++++++++++++++++++++++++++++ glnx-backports.h | 12 ------------ libglnx.h | 1 + meson.build | 1 + 5 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 glnx-backport-testutils.h diff --git a/Makefile-libglnx.am b/Makefile-libglnx.am index bea7b13..f699ff2 100644 --- a/Makefile-libglnx.am +++ b/Makefile-libglnx.am @@ -33,6 +33,7 @@ libglnx_la_SOURCES = \ $(libglnx_srcpath)/glnx-macros.h \ $(libglnx_srcpath)/glnx-backport-autocleanups.h \ $(libglnx_srcpath)/glnx-backport-autoptr.h \ + $(libglnx_srcpath)/glnx-backport-testutils.h \ $(libglnx_srcpath)/glnx-backports.h \ $(libglnx_srcpath)/glnx-backports.c \ $(libglnx_srcpath)/glnx-local-alloc.h \ diff --git a/glnx-backport-testutils.h b/glnx-backport-testutils.h new file mode 100644 index 0000000..f43c204 --- /dev/null +++ b/glnx-backport-testutils.h @@ -0,0 +1,43 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright 2015 Colin Walters + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include + +#include "glnx-backports.h" + +G_BEGIN_DECLS + +#ifndef g_assert_nonnull +#define g_assert_nonnull(x) g_assert (x != NULL) +#endif + +#ifndef g_assert_null +#define g_assert_null(x) g_assert (x == NULL) +#endif + +#if !GLIB_CHECK_VERSION (2, 38, 0) +#define g_test_skip(s) g_test_message ("SKIP: %s", s) +#endif + +G_END_DECLS diff --git a/glnx-backports.h b/glnx-backports.h index 6f766a3..b535e2d 100644 --- a/glnx-backports.h +++ b/glnx-backports.h @@ -84,16 +84,4 @@ gboolean glnx_set_object (GObject **object_ptr, #define G_OPTION_ENTRY_NULL { NULL, 0, 0, 0, NULL, NULL, NULL } #endif -#ifndef g_assert_nonnull -#define g_assert_nonnull(x) g_assert (x != NULL) -#endif - -#ifndef g_assert_null -#define g_assert_null(x) g_assert (x == NULL) -#endif - -#if !GLIB_CHECK_VERSION (2, 38, 0) -#define g_test_skip(s) g_test_message ("SKIP: %s", s) -#endif - G_END_DECLS diff --git a/libglnx.h b/libglnx.h index ca82ba5..63d73ad 100644 --- a/libglnx.h +++ b/libglnx.h @@ -29,6 +29,7 @@ G_BEGIN_DECLS #include #include #include +#include #include #include #include diff --git a/meson.build b/meson.build index 4787c85..86535a8 100644 --- a/meson.build +++ b/meson.build @@ -57,6 +57,7 @@ libglnx_inc = include_directories('.') libglnx_sources = [ 'glnx-backport-autocleanups.h', 'glnx-backport-autoptr.h', + 'glnx-backport-testutils.h', 'glnx-backports.c', 'glnx-backports.h', 'glnx-console.c', -- cgit v1.2.1 From c52b73d4ae58ee2e3f1572c1b546d929ecdbd0c8 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 27 Jul 2022 15:51:41 +0100 Subject: Backport most of the test convenience helpers from GLib's GTest This includes a test (who tests the tests themselves?). Run as `${build}/tests/testing --tap` with semi-modern GLib, or `${build}/tests/testing --verbose` with GLib < 2.38, to check that the output is as reasonable as possible given the limitations of the GLib version in use. Signed-off-by: Simon McVittie --- Makefile-libglnx.am | 1 + glnx-backport-testutils.c | 145 +++++++++++++++++ glnx-backport-testutils.h | 157 ++++++++++++++++++- glnx-backports.h | 6 + meson.build | 1 + tests/meson.build | 11 ++ tests/test-libglnx-testing.c | 361 +++++++++++++++++++++++++++++++++++++++++++ tests/testing-helper.c | 327 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 1007 insertions(+), 2 deletions(-) create mode 100644 glnx-backport-testutils.c create mode 100644 tests/test-libglnx-testing.c create mode 100644 tests/testing-helper.c diff --git a/Makefile-libglnx.am b/Makefile-libglnx.am index f699ff2..5934a2b 100644 --- a/Makefile-libglnx.am +++ b/Makefile-libglnx.am @@ -34,6 +34,7 @@ libglnx_la_SOURCES = \ $(libglnx_srcpath)/glnx-backport-autocleanups.h \ $(libglnx_srcpath)/glnx-backport-autoptr.h \ $(libglnx_srcpath)/glnx-backport-testutils.h \ + $(libglnx_srcpath)/glnx-backport-testutils.c \ $(libglnx_srcpath)/glnx-backports.h \ $(libglnx_srcpath)/glnx-backports.c \ $(libglnx_srcpath)/glnx-local-alloc.h \ diff --git a/glnx-backport-testutils.c b/glnx-backport-testutils.c new file mode 100644 index 0000000..31105c9 --- /dev/null +++ b/glnx-backport-testutils.c @@ -0,0 +1,145 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright 2015 Colin Walters + * Copyright 2020 Niels De Graef + * Copyright 2021-2022 Collabora Ltd. + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "libglnx-config.h" + +#include +#include + +#include + +#include "glnx-backport-autocleanups.h" +#include "glnx-backport-autoptr.h" +#include "glnx-backport-testutils.h" +#include "glnx-backports.h" + +#if !GLIB_CHECK_VERSION (2, 68, 0) +/* Backport of g_assertion_message_cmpstrv() */ +void +_glnx_assertion_message_cmpstrv (const char *domain, + const char *file, + int line, + const char *func, + const char *expr, + const char * const *arg1, + const char * const *arg2, + gsize first_wrong_idx) +{ + const char *s1 = arg1[first_wrong_idx], *s2 = arg2[first_wrong_idx]; + char *a1, *a2, *s, *t1 = NULL, *t2 = NULL; + + a1 = g_strconcat ("\"", t1 = g_strescape (s1, NULL), "\"", NULL); + a2 = g_strconcat ("\"", t2 = g_strescape (s2, NULL), "\"", NULL); + g_free (t1); + g_free (t2); + s = g_strdup_printf ("assertion failed (%s): first differing element at index %" G_GSIZE_FORMAT ": %s does not equal %s", + expr, first_wrong_idx, a1, a2); + g_free (a1); + g_free (a2); + g_assertion_message (domain, file, line, func, s); + g_free (s); +} +#endif + +#if !GLIB_CHECK_VERSION(2, 70, 0) +/* + * Same as g_test_message(), but split messages with newlines into + * multiple separate messages to avoid corrupting stdout, even in older + * GLib versions that didn't do this + */ +void +_glnx_test_message_safe (const char *format, + ...) +{ + g_autofree char *message = NULL; + va_list ap; + char *line; + char *saveptr = NULL; + + va_start (ap, format); + g_vasprintf (&message, format, ap); + va_end (ap); + + for (line = strtok_r (message, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + (g_test_message) ("%s", line); +} + +/* Backport of g_test_fail_printf() */ +void +_glnx_test_fail_printf (const char *format, + ...) +{ + g_autofree char *message = NULL; + va_list ap; + + va_start (ap, format); + g_vasprintf (&message, format, ap); + va_end (ap); + + /* This is the closest we can do in older GLib */ + g_test_message ("Bail out! %s", message); + g_test_fail (); +} + +/* Backport of g_test_skip_printf() */ +void +_glnx_test_skip_printf (const char *format, + ...) +{ + g_autofree char *message = NULL; + va_list ap; + + va_start (ap, format); + g_vasprintf (&message, format, ap); + va_end (ap); + + g_test_skip (message); +} + +/* Backport of g_test_incomplete_printf() */ +void +_glnx_test_incomplete_printf (const char *format, + ...) +{ + g_autofree char *message = NULL; + va_list ap; + + va_start (ap, format); + g_vasprintf (&message, format, ap); + va_end (ap); + +#if GLIB_CHECK_VERSION(2, 58, 0) + /* Since 2.58, g_test_incomplete() sets the exit status correctly. */ + g_test_incomplete (message); +#elif GLIB_CHECK_VERSION (2, 38, 0) + /* Before 2.58, g_test_incomplete() was treated like a failure for the + * purposes of setting the exit status, so prefer to use (our wrapper + * around) g_test_skip(). */ + g_test_skip_printf ("TODO: %s", message); +#else + g_test_message ("TODO: %s", message); +#endif +} +#endif diff --git a/glnx-backport-testutils.h b/glnx-backport-testutils.h index f43c204..2febcc0 100644 --- a/glnx-backport-testutils.h +++ b/glnx-backport-testutils.h @@ -2,6 +2,12 @@ * * Copyright 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * Copyright 2015 Colin Walters + * Copyright 2014 Dan Winship + * Copyright 2015 Colin Walters + * Copyright 2017 Emmanuele Bassi + * Copyright 2018-2019 Endless OS Foundation LLC + * Copyright 2020 Niels De Graef + * Copyright 2021-2022 Collabora Ltd. * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or @@ -28,16 +34,163 @@ G_BEGIN_DECLS -#ifndef g_assert_nonnull +#ifndef g_assert_nonnull /* added in 2.40 */ #define g_assert_nonnull(x) g_assert (x != NULL) #endif -#ifndef g_assert_null +#ifndef g_assert_null /* added in 2.40 */ #define g_assert_null(x) g_assert (x == NULL) #endif #if !GLIB_CHECK_VERSION (2, 38, 0) +/* Not exactly equivalent, but close enough */ #define g_test_skip(s) g_test_message ("SKIP: %s", s) #endif +#if !GLIB_CHECK_VERSION (2, 58, 0) +/* Before 2.58, g_test_incomplete() didn't set the exit status correctly */ +#define g_test_incomplete(s) _glnx_test_incomplete_printf ("%s", s) +#endif + +#if !GLIB_CHECK_VERSION (2, 46, 0) +#define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ + gconstpointer __m1 = m1, __m2 = m2; \ + int __l1 = l1, __l2 = 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_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #l1 " (len(" #m1 ")) == " #l2 " (len(" #m2 "))", \ + (long double) __l1, "==", (long double) __l2, 'i'); \ + 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 +#endif + +#if !GLIB_CHECK_VERSION (2, 58, 0) +#define g_assert_cmpfloat_with_epsilon(n1,n2,epsilon) \ + G_STMT_START { \ + double __n1 = (n1), __n2 = (n2), __epsilon = (epsilon); \ + if (G_APPROX_VALUE (__n1, __n2, __epsilon)) ; else \ + g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #n1 " == " #n2 " (+/- " #epsilon ")", __n1, "==", __n2, 'f'); \ + } G_STMT_END +#endif + +#if !GLIB_CHECK_VERSION (2, 60, 0) +#define g_assert_cmpvariant(v1, v2) \ + G_STMT_START \ + { \ + GVariant *__v1 = (v1), *__v2 = (v2); \ + if (!g_variant_equal (__v1, __v2)) \ + { \ + gchar *__s1, *__s2, *__msg; \ + __s1 = g_variant_print (__v1, TRUE); \ + __s2 = g_variant_print (__v2, TRUE); \ + __msg = g_strdup_printf ("assertion failed (" #v1 " == " #v2 "): %s does not equal %s", __s1, __s2); \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, __msg); \ + g_free (__s1); \ + g_free (__s2); \ + g_free (__msg); \ + } \ + } \ + G_STMT_END +#endif + +#if !GLIB_CHECK_VERSION (2, 62, 0) +/* Not exactly equivalent, but close enough */ +#define g_test_summary(s) g_test_message ("SUMMARY: %s", s) +#endif + +#if !GLIB_CHECK_VERSION (2, 66, 0) +#define g_assert_no_errno(expr) G_STMT_START { \ + int __ret, __errsv; \ + errno = 0; \ + __ret = expr; \ + __errsv = errno; \ + if (__ret < 0) \ + { \ + gchar *__msg; \ + __msg = g_strdup_printf ("assertion failed (" #expr " >= 0): errno %i: %s", __errsv, g_strerror (__errsv)); \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, __msg); \ + g_free (__msg); \ + } \ + } G_STMT_END +#endif + +#if !GLIB_CHECK_VERSION (2, 68, 0) +#define g_assertion_message_cmpstrv _glnx_assertion_message_cmpstrv +void _glnx_assertion_message_cmpstrv (const char *domain, + const char *file, + int line, + const char *func, + const char *expr, + const char * const *arg1, + const char * const *arg2, + gsize first_wrong_idx); +#define g_assert_cmpstrv(strv1, strv2) \ + G_STMT_START \ + { \ + const char * const *__strv1 = (const char * const *) (strv1); \ + const char * const *__strv2 = (const char * const *) (strv2); \ + if (!__strv1 || !__strv2) \ + { \ + if (__strv1) \ + { \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "assertion failed (" #strv1 " == " #strv2 "): " #strv2 " is NULL, but " #strv1 " is not"); \ + } \ + else if (__strv2) \ + { \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "assertion failed (" #strv1 " == " #strv2 "): " #strv1 " is NULL, but " #strv2 " is not"); \ + } \ + } \ + else \ + { \ + guint __l1 = g_strv_length ((char **) __strv1); \ + guint __l2 = g_strv_length ((char **) __strv2); \ + if (__l1 != __l2) \ + { \ + char *__msg; \ + __msg = g_strdup_printf ("assertion failed (" #strv1 " == " #strv2 "): length %u does not equal length %u", __l1, __l2); \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, __msg); \ + g_free (__msg); \ + } \ + else \ + { \ + guint __i; \ + for (__i = 0; __i < __l1; __i++) \ + { \ + if (g_strcmp0 (__strv1[__i], __strv2[__i]) != 0) \ + { \ + g_assertion_message_cmpstrv (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #strv1 " == " #strv2, \ + __strv1, __strv2, __i); \ + } \ + } \ + } \ + } \ + } \ + G_STMT_END +#endif + +#if !GLIB_CHECK_VERSION (2, 70, 0) +/* Before 2.70, diagnostic messages containing newlines were problematic */ +#define g_test_message(...) _glnx_test_message_safe (__VA_ARGS__) +void _glnx_test_message_safe (const char *format, ...) G_GNUC_PRINTF (1, 2); + +#define g_test_fail_printf _glnx_test_fail_printf +void _glnx_test_fail_printf (const char *format, ...) G_GNUC_PRINTF (1, 2); +#define g_test_skip_printf _glnx_test_skip_printf +void _glnx_test_skip_printf (const char *format, ...) G_GNUC_PRINTF (1, 2); +#define g_test_incomplete_printf _glnx_test_incomplete_printf +void _glnx_test_incomplete_printf (const char *format, ...) G_GNUC_PRINTF (1, 2); +#endif + G_END_DECLS diff --git a/glnx-backports.h b/glnx-backports.h index b535e2d..71c3f5c 100644 --- a/glnx-backports.h +++ b/glnx-backports.h @@ -1,6 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2015 Colin Walters + * Copyright 2017 Emmanuele Bassi * SPDX-License-Identifier: LGPL-2.0-or-later * * GLIB - Library of useful routines for C programming @@ -84,4 +85,9 @@ gboolean glnx_set_object (GObject **object_ptr, #define G_OPTION_ENTRY_NULL { NULL, 0, 0, 0, NULL, NULL, NULL } #endif +#ifndef G_APPROX_VALUE /* added in 2.58 */ +#define G_APPROX_VALUE(a, b, epsilon) \ + (((a) > (b) ? (a) - (b) : (b) - (a)) < (epsilon)) +#endif + G_END_DECLS diff --git a/meson.build b/meson.build index 86535a8..9b871db 100644 --- a/meson.build +++ b/meson.build @@ -57,6 +57,7 @@ libglnx_inc = include_directories('.') libglnx_sources = [ 'glnx-backport-autocleanups.h', 'glnx-backport-autoptr.h', + 'glnx-backport-testutils.c', 'glnx-backport-testutils.h', 'glnx-backports.c', 'glnx-backports.h', diff --git a/tests/meson.build b/tests/meson.build index 2c38ab0..2d0a976 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -22,11 +22,22 @@ libglnx_testlib_dep = declare_dependency( ) if get_option('tests') + executable( + 'testing-helper', + 'testing-helper.c', + dependencies : [ + libglnx_dep, + libglnx_deps, + ], + install : false, + ) + test_names = [ 'errors', 'fdio', 'macros', 'shutil', + 'testing', 'xattrs', ] diff --git a/tests/test-libglnx-testing.c b/tests/test-libglnx-testing.c new file mode 100644 index 0000000..279e0e1 --- /dev/null +++ b/tests/test-libglnx-testing.c @@ -0,0 +1,361 @@ +/* + * Copyright 2022 Simon McVittie + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see + * . + */ + +#include "libglnx-config.h" +#include "libglnx.h" + +#include +#include + +#include +#include +#include +#include + +#if GLIB_CHECK_VERSION (2, 38, 0) +#define GTEST_TAP_OR_VERBOSE "--tap" +#else +#define GTEST_TAP_OR_VERBOSE "--verbose" +#endif + +static const char *null = NULL; +static const char *nonnull = "not null"; + +static void +test_assertions (void) +{ + const char *other_nonnull = "not null"; + g_autoptr(GVariant) va = g_variant_ref_sink (g_variant_new ("i", 42)); + g_autoptr(GVariant) vb = g_variant_ref_sink (g_variant_new ("i", 42)); + const char * const strv1[] = {"one", "two", NULL}; + const char * const strv2[] = {"one", "two", NULL}; + GStatBuf statbuf; + + g_assert_null (null); + g_assert_nonnull (nonnull); + g_assert_cmpmem (null, 0, null, 0); + g_assert_cmpmem (nonnull, strlen (nonnull), other_nonnull, strlen (other_nonnull)); + g_assert_cmpfloat_with_epsilon (1.0, 1.00001, 0.01); + g_assert_cmpvariant (va, vb); + g_assert_no_errno (g_stat ("/", &statbuf)); + g_assert_cmpstrv (NULL, NULL); + g_assert_cmpstrv (&null, &null); + g_assert_cmpstrv (strv1, strv2); +} + +static void +test_assertion_failures (void) +{ + static const char * const assertion_failures[] = + { + "nonnull", + "null", + "mem_null_nonnull", + "mem_nonnull_null", + "mem_len", + "mem_cmp", + "cmpfloat_with_epsilon", + "cmpvariant", + "errno", + "cmpstrv_null_nonnull", + "cmpstrv_nonnull_null", + "cmpstrv_len", + "cmpstrv_cmp", + }; + g_autoptr(GError) error = NULL; + g_autofree char *self = NULL; + g_autofree char *dir = NULL; + g_autofree char *exe = NULL; + gsize i; + + self = glnx_readlinkat_malloc (-1, "/proc/self/exe", NULL, &error); + g_assert_no_error (error); + + dir = g_path_get_dirname (self); + exe = g_build_filename (dir, "testing-helper", NULL); + + for (i = 0; i < G_N_ELEMENTS (assertion_failures); i++) + { + g_autofree char *out = NULL; + g_autofree char *err = NULL; + g_autofree char *name = g_strdup_printf ("/assertion-failure/%s", assertion_failures[i]); + int wait_status = -1; + const char *argv[] = { NULL, "assertion-failures", "-p", NULL, NULL, NULL }; + char *line; + char *saveptr = NULL; + + argv[0] = exe; + argv[3] = name; + argv[4] = GTEST_TAP_OR_VERBOSE; + g_test_message ("%s assertion-failures -p %s %s...", exe, name, GTEST_TAP_OR_VERBOSE); + + g_spawn_sync (NULL, /* cwd */ + (char **) argv, + NULL, /* envp */ + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_assert_nonnull (out); + g_assert_nonnull (err); + + for (line = strtok_r (out, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stdout: %s", line); + + saveptr = NULL; + + for (line = strtok_r (err, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stderr: %s", line); + + g_test_message ("wait status: 0x%x", wait_status); + + /* It exited with a nonzero status that was not exit status 77 */ + G_STATIC_ASSERT (WIFEXITED (0)); + G_STATIC_ASSERT (WEXITSTATUS (0) == 0); + g_assert_cmphex (wait_status, !=, 0); + G_STATIC_ASSERT (WIFEXITED (77 << 8)); + G_STATIC_ASSERT (WEXITSTATUS (77 << 8) == 77); + g_assert_cmphex (wait_status, !=, (77 << 8)); + } +} + +static void +test_failures (void) +{ + static const char * const failures[] = + { + "fail", + "fail-printf", + }; + g_autoptr(GError) error = NULL; + g_autofree char *self = NULL; + g_autofree char *dir = NULL; + g_autofree char *exe = NULL; + gsize i; + + self = glnx_readlinkat_malloc (-1, "/proc/self/exe", NULL, &error); + g_assert_no_error (error); + + dir = g_path_get_dirname (self); + exe = g_build_filename (dir, "testing-helper", NULL); + + for (i = 0; i < G_N_ELEMENTS (failures); i++) + { + g_autofree char *out = NULL; + g_autofree char *err = NULL; + int wait_status = -1; + const char *argv[] = { NULL, NULL, NULL, NULL }; + char *line; + char *saveptr; + + argv[0] = exe; + argv[1] = failures[i]; + argv[2] = GTEST_TAP_OR_VERBOSE; + g_test_message ("%s %s %s...", exe, failures[i], GTEST_TAP_OR_VERBOSE); + + g_spawn_sync (NULL, /* cwd */ + (char **) argv, + NULL, /* envp */ + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + for (line = strtok_r (out, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stdout: %s", line); + + for (line = strtok_r (err, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stderr: %s", line); + + g_test_message ("wait status: 0x%x", wait_status); + + G_STATIC_ASSERT (WIFEXITED (0)); + G_STATIC_ASSERT (WEXITSTATUS (0) == 0); + G_STATIC_ASSERT (WIFEXITED (77 << 8)); + G_STATIC_ASSERT (WEXITSTATUS (77 << 8) == 77); + + g_assert_cmphex (wait_status, !=, 0); + g_assert_cmphex (wait_status, !=, (77 << 8)); + } +} + +static void +test_skips (void) +{ + static const char * const skips[] = + { + "skip", + "skip-printf", + "incomplete", + "incomplete-printf", + }; + g_autoptr(GError) error = NULL; + g_autofree char *self = NULL; + g_autofree char *dir = NULL; + g_autofree char *exe = NULL; + gsize i; + + self = glnx_readlinkat_malloc (-1, "/proc/self/exe", NULL, &error); + g_assert_no_error (error); + + dir = g_path_get_dirname (self); + exe = g_build_filename (dir, "testing-helper", NULL); + + for (i = 0; i < G_N_ELEMENTS (skips); i++) + { + g_autofree char *out = NULL; + g_autofree char *err = NULL; + int wait_status = -1; + const char *argv[] = { NULL, NULL, NULL, NULL }; + char *line; + char *saveptr; + + argv[0] = exe; + argv[1] = skips[i]; + argv[2] = GTEST_TAP_OR_VERBOSE; + g_test_message ("%s %s %s...", exe, skips[i], GTEST_TAP_OR_VERBOSE); + + g_spawn_sync (NULL, /* cwd */ + (char **) argv, + NULL, /* envp */ + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + for (line = strtok_r (out, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stdout: %s", line); + + for (line = strtok_r (err, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stderr: %s", line); + + g_test_message ("wait status: 0x%x", wait_status); + + G_STATIC_ASSERT (WIFEXITED (0)); + G_STATIC_ASSERT (WEXITSTATUS (0) == 0); + G_STATIC_ASSERT (WIFEXITED (77 << 8)); + G_STATIC_ASSERT (WEXITSTATUS (77 << 8) == 77); + + /* Ideally the exit status is 77, but it might be 0 with older GLib */ + if (wait_status != 0) + g_assert_cmphex (wait_status, ==, (77 << 8)); + } +} + +static void +test_successes (void) +{ + static const char * const successes[] = + { + "messages", + "pass", + "summary", + }; + g_autoptr(GError) error = NULL; + g_autofree char *self = NULL; + g_autofree char *dir = NULL; + g_autofree char *exe = NULL; + gsize i; + + self = glnx_readlinkat_malloc (-1, "/proc/self/exe", NULL, &error); + g_assert_no_error (error); + + dir = g_path_get_dirname (self); + exe = g_build_filename (dir, "testing-helper", NULL); + + for (i = 0; i < G_N_ELEMENTS (successes); i++) + { + g_autofree char *out = NULL; + g_autofree char *err = NULL; + int wait_status = -1; + const char *argv[] = { NULL, NULL, NULL, NULL }; + char *line; + char *saveptr; + + argv[0] = exe; + argv[1] = successes[i]; + argv[2] = GTEST_TAP_OR_VERBOSE; + g_test_message ("%s %s %s...", exe, successes[i], GTEST_TAP_OR_VERBOSE); + + g_spawn_sync (NULL, /* cwd */ + (char **) argv, + NULL, /* envp */ + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + for (line = strtok_r (out, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stdout: %s", line); + + for (line = strtok_r (err, "\n", &saveptr); + line != NULL; + line = strtok_r (NULL, "\n", &saveptr)) + g_test_message ("stderr: %s", line); + + g_test_message ("wait status: 0x%x", wait_status); + + G_STATIC_ASSERT (WIFEXITED (0)); + G_STATIC_ASSERT (WEXITSTATUS (0) == 0); + g_assert_cmphex (wait_status, ==, 0); + } +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/assertions", test_assertions); + g_test_add_func ("/assertion_failures", test_assertion_failures); + g_test_add_func ("/failures", test_failures); + g_test_add_func ("/skips", test_skips); + g_test_add_func ("/successes", test_successes); + return g_test_run(); +} diff --git a/tests/testing-helper.c b/tests/testing-helper.c new file mode 100644 index 0000000..4e00fbe --- /dev/null +++ b/tests/testing-helper.c @@ -0,0 +1,327 @@ +/* + * Based on glib/tests/testing-helper.c from GLib + * + * Copyright 2018-2022 Collabora Ltd. + * Copyright 2019 Руслан Ижбулатов + * Copyright 2018-2022 Endless OS Foundation LLC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see + * . + */ + +#include "libglnx-config.h" +#include "libglnx.h" + +#include +#include +#include +#include +#include +#include + +static const char *null = NULL; +static const char *nonnull = "not null"; + +static void +test_pass (void) +{ +} + +static void +test_messages (void) +{ + g_test_message ("This message has multiple lines.\n" + "In older GLib, it would corrupt TAP output.\n" + "That's why libglnx provides a wrapper.\n"); +} + +static void +test_assertion_failure_nonnull (void) +{ + g_assert_nonnull (null); +} + +static void +test_assertion_failure_null (void) +{ + g_assert_null (nonnull); +} + +static void +test_assertion_failure_mem_null_nonnull (void) +{ + g_assert_cmpmem (null, 0, nonnull, strlen (nonnull)); +} + +static void +test_assertion_failure_mem_nonnull_null (void) +{ + g_assert_cmpmem (nonnull, strlen (nonnull), null, 0); +} + +static void +test_assertion_failure_mem_len (void) +{ + g_assert_cmpmem (nonnull, strlen (nonnull), nonnull, 0); +} + +static void +test_assertion_failure_mem_cmp (void) +{ + g_assert_cmpmem (nonnull, 4, nonnull + 4, 4); +} + +static void +test_assertion_failure_cmpfloat_with_epsilon (void) +{ + g_assert_cmpfloat_with_epsilon (1.0, 1.5, 0.001); +} + +static void +test_assertion_failure_cmpvariant (void) +{ + g_autoptr(GVariant) a = g_variant_ref_sink (g_variant_new ("i", 42)); + g_autoptr(GVariant) b = g_variant_ref_sink (g_variant_new ("u", 42)); + + g_assert_cmpvariant (a, b); +} + +static void +test_assertion_failure_errno (void) +{ + g_assert_no_errno (mkdir ("/", 0755)); +} + +static void +test_assertion_failure_cmpstrv_null_nonnull (void) +{ + const char * const b[] = { NULL }; + + g_assert_cmpstrv (NULL, b); +} + +static void +test_assertion_failure_cmpstrv_nonnull_null (void) +{ + const char * const a[] = { NULL }; + + g_assert_cmpstrv (a, NULL); +} + +static void +test_assertion_failure_cmpstrv_len (void) +{ + const char * const a[] = { "one", NULL }; + const char * const b[] = { NULL }; + + g_assert_cmpstrv (a, b); +} + +static void +test_assertion_failure_cmpstrv_cmp (void) +{ + const char * const a[] = { "one", "two", NULL }; + const char * const b[] = { "one", "three", NULL }; + + g_assert_cmpstrv (a, b); +} + +static void +test_skip (void) +{ + g_test_skip ("not enough tea"); +} + +static void +test_skip_printf (void) +{ + const char *beverage = "coffee"; + + g_test_skip_printf ("not enough %s", beverage); +} + +static void +test_fail (void) +{ + g_test_fail (); +} + +static void +test_fail_printf (void) +{ + g_test_fail_printf ("this test intentionally left failing"); +} + +static void +test_incomplete (void) +{ + g_test_incomplete ("mind reading not implemented yet"); +} + +static void +test_incomplete_printf (void) +{ + const char *operation = "telekinesis"; + + g_test_incomplete_printf ("%s not implemented yet", operation); +} + +static void +test_summary (void) +{ + g_test_summary ("Tests that g_test_summary() works with TAP, by outputting a " + "known summary message in testing-helper, and checking for " + "it in the TAP output later."); +} + +int +main (int argc, + char *argv[]) +{ + char *argv1; + + setlocale (LC_ALL, ""); + +#ifdef G_OS_WIN32 + /* Windows opens std streams in text mode, with \r\n EOLs. + * Sometimes it's easier to force a switch to binary mode than + * to account for extra \r in testcases. + */ + setmode (fileno (stdout), O_BINARY); +#endif + + g_return_val_if_fail (argc > 1, 1); + argv1 = argv[1]; + + if (argc > 2) + memmove (&argv[1], &argv[2], (argc - 2) * sizeof (char *)); + + argc -= 1; + argv[argc] = NULL; + + if (g_strcmp0 (argv1, "init-null-argv0") == 0) + { + int test_argc = 0; + char *test_argva[1] = { NULL }; + char **test_argv = test_argva; + + /* Test that `g_test_init()` can handle being called with an empty argv + * and argc == 0. While this isn’t recommended, it is possible for another + * process to use execve() to call a gtest process this way, so we’d + * better handle it gracefully. + * + * This test can’t be run after `g_test_init()` has been called normally, + * as it isn’t allowed to be called more than once in a process. */ + g_test_init (&test_argc, &test_argv, NULL); + + return 0; + } + + g_test_init (&argc, &argv, NULL); +#if GLIB_CHECK_VERSION(2, 38, 0) + g_test_set_nonfatal_assertions (); +#endif + + if (g_strcmp0 (argv1, "pass") == 0) + { + g_test_add_func ("/pass", test_pass); + } + else if (g_strcmp0 (argv1, "messages") == 0) + { + g_test_add_func ("/messages", test_messages); + } + else if (g_strcmp0 (argv1, "skip") == 0) + { + g_test_add_func ("/skip", test_skip); + } + else if (g_strcmp0 (argv1, "skip-printf") == 0) + { + g_test_add_func ("/skip-printf", test_skip_printf); + } + else if (g_strcmp0 (argv1, "incomplete") == 0) + { + g_test_add_func ("/incomplete", test_incomplete); + } + else if (g_strcmp0 (argv1, "incomplete-printf") == 0) + { + g_test_add_func ("/incomplete-printf", test_incomplete_printf); + } + else if (g_strcmp0 (argv1, "fail") == 0) + { + g_test_add_func ("/fail", test_fail); + } + else if (g_strcmp0 (argv1, "fail-printf") == 0) + { + g_test_add_func ("/fail-printf", test_fail_printf); + } + else if (g_strcmp0 (argv1, "all-non-failures") == 0) + { + g_test_add_func ("/pass", test_pass); + g_test_add_func ("/skip", test_skip); + g_test_add_func ("/incomplete", test_incomplete); + } + else if (g_strcmp0 (argv1, "all") == 0) + { + g_test_add_func ("/pass", test_pass); + g_test_add_func ("/skip", test_skip); + g_test_add_func ("/incomplete", test_incomplete); + g_test_add_func ("/fail", test_fail); + } + else if (g_strcmp0 (argv1, "skip-options") == 0) + { + /* The caller is expected to skip some of these with + * -p/-r, -s/-x and/or --GTestSkipCount */ + g_test_add_func ("/a", test_pass); + g_test_add_func ("/b", test_pass); + g_test_add_func ("/b/a", test_pass); + g_test_add_func ("/b/b", test_pass); + g_test_add_func ("/b/b/a", test_pass); + g_test_add_func ("/prefix/a", test_pass); + g_test_add_func ("/prefix/b/b", test_pass); + g_test_add_func ("/prefix-long/a", test_pass); + g_test_add_func ("/c/a", test_pass); + g_test_add_func ("/d/a", test_pass); + } + else if (g_strcmp0 (argv1, "summary") == 0) + { + g_test_add_func ("/summary", test_summary); + } + else if (g_strcmp0 (argv1, "assertion-failures") == 0) + { + /* Use -p to select a specific one of these */ +#define T(x) g_test_add_func ("/assertion-failure/" #x, test_assertion_failure_ ## x) + T (nonnull); + T (null); + T (mem_null_nonnull); + T (mem_nonnull_null); + T (mem_len); + T (mem_cmp); + T (cmpfloat_with_epsilon); + T (cmpvariant); + T (errno); + T (cmpstrv_null_nonnull); + T (cmpstrv_nonnull_null); + T (cmpstrv_len); + T (cmpstrv_cmp); +#undef T + } + else + { + g_assert_not_reached (); + } + + return g_test_run (); +} -- cgit v1.2.1