1/* A self-testing framework, for use by -fself-test.
2 Copyright (C) 2015-2024 Free Software Foundation, Inc.
3
4This file is part of GCC.
5
6GCC is free software; you can redistribute it and/or modify it under
7the terms of the GNU General Public License as published by the Free
8Software Foundation; either version 3, or (at your option) any later
9version.
10
11GCC is distributed in the hope that it will be useful, but WITHOUT ANY
12WARRANTY; without even the implied warranty of MERCHANTABILITY or
13FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14for more details.
15
16You should have received a copy of the GNU General Public License
17along with GCC; see the file COPYING3. If not see
18<http://www.gnu.org/licenses/>. */
19
20#include "config.h"
21#include "system.h"
22#include "coretypes.h"
23#include "selftest.h"
24#include "intl.h"
25
26#if CHECKING_P
27
28namespace selftest {
29
30int num_passes;
31
32/* Record the successful outcome of some aspect of a test. */
33
34void
35pass (const location &/*loc*/, const char */*msg*/)
36{
37 num_passes++;
38}
39
40/* Report the failed outcome of some aspect of a test and abort. */
41
42void
43fail (const location &loc, const char *msg)
44{
45 fprintf (stderr,format: "%s:%i: %s: FAIL: %s\n", loc.m_file, loc.m_line,
46 loc.m_function, msg);
47 abort ();
48}
49
50/* As "fail", but using printf-style formatted output. */
51
52void
53fail_formatted (const location &loc, const char *fmt, ...)
54{
55 va_list ap;
56
57 fprintf (stderr, format: "%s:%i: %s: FAIL: ", loc.m_file, loc.m_line,
58 loc.m_function);
59 va_start (ap, fmt);
60 vfprintf (stderr, format: fmt, arg: ap);
61 va_end (ap);
62 fprintf (stderr, format: "\n");
63 abort ();
64}
65
66/* Implementation detail of ASSERT_STREQ.
67 Compare val1 and val2 with strcmp. They ought
68 to be non-NULL; fail gracefully if either or both are NULL. */
69
70void
71assert_streq (const location &loc,
72 const char *desc_val1, const char *desc_val2,
73 const char *val1, const char *val2)
74{
75 /* If val1 or val2 are NULL, fail with a custom error message. */
76 if (val1 == NULL)
77 if (val2 == NULL)
78 fail_formatted (loc, fmt: "ASSERT_STREQ (%s, %s) val1=NULL val2=NULL",
79 desc_val1, desc_val2);
80 else
81 fail_formatted (loc, fmt: "ASSERT_STREQ (%s, %s) val1=NULL val2=\"%s\"",
82 desc_val1, desc_val2, val2);
83 else
84 if (val2 == NULL)
85 fail_formatted (loc, fmt: "ASSERT_STREQ (%s, %s) val1=\"%s\" val2=NULL",
86 desc_val1, desc_val2, val1);
87 else
88 {
89 if (strcmp (s1: val1, s2: val2) == 0)
90 pass (loc, "ASSERT_STREQ");
91 else
92 fail_formatted (loc, fmt: "ASSERT_STREQ (%s, %s)\n val1=\"%s\"\n val2=\"%s\"\n",
93 desc_val1, desc_val2, val1, val2);
94 }
95}
96
97/* Implementation detail of ASSERT_STR_CONTAINS.
98 Use strstr to determine if val_needle is within val_haystack.
99 ::selftest::pass if it is found.
100 ::selftest::fail if it is not found. */
101
102void
103assert_str_contains (const location &loc,
104 const char *desc_haystack,
105 const char *desc_needle,
106 const char *val_haystack,
107 const char *val_needle)
108{
109 /* If val_haystack is NULL, fail with a custom error message. */
110 if (val_haystack == NULL)
111 fail_formatted (loc, fmt: "ASSERT_STR_CONTAINS (%s, %s) haystack=NULL",
112 desc_haystack, desc_needle);
113
114 /* If val_needle is NULL, fail with a custom error message. */
115 if (val_needle == NULL)
116 fail_formatted (loc,
117 fmt: "ASSERT_STR_CONTAINS (%s, %s) haystack=\"%s\" needle=NULL",
118 desc_haystack, desc_needle, val_haystack);
119
120 const char *test = strstr (haystack: val_haystack, needle: val_needle);
121 if (test)
122 pass (loc, "ASSERT_STR_CONTAINS");
123 else
124 fail_formatted
125 (loc, fmt: "ASSERT_STR_CONTAINS (%s, %s) haystack=\"%s\" needle=\"%s\"",
126 desc_haystack, desc_needle, val_haystack, val_needle);
127}
128
129/* Implementation detail of ASSERT_STR_STARTSWITH.
130 Determine if VAL_STR starts with VAL_PREFIX.
131 ::selftest::pass if VAL_STR does start with VAL_PREFIX.
132 ::selftest::fail if it does not, or either is NULL (using
133 DESC_STR and DESC_PREFIX in the error message). */
134
135void
136assert_str_startswith (const location &loc,
137 const char *desc_str,
138 const char *desc_prefix,
139 const char *val_str,
140 const char *val_prefix)
141{
142 /* If val_str is NULL, fail with a custom error message. */
143 if (val_str == NULL)
144 fail_formatted (loc, fmt: "ASSERT_STR_STARTSWITH (%s, %s) str=NULL",
145 desc_str, desc_prefix);
146
147 /* If val_prefix is NULL, fail with a custom error message. */
148 if (val_prefix == NULL)
149 fail_formatted (loc,
150 fmt: "ASSERT_STR_STARTSWITH (%s, %s) str=\"%s\" prefix=NULL",
151 desc_str, desc_prefix, val_str);
152
153 if (startswith (str: val_str, prefix: val_prefix))
154 pass (loc, "ASSERT_STR_STARTSWITH");
155 else
156 fail_formatted
157 (loc, fmt: "ASSERT_STR_STARTSWITH (%s, %s) str=\"%s\" prefix=\"%s\"",
158 desc_str, desc_prefix, val_str, val_prefix);
159}
160
161
162/* Constructor. Generate a name for the file. */
163
164named_temp_file::named_temp_file (const char *suffix,
165 file_cache *fc)
166{
167 m_filename = make_temp_file (suffix);
168 ASSERT_NE (m_filename, NULL);
169 m_file_cache = fc;
170}
171
172/* Destructor. Delete the tempfile. */
173
174named_temp_file::~named_temp_file ()
175{
176 unlink (name: m_filename);
177 if (m_file_cache)
178 m_file_cache->forcibly_evict_file (file_path: m_filename);
179 free (ptr: m_filename);
180}
181
182/* Constructor. Create a tempfile using SUFFIX, and write CONTENT to
183 it. Abort if anything goes wrong, using LOC as the effective
184 location in the problem report. */
185
186temp_source_file::temp_source_file (const location &loc,
187 const char *suffix,
188 const char *content,
189 file_cache *fc)
190: named_temp_file (suffix, fc)
191{
192 FILE *out = fopen (filename: get_filename (), modes: "w");
193 if (!out)
194 fail_formatted (loc, fmt: "unable to open tempfile: %s", get_filename ());
195 fprintf (stream: out, format: "%s", content);
196 fclose (stream: out);
197}
198
199/* As above, but with a size, to allow for NUL bytes in CONTENT. */
200
201temp_source_file::temp_source_file (const location &loc,
202 const char *suffix,
203 const char *content,
204 size_t sz)
205: named_temp_file (suffix)
206{
207 FILE *out = fopen (filename: get_filename (), modes: "w");
208 if (!out)
209 fail_formatted (loc, fmt: "unable to open tempfile: %s", get_filename ());
210 fwrite (ptr: content, size: sz, n: 1, s: out);
211 fclose (stream: out);
212}
213
214/* Avoid introducing locale-specific differences in the results
215 by hardcoding open_quote and close_quote. */
216
217auto_fix_quotes::auto_fix_quotes ()
218{
219 m_saved_open_quote = open_quote;
220 m_saved_close_quote = close_quote;
221 open_quote = "`";
222 close_quote = "'";
223}
224
225/* Restore old values of open_quote and close_quote. */
226
227auto_fix_quotes::~auto_fix_quotes ()
228{
229 open_quote = m_saved_open_quote;
230 close_quote = m_saved_close_quote;
231}
232
233/* Read the contents of PATH into memory, returning a 0-terminated buffer
234 that must be freed by the caller.
235 Fail (and abort) if there are any problems, with LOC as the reported
236 location of the failure. */
237
238char *
239read_file (const location &loc, const char *path)
240{
241 FILE *f_in = fopen (filename: path, modes: "r");
242 if (!f_in)
243 fail_formatted (loc, fmt: "unable to open file: %s", path);
244
245 /* Read content, allocating FIXME. */
246 char *result = NULL;
247 size_t total_sz = 0;
248 size_t alloc_sz = 0;
249 char buf[4096];
250 size_t iter_sz_in;
251
252 while ( (iter_sz_in = fread (ptr: buf, size: 1, n: sizeof (buf), stream: f_in)) )
253 {
254 gcc_assert (alloc_sz >= total_sz);
255 size_t old_total_sz = total_sz;
256 total_sz += iter_sz_in;
257 /* Allow 1 extra byte for 0-termination. */
258 if (alloc_sz < (total_sz + 1))
259 {
260 size_t new_alloc_sz = alloc_sz ? alloc_sz * 2: total_sz + 1;
261 result = (char *)xrealloc (result, new_alloc_sz);
262 alloc_sz = new_alloc_sz;
263 }
264 memcpy (dest: result + old_total_sz, src: buf, n: iter_sz_in);
265 }
266
267 if (!feof (stream: f_in))
268 fail_formatted (loc, fmt: "error reading from %s: %s", path,
269 xstrerror (errno));
270
271 fclose (stream: f_in);
272
273 /* 0-terminate the buffer. */
274 gcc_assert (total_sz < alloc_sz);
275 result[total_sz] = '\0';
276
277 return result;
278}
279
280/* The path of SRCDIR/testsuite/selftests. */
281
282const char *path_to_selftest_files = NULL;
283
284/* Convert a path relative to SRCDIR/testsuite/selftests
285 to a real path (either absolute, or relative to pwd).
286 The result should be freed by the caller. */
287
288char *
289locate_file (const char *name)
290{
291 ASSERT_NE (NULL, path_to_selftest_files);
292 return concat (path_to_selftest_files, "/", name, NULL);
293}
294
295/* selftest::test_runner's ctor. */
296
297test_runner::test_runner (const char *name)
298: m_name (name),
299 m_start_time (get_run_time ())
300{
301}
302
303/* selftest::test_runner's dtor. Print a summary line to stderr. */
304
305test_runner::~test_runner ()
306{
307 /* Finished running tests. */
308 long finish_time = get_run_time ();
309 long elapsed_time = finish_time - m_start_time;
310
311 fprintf (stderr,
312 format: "%s: %i pass(es) in %ld.%06ld seconds\n",
313 m_name, num_passes,
314 elapsed_time / 1000000, elapsed_time % 1000000);
315}
316
317/* Selftests for libiberty. */
318
319/* Verify that xstrndup generates EXPECTED when called on SRC and N. */
320
321static void
322assert_xstrndup_eq (const char *expected, const char *src, size_t n)
323{
324 char *buf = xstrndup (src, n);
325 ASSERT_STREQ (expected, buf);
326 free (ptr: buf);
327}
328
329/* Verify that xstrndup works as expected. */
330
331static void
332test_xstrndup ()
333{
334 assert_xstrndup_eq (expected: "", src: "test", n: 0);
335 assert_xstrndup_eq (expected: "t", src: "test", n: 1);
336 assert_xstrndup_eq (expected: "te", src: "test", n: 2);
337 assert_xstrndup_eq (expected: "tes", src: "test", n: 3);
338 assert_xstrndup_eq (expected: "test", src: "test", n: 4);
339 assert_xstrndup_eq (expected: "test", src: "test", n: 5);
340
341 /* Test on an string without zero termination. */
342 const char src[4] = {'t', 'e', 's', 't'};
343 assert_xstrndup_eq (expected: "", src, n: 0);
344 assert_xstrndup_eq (expected: "t", src, n: 1);
345 assert_xstrndup_eq (expected: "te", src, n: 2);
346 assert_xstrndup_eq (expected: "tes", src, n: 3);
347 assert_xstrndup_eq (expected: "test", src, n: 4);
348}
349
350/* Run selftests for libiberty. */
351
352static void
353test_libiberty ()
354{
355 test_xstrndup ();
356}
357
358/* Selftests for the selftest system itself. */
359
360/* Sanity-check the ASSERT_ macros with various passing cases. */
361
362static void
363test_assertions ()
364{
365 ASSERT_TRUE (true);
366 ASSERT_FALSE (false);
367 ASSERT_EQ (1, 1);
368 ASSERT_EQ_AT (SELFTEST_LOCATION, 1, 1);
369 ASSERT_NE (1, 2);
370 ASSERT_GT (2, 1);
371 ASSERT_GT_AT (SELFTEST_LOCATION, 2, 1);
372 ASSERT_LT (1, 2);
373 ASSERT_LT_AT (SELFTEST_LOCATION, 1, 2);
374 ASSERT_STREQ ("test", "test");
375 ASSERT_STREQ_AT (SELFTEST_LOCATION, "test", "test");
376 ASSERT_STR_CONTAINS ("foo bar baz", "bar");
377}
378
379/* Verify named_temp_file. */
380
381static void
382test_named_temp_file ()
383{
384 named_temp_file t (".txt");
385 FILE *f = fopen (filename: t.get_filename (), modes: "w");
386 if (!f)
387 fail_formatted (SELFTEST_LOCATION,
388 fmt: "unable to open %s for writing", t.get_filename ());
389 fclose (stream: f);
390}
391
392/* Verify read_file (and also temp_source_file). */
393
394static void
395test_read_file ()
396{
397 temp_source_file t (SELFTEST_LOCATION, "test1.s",
398 "\tjmp\t.L2\n");
399 char *buf = read_file (SELFTEST_LOCATION, path: t.get_filename ());
400 ASSERT_STREQ ("\tjmp\t.L2\n", buf);
401 free (ptr: buf);
402}
403
404/* Verify locate_file (and read_file). */
405
406static void
407test_locate_file ()
408{
409 char *path = locate_file (name: "example.txt");
410 char *buf = read_file (SELFTEST_LOCATION, path);
411 ASSERT_STREQ ("example of a selftest file\n", buf);
412 free (ptr: buf);
413 free (ptr: path);
414}
415
416/* Run all of the selftests within this file. */
417
418void
419selftest_cc_tests ()
420{
421 test_libiberty ();
422 test_assertions ();
423 test_named_temp_file ();
424 test_read_file ();
425 test_locate_file ();
426}
427
428} // namespace selftest
429
430#endif /* #if CHECKING_P */
431

source code of gcc/selftest.cc