1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright © 2022 Emmanuel Fleury <emmanuel.fleury@gmail.com>
5 #
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301 USA
20
21 """ Integration tests for g_assert() functions. """
22
23 import collections
24 import os
25 import shutil
26 import subprocess
27 import tempfile
28 import unittest
29
30 import taptestrunner
31
32 Result = collections.namedtuple("Result", ("info", "out", "err"))
33
34 GDB_SCRIPT = """
35 # Work around https://sourceware.org/bugzilla/show_bug.cgi?id=22501
36 set confirm off
37 set print elements 0
38 set auto-load safe-path /
39 run
40 print *((char**) &__glib_assert_msg)
41 quit
42 """
43
44
45 class ESC[4;38;5;81mTestAssertMessage(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
46 """Integration test for throwing message on g_assert().
47
48 This can be run when installed or uninstalled. When uninstalled,
49 it requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set.
50
51 The idea with this test harness is to test if g_assert() prints
52 an error message when called, and that it saves this error
53 message in a global variable accessible to gdb, so that developers
54 and automated tools can more easily debug assertion failures.
55 """
56
57 def setUp(self):
58 self.__gdb = shutil.which("gdb")
59 self.timeout_seconds = 10 # seconds per test
60
61 ext = ""
62 if os.name == "nt":
63 ext = ".exe"
64 if "G_TEST_BUILDDIR" in os.environ:
65 self.__assert_msg_test = os.path.join(
66 os.environ["G_TEST_BUILDDIR"], "assert-msg-test" + ext
67 )
68 else:
69 self.__assert_msg_test = os.path.join(
70 os.path.dirname(__file__), "assert-msg-test" + ext
71 )
72 print("assert-msg-test:", self.__assert_msg_test)
73
74 def runAssertMessage(self, *args):
75 argv = [self.__assert_msg_test]
76 argv.extend(args)
77 print("Running:", argv)
78
79 env = os.environ.copy()
80 env["LC_ALL"] = "C.UTF-8"
81 print("Environment:", env)
82
83 # We want to ensure consistent line endings...
84 info = subprocess.run(
85 argv,
86 timeout=self.timeout_seconds,
87 stdout=subprocess.PIPE,
88 stderr=subprocess.PIPE,
89 env=env,
90 universal_newlines=True,
91 )
92 out = info.stdout.strip()
93 err = info.stderr.strip()
94
95 result = Result(info, out, err)
96
97 print("Output:", result.out)
98 print("Error:", result.err)
99 return result
100
101 def runGdbAssertMessage(self, *args):
102 if self.__gdb is None:
103 return Result(None, "", "")
104
105 argv = ["gdb", "-n", "--batch"]
106 argv.extend(args)
107 print("Running:", argv)
108
109 env = os.environ.copy()
110 env["LC_ALL"] = "C.UTF-8"
111 print("Environment:", env)
112
113 # We want to ensure consistent line endings...
114 info = subprocess.run(
115 argv,
116 timeout=self.timeout_seconds,
117 stdout=subprocess.PIPE,
118 stderr=subprocess.PIPE,
119 env=env,
120 universal_newlines=True,
121 )
122 out = info.stdout.strip()
123 err = info.stderr.strip()
124
125 result = Result(info, out, err)
126
127 print("Output:", result.out)
128 print("Error:", result.err)
129 print(result.info)
130 return result
131
132 def test_gassert(self):
133 """Test running g_assert() and fail the program."""
134 result = self.runAssertMessage()
135
136 if os.name == "nt":
137 self.assertEqual(result.info.returncode, 3)
138 else:
139 self.assertEqual(result.info.returncode, -6)
140 self.assertIn("assertion failed: (42 < 0)", result.out)
141
142 def test_gdb_gassert(self):
143 """Test running g_assert() within gdb and fail the program."""
144 if self.__gdb is None:
145 self.skipTest("GDB is not installed, skipping this test!")
146
147 with tempfile.NamedTemporaryFile(
148 prefix="assert-msg-test-", suffix=".gdb", mode="w", delete=False
149 ) as tmp:
150 try:
151 tmp.write(GDB_SCRIPT)
152 tmp.close()
153 result = self.runGdbAssertMessage(
154 "-x", tmp.name, self.__assert_msg_test
155 )
156 finally:
157 os.unlink(tmp.name)
158
159 # Some CI environments disable ptrace (as they’re running in a
160 # container). If so, skip the test as there’s nothing we can do.
161 if result.info.returncode != 0 and (
162 "ptrace: Operation not permitted" in result.err
163 or "warning: opening /proc/PID/mem file for lwp" in result.err
164 ):
165 self.skipTest("GDB is not functional due to ptrace being disabled")
166
167 self.assertEqual(result.info.returncode, 0)
168 self.assertIn("$1 = 0x", result.out)
169 self.assertIn("assertion failed: (42 < 0)", result.out)
170
171
172 if __name__ == "__main__":
173 unittest.main(testRunner=taptestrunner.TAPTestRunner())