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