diff --git a/glib/tests/meson.build b/glib/tests/meson.build
index a01649b77..0d913b879 100644
--- a/glib/tests/meson.build
+++ b/glib/tests/meson.build
@@ -291,6 +291,15 @@ test_extra_programs = {
'test-spawn-echo' : {},
}
+if have_dlopen_dlsym
+ test_extra_programs += {
+ 'messages-low-memory' : {
+ 'dependencies' : libdl_dep,
+ 'override_options' : ['b_asneeded=false'],
+ },
+ }
+endif
+
if host_machine.system() == 'windows'
# test-spawn-sleep helper binary required by the spawn tests above
test_extra_programs += {
@@ -334,6 +343,7 @@ foreach program_name, extra_args : test_extra_programs
sources: [source, extra_sources],
c_args : test_cargs,
cpp_args: test_cpp_args,
+ override_options : extra_args.get('override_options', []),
dependencies : test_deps + extra_args.get('dependencies', []),
install_dir : installed_tests_execdir,
install_tag : 'tests',
@@ -367,6 +377,7 @@ foreach test_name, extra_args : glib_tests
c_args : test_cargs + extra_args.get('c_args', []),
cpp_args : test_cpp_args + extra_args.get('cpp_args', []),
link_args : extra_args.get('link_args', []),
+ override_options : extra_args.get('override_options', []),
dependencies : test_deps + extra_args.get('dependencies', []),
install_dir: installed_tests_execdir,
install_tag: 'tests',
@@ -409,6 +420,14 @@ python_tests = {
},
}
+if 'messages-low-memory' in test_extra_programs
+ python_tests += {
+ 'messages-low-memory.py' : {
+ 'extra_programs': ['messages-low-memory'],
+ },
+ }
+endif
+
foreach test_name, extra_args : python_tests
depends = [extra_args.get('depends', [])]
suite = ['glib', 'no-valgrind']
diff --git a/glib/tests/messages-low-memory.c b/glib/tests/messages-low-memory.c
new file mode 100644
index 000000000..af35c4e26
--- /dev/null
+++ b/glib/tests/messages-low-memory.c
@@ -0,0 +1,88 @@
+/* Unit tests for gmessages on low-memory
+ *
+ * Copyright (C) 2022 Marco Trevisan
+ *
+ * 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 .
+ *
+ * Author: Marco Trevisan
+ */
+
+#include "config.h"
+
+#include
+#include
+
+static gboolean malloc_eom = FALSE;
+static gboolean our_malloc_called = FALSE;
+
+#ifdef ENOMEM
+/* Wrapper around malloc() which returns `ENOMEM` if the test variable
+ * `malloc_eom` is set.
+ * Otherwise passes through to the normal malloc() in libc.
+ */
+
+void *
+malloc (size_t size)
+{
+ static void *(*real_malloc)(size_t);
+ if (!real_malloc)
+ real_malloc = dlsym (RTLD_NEXT, "malloc");
+
+ if (malloc_eom)
+ {
+ our_malloc_called = TRUE;
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return real_malloc (size);
+}
+#endif
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_setenv ("LC_ALL", "C", TRUE);
+
+#ifndef ENOMEM
+ g_message ("ENOMEM Not defined, test skipped");
+ return 77;
+#endif
+
+ g_message ("Simulates a situation in which we were crashing because "
+ "of low-memory, leading malloc to fail instead of aborting");
+ g_message ("bug: https://gitlab.gnome.org/GNOME/glib/-/issues/2753");
+
+ /* Setting `malloc_eom` to true should cause the override `malloc()`
+ * in this file to fail on the allocation on the next line. */
+ malloc_eom = TRUE;
+ g_message ("Memory is exhausted, but we'll write anyway: %u", 123);
+
+#ifndef __linux__
+ if (!our_malloc_called)
+ {
+ /* For some reasons this doesn't work darwin systems, so ignore the result
+ * for non-linux, while we want to ensure the test is valid at least there
+ */
+ g_message ("Our malloc implementation has not been called, the test "
+ "has not been performed");
+ return 77;
+ }
+#endif
+
+ return 0;
+}
diff --git a/glib/tests/messages-low-memory.py b/glib/tests/messages-low-memory.py
new file mode 100644
index 000000000..8e071b164
--- /dev/null
+++ b/glib/tests/messages-low-memory.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2022 Emmanuel Fleury
+# Copyright © 2022 Marco Trevisan
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA
+
+""" Integration tests for g_message functions on low-memory. """
+
+import collections
+import os
+import subprocess
+import unittest
+
+import taptestrunner
+
+Result = collections.namedtuple("Result", ("info", "out", "err"))
+
+
+class TestMessagesLowMemory(unittest.TestCase):
+ """Integration test for checking g_message()’s behavior on low memory.
+
+ This can be run when installed or uninstalled. When uninstalled,
+ it requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set.
+
+ The idea with this test harness is to test if g_message and friends
+ assert instead of crashing if memory is exhausted, printing the expected
+ error message.
+ """
+
+ test_binary = "messages-low-memory"
+
+ def setUp(self):
+ ext = ""
+ if os.name == "nt":
+ ext = ".exe"
+ if "G_TEST_BUILDDIR" in os.environ:
+ self._test_binary = os.path.join(
+ os.environ["G_TEST_BUILDDIR"], self.test_binary + ext
+ )
+ else:
+ self._test_binary = os.path.join(
+ os.path.dirname(__file__), self.test_binary + ext
+ )
+ print("messages-low-memory:", self._test_binary)
+
+ def runTestBinary(self, *args):
+ print("Running:", *args)
+
+ env = os.environ.copy()
+ env["LC_ALL"] = "C.UTF-8"
+ print("Environment:", env)
+
+ # We want to ensure consistent line endings...
+ info = subprocess.run(
+ *args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env=env,
+ universal_newlines=True,
+ )
+ out = info.stdout.strip()
+ err = info.stderr.strip()
+
+ result = Result(info, out, err)
+
+ print("Return code:", result.info.returncode)
+ print("Output:", result.out)
+ print("Error:", result.err)
+ return result
+
+ def test_message_memory_allocation_failure(self):
+ """Test running g_message() when memory is exhausted."""
+ result = self.runTestBinary(self._test_binary)
+
+ if result.info.returncode == 77:
+ self.skipTest("Not supported")
+
+ if os.name == "nt":
+ self.assertEqual(result.info.returncode, 3)
+ else:
+ self.assertEqual(result.info.returncode, -6)
+ self.assertIn("failed to allocate memory", result.err)
+
+
+if __name__ == "__main__":
+ unittest.main(testRunner=taptestrunner.TAPTestRunner())