aboutsummaryrefslogtreecommitdiff
path: root/gcc/selftest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/selftest.cc')
-rw-r--r--gcc/selftest.cc426
1 files changed, 426 insertions, 0 deletions
diff --git a/gcc/selftest.cc b/gcc/selftest.cc
new file mode 100644
index 0000000..ed5b396
--- /dev/null
+++ b/gcc/selftest.cc
@@ -0,0 +1,426 @@
+/* A self-testing framework, for use by -fself-test.
+ Copyright (C) 2015-2022 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC 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 General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "selftest.h"
+#include "intl.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+int num_passes;
+
+/* Record the successful outcome of some aspect of a test. */
+
+void
+pass (const location &/*loc*/, const char */*msg*/)
+{
+ num_passes++;
+}
+
+/* Report the failed outcome of some aspect of a test and abort. */
+
+void
+fail (const location &loc, const char *msg)
+{
+ fprintf (stderr,"%s:%i: %s: FAIL: %s\n", loc.m_file, loc.m_line,
+ loc.m_function, msg);
+ abort ();
+}
+
+/* As "fail", but using printf-style formatted output. */
+
+void
+fail_formatted (const location &loc, const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf (stderr, "%s:%i: %s: FAIL: ", loc.m_file, loc.m_line,
+ loc.m_function);
+ va_start (ap, fmt);
+ vfprintf (stderr, fmt, ap);
+ va_end (ap);
+ fprintf (stderr, "\n");
+ abort ();
+}
+
+/* Implementation detail of ASSERT_STREQ.
+ Compare val1 and val2 with strcmp. They ought
+ to be non-NULL; fail gracefully if either or both are NULL. */
+
+void
+assert_streq (const location &loc,
+ const char *desc_val1, const char *desc_val2,
+ const char *val1, const char *val2)
+{
+ /* If val1 or val2 are NULL, fail with a custom error message. */
+ if (val1 == NULL)
+ if (val2 == NULL)
+ fail_formatted (loc, "ASSERT_STREQ (%s, %s) val1=NULL val2=NULL",
+ desc_val1, desc_val2);
+ else
+ fail_formatted (loc, "ASSERT_STREQ (%s, %s) val1=NULL val2=\"%s\"",
+ desc_val1, desc_val2, val2);
+ else
+ if (val2 == NULL)
+ fail_formatted (loc, "ASSERT_STREQ (%s, %s) val1=\"%s\" val2=NULL",
+ desc_val1, desc_val2, val1);
+ else
+ {
+ if (strcmp (val1, val2) == 0)
+ pass (loc, "ASSERT_STREQ");
+ else
+ fail_formatted (loc, "ASSERT_STREQ (%s, %s)\n val1=\"%s\"\n val2=\"%s\"\n",
+ desc_val1, desc_val2, val1, val2);
+ }
+}
+
+/* Implementation detail of ASSERT_STR_CONTAINS.
+ Use strstr to determine if val_needle is within val_haystack.
+ ::selftest::pass if it is found.
+ ::selftest::fail if it is not found. */
+
+void
+assert_str_contains (const location &loc,
+ const char *desc_haystack,
+ const char *desc_needle,
+ const char *val_haystack,
+ const char *val_needle)
+{
+ /* If val_haystack is NULL, fail with a custom error message. */
+ if (val_haystack == NULL)
+ fail_formatted (loc, "ASSERT_STR_CONTAINS (%s, %s) haystack=NULL",
+ desc_haystack, desc_needle);
+
+ /* If val_needle is NULL, fail with a custom error message. */
+ if (val_needle == NULL)
+ fail_formatted (loc,
+ "ASSERT_STR_CONTAINS (%s, %s) haystack=\"%s\" needle=NULL",
+ desc_haystack, desc_needle, val_haystack);
+
+ const char *test = strstr (val_haystack, val_needle);
+ if (test)
+ pass (loc, "ASSERT_STR_CONTAINS");
+ else
+ fail_formatted
+ (loc, "ASSERT_STR_CONTAINS (%s, %s) haystack=\"%s\" needle=\"%s\"",
+ desc_haystack, desc_needle, val_haystack, val_needle);
+}
+
+/* Implementation detail of ASSERT_STR_STARTSWITH.
+ Determine if VAL_STR starts with VAL_PREFIX.
+ ::selftest::pass if VAL_STR does start with VAL_PREFIX.
+ ::selftest::fail if it does not, or either is NULL (using
+ DESC_STR and DESC_PREFIX in the error message). */
+
+void
+assert_str_startswith (const location &loc,
+ const char *desc_str,
+ const char *desc_prefix,
+ const char *val_str,
+ const char *val_prefix)
+{
+ /* If val_str is NULL, fail with a custom error message. */
+ if (val_str == NULL)
+ fail_formatted (loc, "ASSERT_STR_STARTSWITH (%s, %s) str=NULL",
+ desc_str, desc_prefix);
+
+ /* If val_prefix is NULL, fail with a custom error message. */
+ if (val_prefix == NULL)
+ fail_formatted (loc,
+ "ASSERT_STR_STARTSWITH (%s, %s) str=\"%s\" prefix=NULL",
+ desc_str, desc_prefix, val_str);
+
+ if (startswith (val_str, val_prefix))
+ pass (loc, "ASSERT_STR_STARTSWITH");
+ else
+ fail_formatted
+ (loc, "ASSERT_STR_STARTSWITH (%s, %s) str=\"%s\" prefix=\"%s\"",
+ desc_str, desc_prefix, val_str, val_prefix);
+}
+
+
+/* Constructor. Generate a name for the file. */
+
+named_temp_file::named_temp_file (const char *suffix)
+{
+ m_filename = make_temp_file (suffix);
+ ASSERT_NE (m_filename, NULL);
+}
+
+/* Destructor. Delete the tempfile. */
+
+named_temp_file::~named_temp_file ()
+{
+ unlink (m_filename);
+ diagnostics_file_cache_forcibly_evict_file (m_filename);
+ free (m_filename);
+}
+
+/* Constructor. Create a tempfile using SUFFIX, and write CONTENT to
+ it. Abort if anything goes wrong, using LOC as the effective
+ location in the problem report. */
+
+temp_source_file::temp_source_file (const location &loc,
+ const char *suffix,
+ const char *content)
+: named_temp_file (suffix)
+{
+ FILE *out = fopen (get_filename (), "w");
+ if (!out)
+ fail_formatted (loc, "unable to open tempfile: %s", get_filename ());
+ fprintf (out, "%s", content);
+ fclose (out);
+}
+
+/* As above, but with a size, to allow for NUL bytes in CONTENT. */
+
+temp_source_file::temp_source_file (const location &loc,
+ const char *suffix,
+ const char *content,
+ size_t sz)
+: named_temp_file (suffix)
+{
+ FILE *out = fopen (get_filename (), "w");
+ if (!out)
+ fail_formatted (loc, "unable to open tempfile: %s", get_filename ());
+ fwrite (content, sz, 1, out);
+ fclose (out);
+}
+
+/* Avoid introducing locale-specific differences in the results
+ by hardcoding open_quote and close_quote. */
+
+auto_fix_quotes::auto_fix_quotes ()
+{
+ m_saved_open_quote = open_quote;
+ m_saved_close_quote = close_quote;
+ open_quote = "`";
+ close_quote = "'";
+}
+
+/* Restore old values of open_quote and close_quote. */
+
+auto_fix_quotes::~auto_fix_quotes ()
+{
+ open_quote = m_saved_open_quote;
+ close_quote = m_saved_close_quote;
+}
+
+/* Read the contents of PATH into memory, returning a 0-terminated buffer
+ that must be freed by the caller.
+ Fail (and abort) if there are any problems, with LOC as the reported
+ location of the failure. */
+
+char *
+read_file (const location &loc, const char *path)
+{
+ FILE *f_in = fopen (path, "r");
+ if (!f_in)
+ fail_formatted (loc, "unable to open file: %s", path);
+
+ /* Read content, allocating FIXME. */
+ char *result = NULL;
+ size_t total_sz = 0;
+ size_t alloc_sz = 0;
+ char buf[4096];
+ size_t iter_sz_in;
+
+ while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) )
+ {
+ gcc_assert (alloc_sz >= total_sz);
+ size_t old_total_sz = total_sz;
+ total_sz += iter_sz_in;
+ /* Allow 1 extra byte for 0-termination. */
+ if (alloc_sz < (total_sz + 1))
+ {
+ size_t new_alloc_sz = alloc_sz ? alloc_sz * 2: total_sz + 1;
+ result = (char *)xrealloc (result, new_alloc_sz);
+ alloc_sz = new_alloc_sz;
+ }
+ memcpy (result + old_total_sz, buf, iter_sz_in);
+ }
+
+ if (!feof (f_in))
+ fail_formatted (loc, "error reading from %s: %s", path,
+ xstrerror (errno));
+
+ fclose (f_in);
+
+ /* 0-terminate the buffer. */
+ gcc_assert (total_sz < alloc_sz);
+ result[total_sz] = '\0';
+
+ return result;
+}
+
+/* The path of SRCDIR/testsuite/selftests. */
+
+const char *path_to_selftest_files = NULL;
+
+/* Convert a path relative to SRCDIR/testsuite/selftests
+ to a real path (either absolute, or relative to pwd).
+ The result should be freed by the caller. */
+
+char *
+locate_file (const char *name)
+{
+ ASSERT_NE (NULL, path_to_selftest_files);
+ return concat (path_to_selftest_files, "/", name, NULL);
+}
+
+/* selftest::test_runner's ctor. */
+
+test_runner::test_runner (const char *name)
+: m_name (name),
+ m_start_time (get_run_time ())
+{
+}
+
+/* selftest::test_runner's dtor. Print a summary line to stderr. */
+
+test_runner::~test_runner ()
+{
+ /* Finished running tests. */
+ long finish_time = get_run_time ();
+ long elapsed_time = finish_time - m_start_time;
+
+ fprintf (stderr,
+ "%s: %i pass(es) in %ld.%06ld seconds\n",
+ m_name, num_passes,
+ elapsed_time / 1000000, elapsed_time % 1000000);
+}
+
+/* Selftests for libiberty. */
+
+/* Verify that xstrndup generates EXPECTED when called on SRC and N. */
+
+static void
+assert_xstrndup_eq (const char *expected, const char *src, size_t n)
+{
+ char *buf = xstrndup (src, n);
+ ASSERT_STREQ (expected, buf);
+ free (buf);
+}
+
+/* Verify that xstrndup works as expected. */
+
+static void
+test_xstrndup ()
+{
+ assert_xstrndup_eq ("", "test", 0);
+ assert_xstrndup_eq ("t", "test", 1);
+ assert_xstrndup_eq ("te", "test", 2);
+ assert_xstrndup_eq ("tes", "test", 3);
+ assert_xstrndup_eq ("test", "test", 4);
+ assert_xstrndup_eq ("test", "test", 5);
+
+ /* Test on an string without zero termination. */
+ const char src[4] = {'t', 'e', 's', 't'};
+ assert_xstrndup_eq ("", src, 0);
+ assert_xstrndup_eq ("t", src, 1);
+ assert_xstrndup_eq ("te", src, 2);
+ assert_xstrndup_eq ("tes", src, 3);
+ assert_xstrndup_eq ("test", src, 4);
+}
+
+/* Run selftests for libiberty. */
+
+static void
+test_libiberty ()
+{
+ test_xstrndup ();
+}
+
+/* Selftests for the selftest system itself. */
+
+/* Sanity-check the ASSERT_ macros with various passing cases. */
+
+static void
+test_assertions ()
+{
+ ASSERT_TRUE (true);
+ ASSERT_FALSE (false);
+ ASSERT_EQ (1, 1);
+ ASSERT_EQ_AT (SELFTEST_LOCATION, 1, 1);
+ ASSERT_NE (1, 2);
+ ASSERT_GT (2, 1);
+ ASSERT_GT_AT (SELFTEST_LOCATION, 2, 1);
+ ASSERT_LT (1, 2);
+ ASSERT_LT_AT (SELFTEST_LOCATION, 1, 2);
+ ASSERT_STREQ ("test", "test");
+ ASSERT_STREQ_AT (SELFTEST_LOCATION, "test", "test");
+ ASSERT_STR_CONTAINS ("foo bar baz", "bar");
+}
+
+/* Verify named_temp_file. */
+
+static void
+test_named_temp_file ()
+{
+ named_temp_file t (".txt");
+ FILE *f = fopen (t.get_filename (), "w");
+ if (!f)
+ fail_formatted (SELFTEST_LOCATION,
+ "unable to open %s for writing", t.get_filename ());
+ fclose (f);
+}
+
+/* Verify read_file (and also temp_source_file). */
+
+static void
+test_read_file ()
+{
+ temp_source_file t (SELFTEST_LOCATION, "test1.s",
+ "\tjmp\t.L2\n");
+ char *buf = read_file (SELFTEST_LOCATION, t.get_filename ());
+ ASSERT_STREQ ("\tjmp\t.L2\n", buf);
+ free (buf);
+}
+
+/* Verify locate_file (and read_file). */
+
+static void
+test_locate_file ()
+{
+ char *path = locate_file ("example.txt");
+ char *buf = read_file (SELFTEST_LOCATION, path);
+ ASSERT_STREQ ("example of a selftest file\n", buf);
+ free (buf);
+ free (path);
+}
+
+/* Run all of the selftests within this file. */
+
+void
+selftest_c_tests ()
+{
+ test_libiberty ();
+ test_assertions ();
+ test_named_temp_file ();
+ test_read_file ();
+ test_locate_file ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */