]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
diagnostics: add selftests for SARIF output
authorDavid Malcolm <dmalcolm@redhat.com>
Wed, 24 Jul 2024 22:07:56 +0000 (18:07 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Wed, 24 Jul 2024 22:07:56 +0000 (18:07 -0400)
The existing DejaGnu-based tests for our SARIF output used regexes
to verify the JSON at the string level, which lets us test for
the presence of properties, but doesn't check the overall structure.

This patch uses the selftest framework to verify the structure of
the tree of JSON values for a log containing one diagnostic.

No functional change intended.

gcc/ChangeLog:
* diagnostic-format-sarif.cc (sarif_builder::flush_to_object):
New, using code moved from...
(sarif_builder::end_group): ...here.
(class selftest::test_sarif_diagnostic_context): New.
(selftest::test_simple_log): New.
(selftest::diagnostic_format_sarif_cc_tests): Call it.
* json.h (json::object::is_empty): New.
* selftest-diagnostic.cc (test_diagnostic_context::report): New.
* selftest-diagnostic.h (test_diagnostic_context::report): New
decl.
* selftest-json.cc (selftest::assert_json_string_eq): New.
(selftest::expect_json_object_with_string_property): New.
(selftest::assert_json_string_property_eq): New.
* selftest-json.h (selftest::assert_json_string_eq): New decl.
(ASSERT_JSON_STRING_EQ): New macro.
(selftest::expect_json_object_with_string_property): New decl.
(EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY): New macro.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/diagnostic-format-sarif.cc
gcc/json.h
gcc/selftest-diagnostic.cc
gcc/selftest-diagnostic.h
gcc/selftest-json.cc
gcc/selftest-json.h

index afb29eab583940595440e68c4585f542cdbd05cf..816f3210036eebe8c70b74e2607d821590246e22 100644 (file)
@@ -380,6 +380,7 @@ public:
                     const diagnostic_diagram &diagram);
   void end_group ();
 
+  std::unique_ptr<sarif_log> flush_to_object ();
   void flush_to_file (FILE *outf);
 
   std::unique_ptr<json::array>
@@ -860,6 +861,20 @@ sarif_builder::end_group ()
   m_cur_group_result = nullptr;
 }
 
+/* Create a top-level object, and add it to all the results
+   (and other entities) we've seen so far, moving ownership
+   to the object.  */
+
+std::unique_ptr<sarif_log>
+sarif_builder::flush_to_object ()
+{
+  m_invocation_obj->prepare_to_flush (m_context);
+  std::unique_ptr<sarif_log> top
+    = make_top_level_object (std::move (m_invocation_obj),
+                            std::move (m_results_array));
+  return top;
+}
+
 /* Create a top-level object, and add it to all the results
    (and other entities) we've seen so far.
 
@@ -868,12 +883,8 @@ sarif_builder::end_group ()
 void
 sarif_builder::flush_to_file (FILE *outf)
 {
-  m_invocation_obj->prepare_to_flush (m_context);
-  std::unique_ptr<sarif_log> top
-    = make_top_level_object (std::move (m_invocation_obj),
-                            std::move (m_results_array));
+  std::unique_ptr<sarif_log> top = flush_to_object ();
   top->dump (outf, m_formatted);
-  m_invocation_obj = nullptr;
   fprintf (outf, "\n");
 }
 
@@ -2434,6 +2445,54 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
 
 namespace selftest {
 
+/* A subclass of sarif_output_format for writing selftests.
+   The JSON output is cached internally, rather than written
+   out to a file.  */
+
+class test_sarif_diagnostic_context : public test_diagnostic_context
+{
+public:
+  test_sarif_diagnostic_context ()
+  {
+    diagnostic_output_format_init_sarif (*this);
+
+    m_format = new buffered_output_format (*this,
+                                          "MAIN_INPUT_FILENAME",
+                                          true);
+    set_output_format (m_format); // give ownership;
+  }
+
+  std::unique_ptr<sarif_log> flush_to_object ()
+  {
+    return m_format->flush_to_object ();
+  }
+
+private:
+  class buffered_output_format : public sarif_output_format
+  {
+  public:
+    buffered_output_format (diagnostic_context &context,
+                           const char *main_input_filename_,
+                           bool formatted)
+      : sarif_output_format (context, main_input_filename_, formatted)
+    {
+    }
+    bool machine_readable_stderr_p () const final override
+    {
+      return false;
+    }
+    std::unique_ptr<sarif_log> flush_to_object ()
+    {
+      return m_builder.flush_to_object ();
+    }
+  };
+
+  buffered_output_format *m_format; // borrowed
+};
+
+/* Test making a sarif_location for a complex rich_location
+   with labels and escape-on-output.  */
+
 static void
 test_make_location_object (const line_table_case &case_)
 {
@@ -2550,12 +2609,128 @@ test_make_location_object (const line_table_case &case_)
   }
 }
 
+/* Test of reporting a diagnostic to a diagnostic_context and
+   examining the generated sarif_log.
+   Verify various basic properties. */
+
+static void
+test_simple_log ()
+{
+  test_sarif_diagnostic_context dc;
+
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+  dc.report (DK_ERROR, richloc, nullptr, 0, "this is a test: %i", 42);
+
+  auto log_ptr = dc.flush_to_object ();
+
+  // 3.13 sarifLog:
+  auto log = log_ptr.get ();
+  ASSERT_JSON_STRING_PROPERTY_EQ (log, "$schema", SARIF_SCHEMA); // 3.13.3
+  ASSERT_JSON_STRING_PROPERTY_EQ (log, "version", SARIF_VERSION); // 3.13.2
+
+  auto runs = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (log, "runs"); // 3.13.4
+  ASSERT_EQ (runs->size (), 1);
+
+  // 3.14 "run" object:
+  auto run = (*runs)[0];
+
+  {
+    // 3.14.6:
+    auto tool = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (run, "tool");
+
+    EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (tool, "driver"); // 3.18.2
+  }
+
+  {
+    // 3.14.11
+    auto invocations
+      = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "invocations");
+    ASSERT_EQ (invocations->size (), 1);
+
+    {
+      // 3.20 "invocation" object:
+      auto invocation = (*invocations)[0];
+
+      // 3.20.3 arguments property
+
+      // 3.20.7 startTimeUtc property
+      EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (invocation, "startTimeUtc");
+
+      // 3.20.8 endTimeUtc property
+      EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (invocation, "endTimeUtc");
+
+      // 3.20.19 workingDirectory property
+      {
+       auto wd_obj
+         = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (invocation,
+                                                    "workingDirectory");
+       EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (wd_obj, "uri");
+      }
+
+      // 3.20.21 toolExecutionNotifications property
+      auto notifications
+       = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY
+           (invocation, "toolExecutionNotifications");
+      ASSERT_EQ (notifications->size (), 0);
+    }
+  }
+
+  {
+    // 3.14.15:
+    auto artifacts = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "artifacts");
+    ASSERT_EQ (artifacts->size (), 1);
+
+    {
+      // 3.24 "artifact" object:
+      auto artifact = (*artifacts)[0];
+
+      // 3.24.2:
+      auto location
+       = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (artifact, "location");
+      ASSERT_JSON_STRING_PROPERTY_EQ (location, "uri", "MAIN_INPUT_FILENAME");
+
+      // 3.24.6:
+      auto roles = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (artifact, "roles");
+      ASSERT_EQ (roles->size (), 1);
+      {
+       auto role = (*roles)[0];
+       ASSERT_JSON_STRING_EQ (role, "analysisTarget");
+      }
+    }
+  }
+
+  {
+    // 3.14.23:
+    auto results = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "results");
+    ASSERT_EQ (results->size (), 1);
+
+    {
+      // 3.27 "result" object:
+      auto result = (*results)[0];
+      ASSERT_JSON_STRING_PROPERTY_EQ (result, "ruleId", "error");
+      ASSERT_JSON_STRING_PROPERTY_EQ (result, "level", "error"); // 3.27.10
+
+      {
+       // 3.27.11:
+       auto message
+         = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (result, "message");
+       ASSERT_JSON_STRING_PROPERTY_EQ (message, "text",
+                                       "this is a test: 42");
+      }
+
+      // 3.27.12:
+      EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (result, "locations");
+    }
+  }
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 diagnostic_format_sarif_cc_tests ()
 {
   for_each_line_table_case (test_make_location_object);
+  test_simple_log ();
 }
 
 } // namespace selftest
index 96721edf53657f0ec87f1ccebfc1d26eb32e9ebd..21f71fe1c4ab1d163ad83361e48cc407c8f41696 100644 (file)
@@ -109,6 +109,8 @@ class object : public value
   enum kind get_kind () const final override { return JSON_OBJECT; }
   void print (pretty_printer *pp, bool formatted) const final override;
 
+  bool is_empty () const { return m_map.is_empty (); }
+
   void set (const char *key, value *v);
 
   /* Set the property KEY of this object, requiring V
index 2684216a49fee6dc6fc921e60cfdfe6efc5bc2d0..c9e9d7094bd33165726a76148ee1d1ee3ab12106 100644 (file)
@@ -60,6 +60,20 @@ test_diagnostic_context::start_span_cb (diagnostic_context *context,
   default_diagnostic_start_span_fn (context, exploc);
 }
 
+bool
+test_diagnostic_context::report (diagnostic_t kind,
+                                rich_location &richloc,
+                                const diagnostic_metadata *metadata,
+                                int option,
+                                const char * fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  bool result = diagnostic_impl (&richloc, metadata, option, fmt, &ap, kind);
+  va_end (ap);
+  return result;
+}
+
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
index 72a65fdb977f1cab0fd94cae86de9917434c814e..f899443c496150a4f692bfdfbed3e9f4af411a1c 100644 (file)
@@ -40,6 +40,16 @@ class test_diagnostic_context : public diagnostic_context
      real filename (to avoid printing the names of tempfiles).  */
   static void
   start_span_cb (diagnostic_context *context, expanded_location exploc);
+
+  /* Report a diagnostic to this context.  For a selftest, this
+     should only be called on a context that uses a non-standard formatter
+     that e.g. gathers the results in memory, rather than emits to stderr.  */
+  bool
+  report (diagnostic_t kind,
+         rich_location &richloc,
+         const diagnostic_metadata *metadata,
+         int option,
+         const char * fmt, ...) ATTRIBUTE_GCC_DIAG(6,7);
 };
 
 } // namespace selftest
index 271e9b441120c3a7221cdb36a60dc65c03312aa3..4f52a87538ae9c0f798c25e0daa4988153016a8b 100644 (file)
@@ -33,6 +33,20 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace selftest {
 
+/* Assert that VALUE is a non-null json::string
+   equalling EXPECTED_VALUE.
+   Use LOC for any failures.  */
+
+void
+assert_json_string_eq (const location &loc,
+                      const json::value *value,
+                      const char *expected_value)
+{
+  ASSERT_EQ_AT (loc, value->get_kind (), json::JSON_STRING);
+  const json::string *str = static_cast<const json::string *> (value);
+  ASSERT_STREQ_AT (loc, expected_value, str->get_string ());
+}
+
 /* Assert that VALUE is a non-null json::object,
    returning it as such, failing at LOC if this isn't the case.  */
 
@@ -112,6 +126,22 @@ expect_json_object_with_array_property (const location &loc,
   return static_cast<const json::array *> (property_value);
 }
 
+/* Assert that VALUE is a non-null json::object that has property
+   PROPERTY_NAME, and that the property value is a non-null JSON string.
+   Return the value of the property as a json::string.
+   Use LOC for any failures.  */
+
+const json::string *
+expect_json_object_with_string_property (const location &loc,
+                                        const json::value *value,
+                                        const char *property_name)
+{
+  const json::value *property_value
+    = expect_json_object_with_property (loc, value, property_name);
+  ASSERT_EQ_AT (loc, property_value->get_kind (), json::JSON_STRING);
+  return static_cast<const json::string *> (property_value);
+}
+
 /* Assert that VALUE is a non-null json::object that has property
    PROPERTY_NAME, and that the value of that property is a non-null
    JSON string equalling EXPECTED_VALUE.
@@ -125,9 +155,7 @@ assert_json_string_property_eq (const location &loc,
 {
   const json::value *property_value
     = expect_json_object_with_property (loc, value, property_name);
-  ASSERT_EQ_AT (loc, property_value->get_kind (), json::JSON_STRING);
-  const json::string *str = static_cast<const json::string *> (property_value);
-  ASSERT_STREQ_AT (loc, expected_value, str->get_string ());
+  assert_json_string_eq (loc, property_value, expected_value);
 }
 
 } // namespace selftest
index 23b4d18951ca906278b3658d29b8463789c97e61..80527d7028138d1b3006ad4068dbe73ffdc7d56d 100644 (file)
@@ -30,6 +30,19 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace selftest {
 
+/* Assert that VALUE is a non-null json::string
+   equalling EXPECTED_VALUE.
+   Use LOC for any failures.  */
+
+void
+assert_json_string_eq (const location &loc,
+                      const json::value *value,
+                      const char *expected_value);
+#define ASSERT_JSON_STRING_EQ(JSON_VALUE, EXPECTED_VALUE) \
+  assert_json_string_eq ((SELFTEST_LOCATION),                  \
+                        (JSON_VALUE),                          \
+                        (EXPECTED_VALUE))
+
 /* Assert that VALUE is a non-null json::object,
    returning it as such, failing at LOC if this isn't the case.  */
 
@@ -91,6 +104,20 @@ expect_json_object_with_array_property (const location &loc,
                                          (JSON_VALUE),         \
                                          (PROPERTY_NAME))
 
+/* Assert that VALUE is a non-null json::object that has property
+   PROPERTY_NAME, and that the property value is a non-null JSON string.
+   Return the value of the property as a json::string.
+   Use LOC for any failures.  */
+
+const json::string *
+expect_json_object_with_string_property (const location &loc,
+                                        const json::value *value,
+                                        const char *property_name);
+#define EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY(JSON_VALUE, PROPERTY_NAME) \
+  expect_json_object_with_string_property ((SELFTEST_LOCATION),                \
+                                          (JSON_VALUE),                \
+                                          (PROPERTY_NAME))
+
 /* Assert that VALUE is a non-null json::object that has property
    PROPERTY_NAME, and that the value of that property is a non-null
    JSON string equalling EXPECTED_VALUE.