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