Line data Source code
1 : /* A self-testing framework, for use by -fself-test.
2 : Copyright (C) 2015-2026 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 30660136 : pass (const location &/*loc*/, const char */*msg*/)
37 : {
38 30660136 : num_passes++;
39 30660136 : }
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 29201 : 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 29201 : 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 29201 : 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 29201 : if (strcmp (val1, val2) == 0)
111 29201 : 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 29201 : }
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 */
|