gtestutils: add g_text_expect_message()
authorDan Winship <danw@gnome.org>
Mon, 30 Jul 2012 20:05:08 +0000 (16:05 -0400)
committerDan Winship <danw@gnome.org>
Mon, 20 Aug 2012 17:54:52 +0000 (13:54 -0400)
Add g_test_expect_message() and g_test_assert_expected_messages(), to
allow tests of warnings, error messages, return-if-fails, etc.

https://bugzilla.gnome.org/show_bug.cgi?id=679556

docs/reference/glib/glib-sections.txt
glib/glib.symbols
glib/gmessages.c
glib/gtestutils.h
glib/tests/testing.c

index a7f0a65..2d7f232 100644 (file)
@@ -2901,6 +2901,9 @@ g_test_queue_free
 g_test_queue_destroy
 g_test_queue_unref
 
+g_test_expect_message
+g_test_assert_expected_messages
+
 GTestTrapFlags
 g_test_trap_fork
 g_test_trap_has_passed
@@ -2945,6 +2948,7 @@ g_assertion_message_expr
 g_assertion_message_cmpstr
 g_assertion_message_cmpnum
 g_assertion_message_error
+g_test_assert_expected_messages_internal
 
 g_test_config_vars
 
index 49c6442..9609798 100644 (file)
@@ -1187,11 +1187,13 @@ g_strcmp0
 g_test_add_data_func
 g_test_add_func
 g_test_add_vtable
+g_test_assert_expected_messages_internal
 g_test_bug
 g_test_bug_base
 g_test_config_vars
 g_test_create_case
 g_test_create_suite
+g_test_expect_message
 g_test_fail
 g_test_get_root
 g_test_init
index b1697d6..c83d3e8 100644 (file)
@@ -72,6 +72,7 @@
 #include "gthread.h"
 #include "gstrfuncs.h"
 #include "gstring.h"
+#include "gpattern.h"
 
 #ifdef G_OS_WIN32
 #include <process.h>           /* For getpid() */
@@ -641,6 +642,197 @@ g_log_remove_handler (const gchar *log_domain,
             G_STRLOC, handler_id, log_domain);
 }
 
+#define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
+                           (wc == 0x7f) || \
+                           (wc >= 0x80 && wc < 0xa0)))
+     
+static gchar*
+strdup_convert (const gchar *string,
+               const gchar *charset)
+{
+  if (!g_utf8_validate (string, -1, NULL))
+    {
+      GString *gstring = g_string_new ("[Invalid UTF-8] ");
+      guchar *p;
+
+      for (p = (guchar *)string; *p; p++)
+       {
+         if (CHAR_IS_SAFE(*p) &&
+             !(*p == '\r' && *(p + 1) != '\n') &&
+             *p < 0x80)
+           g_string_append_c (gstring, *p);
+         else
+           g_string_append_printf (gstring, "\\x%02x", (guint)(guchar)*p);
+       }
+      
+      return g_string_free (gstring, FALSE);
+    }
+  else
+    {
+      GError *err = NULL;
+      
+      gchar *result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, &err);
+      if (result)
+       return result;
+      else
+       {
+         /* Not thread-safe, but doesn't matter if we print the warning twice
+          */
+         static gboolean warned = FALSE; 
+         if (!warned)
+           {
+             warned = TRUE;
+             _g_fprintf (stderr, "GLib: Cannot convert message: %s\n", err->message);
+           }
+         g_error_free (err);
+         
+         return g_strdup (string);
+       }
+    }
+}
+
+/* For a radix of 8 we need at most 3 output bytes for 1 input
+ * byte. Additionally we might need up to 2 output bytes for the
+ * readix prefix and 1 byte for the trailing NULL.
+ */
+#define FORMAT_UNSIGNED_BUFSIZE ((GLIB_SIZEOF_LONG * 3) + 3)
+
+static void
+format_unsigned (gchar  *buf,
+                gulong  num,
+                guint   radix)
+{
+  gulong tmp;
+  gchar c;
+  gint i, n;
+
+  /* we may not call _any_ GLib functions here (or macros like g_return_if_fail()) */
+
+  if (radix != 8 && radix != 10 && radix != 16)
+    {
+      *buf = '\000';
+      return;
+    }
+  
+  if (!num)
+    {
+      *buf++ = '0';
+      *buf = '\000';
+      return;
+    } 
+  
+  if (radix == 16)
+    {
+      *buf++ = '0';
+      *buf++ = 'x';
+    }
+  else if (radix == 8)
+    {
+      *buf++ = '0';
+    }
+       
+  n = 0;
+  tmp = num;
+  while (tmp)
+    {
+      tmp /= radix;
+      n++;
+    }
+
+  i = n;
+
+  /* Again we can't use g_assert; actually this check should _never_ fail. */
+  if (n > FORMAT_UNSIGNED_BUFSIZE - 3)
+    {
+      *buf = '\000';
+      return;
+    }
+
+  while (num)
+    {
+      i--;
+      c = (num % radix);
+      if (c < 10)
+       buf[i] = c + '0';
+      else
+       buf[i] = c + 'a' - 10;
+      num /= radix;
+    }
+  
+  buf[n] = '\000';
+}
+
+/* string size big enough to hold level prefix */
+#define        STRING_BUFFER_SIZE      (FORMAT_UNSIGNED_BUFSIZE + 32)
+
+#define        ALERT_LEVELS            (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)
+
+/* these are emitted by the default log handler */
+#define DEFAULT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE)
+/* these are filtered by G_MESSAGES_DEBUG by the default log handler */
+#define INFO_LEVELS (G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG)
+
+static int
+mklevel_prefix (gchar          level_prefix[STRING_BUFFER_SIZE],
+               GLogLevelFlags log_level)
+{
+  gboolean to_stdout = TRUE;
+
+  /* we may not call _any_ GLib functions here */
+
+  switch (log_level & G_LOG_LEVEL_MASK)
+    {
+    case G_LOG_LEVEL_ERROR:
+      strcpy (level_prefix, "ERROR");
+      to_stdout = FALSE;
+      break;
+    case G_LOG_LEVEL_CRITICAL:
+      strcpy (level_prefix, "CRITICAL");
+      to_stdout = FALSE;
+      break;
+    case G_LOG_LEVEL_WARNING:
+      strcpy (level_prefix, "WARNING");
+      to_stdout = FALSE;
+      break;
+    case G_LOG_LEVEL_MESSAGE:
+      strcpy (level_prefix, "Message");
+      to_stdout = FALSE;
+      break;
+    case G_LOG_LEVEL_INFO:
+      strcpy (level_prefix, "INFO");
+      break;
+    case G_LOG_LEVEL_DEBUG:
+      strcpy (level_prefix, "DEBUG");
+      break;
+    default:
+      if (log_level)
+       {
+         strcpy (level_prefix, "LOG-");
+         format_unsigned (level_prefix + 4, log_level & G_LOG_LEVEL_MASK, 16);
+       }
+      else
+       strcpy (level_prefix, "LOG");
+      break;
+    }
+  if (log_level & G_LOG_FLAG_RECURSION)
+    strcat (level_prefix, " (recursed)");
+  if (log_level & ALERT_LEVELS)
+    strcat (level_prefix, " **");
+
+#ifdef G_OS_WIN32
+  win32_keep_fatal_message = (log_level & G_LOG_FLAG_FATAL) != 0;
+#endif
+  return to_stdout ? 1 : 2;
+}
+
+typedef struct {
+  gchar          *log_domain;
+  GLogLevelFlags  log_level;
+  gchar          *pattern;
+} GTestExpectedMessage;
+
+static GSList *expected_messages = NULL;
+
 /**
  * g_logv:
  * @log_domain: the log domain
@@ -681,6 +873,37 @@ g_logv (const gchar   *log_domain,
   else
     msg = msg_alloc = g_strdup_vprintf (format, args);
 
+  if (expected_messages)
+    {
+      GTestExpectedMessage *expected = expected_messages->data;
+
+      expected_messages = g_slist_delete_link (expected_messages,
+                                               expected_messages);
+      if (strcmp (expected->log_domain, log_domain) == 0 &&
+          ((log_level & expected->log_level) == expected->log_level) &&
+          g_pattern_match_simple (expected->pattern, msg))
+        {
+          g_free (expected->log_domain);
+          g_free (expected->pattern);
+          g_free (expected);
+          g_free (msg_alloc);
+          return;
+        }
+      else
+        {
+          gchar level_prefix[STRING_BUFFER_SIZE];
+          gchar *expected_message;
+
+          mklevel_prefix (level_prefix, expected->log_level);
+          expected_message = g_strdup_printf ("Did not see expected message %s: %s",
+                                              level_prefix, expected->pattern);
+          g_log_default_handler (log_domain, log_level, expected_message, NULL);
+          g_free (expected_message);
+
+          log_level |= G_LOG_FLAG_FATAL;
+        }
+    }
+
   for (i = g_bit_nth_msf (log_level, -1); i >= 0; i = g_bit_nth_msf (log_level, i))
     {
       register GLogLevelFlags test_level;
@@ -833,189 +1056,90 @@ g_assert_warning (const char *log_domain,
   abort ();
 }
 
-#define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
-                           (wc == 0x7f) || \
-                           (wc >= 0x80 && wc < 0xa0)))
-     
-static gchar*
-strdup_convert (const gchar *string,
-               const gchar *charset)
-{
-  if (!g_utf8_validate (string, -1, NULL))
-    {
-      GString *gstring = g_string_new ("[Invalid UTF-8] ");
-      guchar *p;
-
-      for (p = (guchar *)string; *p; p++)
-       {
-         if (CHAR_IS_SAFE(*p) &&
-             !(*p == '\r' && *(p + 1) != '\n') &&
-             *p < 0x80)
-           g_string_append_c (gstring, *p);
-         else
-           g_string_append_printf (gstring, "\\x%02x", (guint)(guchar)*p);
-       }
-      
-      return g_string_free (gstring, FALSE);
-    }
-  else
-    {
-      GError *err = NULL;
-      
-      gchar *result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, &err);
-      if (result)
-       return result;
-      else
-       {
-         /* Not thread-safe, but doesn't matter if we print the warning twice
-          */
-         static gboolean warned = FALSE; 
-         if (!warned)
-           {
-             warned = TRUE;
-             _g_fprintf (stderr, "GLib: Cannot convert message: %s\n", err->message);
-           }
-         g_error_free (err);
-         
-         return g_strdup (string);
-       }
-    }
-}
-
-/* For a radix of 8 we need at most 3 output bytes for 1 input
- * byte. Additionally we might need up to 2 output bytes for the
- * readix prefix and 1 byte for the trailing NULL.
+/**
+ * g_test_expect_message:
+ * @log_domain: the log domain of the message
+ * @log_level: the log level of the message
+ * @pattern: a glob-style
+ *     <link linkend="glib-Glob-style-pattern-matching">pattern</link>
+ *
+ * Indicates that a message with the given @log_domain and @log_level,
+ * with text matching @pattern, is expected to be logged. When this
+ * message is logged, it will not be printed, and the test case will
+ * not abort.
+ *
+ * Use g_test_assert_expected_messages() to assert that all
+ * previously-expected messages have been seen and suppressed.
+ *
+ * You can call this multiple times in a row, if multiple messages are
+ * expected as a result of a single call. (The messages must appear in
+ * the same order as the calls to g_test_expect_message().)
+ *
+ * For example:
+ *
+ * |[
+ *   /&ast; g_main_context_push_thread_default() should fail if the
+ *    &ast; context is already owned by another thread.
+ *    &ast;/
+ *   g_test_expect_message (G_LOG_DOMAIN,
+ *                          G_LOG_LEVEL_CRITICIAL,
+ *                          "assertion.*acquired_context.*failed");
+ *   g_main_context_push_thread_default (bad_context);
+ *   g_test_assert_expected_messages ();
+ * ]|
+ *
+ * Note that you cannot use this to test g_error() messages, since
+ * g_error() intentionally never returns even if the program doesn't
+ * abort; use g_test_trap_fork() in this case.
+ *
+ * Since: 2.34
  */
-#define FORMAT_UNSIGNED_BUFSIZE ((GLIB_SIZEOF_LONG * 3) + 3)
-
-static void
-format_unsigned (gchar  *buf,
-                gulong  num,
-                guint   radix)
+void
+g_test_expect_message (const gchar    *log_domain,
+                       GLogLevelFlags  log_level,
+                       const gchar    *pattern)
 {
-  gulong tmp;
-  gchar c;
-  gint i, n;
+  GTestExpectedMessage *expected;
 
-  /* we may not call _any_ GLib functions here (or macros like g_return_if_fail()) */
+  expected = g_new (GTestExpectedMessage, 1);
+  expected->log_domain = g_strdup (log_domain);
+  expected->log_level = log_level;
+  expected->pattern = g_strdup (pattern);
 
-  if (radix != 8 && radix != 10 && radix != 16)
-    {
-      *buf = '\000';
-      return;
-    }
-  
-  if (!num)
-    {
-      *buf++ = '0';
-      *buf = '\000';
-      return;
-    } 
-  
-  if (radix == 16)
-    {
-      *buf++ = '0';
-      *buf++ = 'x';
-    }
-  else if (radix == 8)
-    {
-      *buf++ = '0';
-    }
-       
-  n = 0;
-  tmp = num;
-  while (tmp)
-    {
-      tmp /= radix;
-      n++;
-    }
-
-  i = n;
-
-  /* Again we can't use g_assert; actually this check should _never_ fail. */
-  if (n > FORMAT_UNSIGNED_BUFSIZE - 3)
-    {
-      *buf = '\000';
-      return;
-    }
-
-  while (num)
-    {
-      i--;
-      c = (num % radix);
-      if (c < 10)
-       buf[i] = c + '0';
-      else
-       buf[i] = c + 'a' - 10;
-      num /= radix;
-    }
-  
-  buf[n] = '\000';
+  expected_messages = g_slist_append (expected_messages, expected);
 }
 
-/* string size big enough to hold level prefix */
-#define        STRING_BUFFER_SIZE      (FORMAT_UNSIGNED_BUFSIZE + 32)
-
-#define        ALERT_LEVELS            (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)
-
-/* these are emitted by the default log handler */
-#define DEFAULT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE)
-/* these are filtered by G_MESSAGES_DEBUG by the default log handler */
-#define INFO_LEVELS (G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG)
-
-static int
-mklevel_prefix (gchar          level_prefix[STRING_BUFFER_SIZE],
-               GLogLevelFlags log_level)
+void
+g_test_assert_expected_messages_internal (const char     *domain,
+                                          const char     *file,
+                                          int             line,
+                                          const char     *func)
 {
-  gboolean to_stdout = TRUE;
+  if (expected_messages)
+    {
+      GTestExpectedMessage *expected;
+      gchar level_prefix[STRING_BUFFER_SIZE];
+      gchar *message;
 
-  /* we may not call _any_ GLib functions here */
+      expected = expected_messages->data;
 
-  switch (log_level & G_LOG_LEVEL_MASK)
-    {
-    case G_LOG_LEVEL_ERROR:
-      strcpy (level_prefix, "ERROR");
-      to_stdout = FALSE;
-      break;
-    case G_LOG_LEVEL_CRITICAL:
-      strcpy (level_prefix, "CRITICAL");
-      to_stdout = FALSE;
-      break;
-    case G_LOG_LEVEL_WARNING:
-      strcpy (level_prefix, "WARNING");
-      to_stdout = FALSE;
-      break;
-    case G_LOG_LEVEL_MESSAGE:
-      strcpy (level_prefix, "Message");
-      to_stdout = FALSE;
-      break;
-    case G_LOG_LEVEL_INFO:
-      strcpy (level_prefix, "INFO");
-      break;
-    case G_LOG_LEVEL_DEBUG:
-      strcpy (level_prefix, "DEBUG");
-      break;
-    default:
-      if (log_level)
-       {
-         strcpy (level_prefix, "LOG-");
-         format_unsigned (level_prefix + 4, log_level & G_LOG_LEVEL_MASK, 16);
-       }
-      else
-       strcpy (level_prefix, "LOG");
-      break;
+      mklevel_prefix (level_prefix, expected->log_level);
+      message = g_strdup_printf ("Did not see expected message %s: %s",
+                                 level_prefix, expected->pattern);
+      g_assertion_message (domain, file, line, func, message);
+      g_free (message);
     }
-  if (log_level & G_LOG_FLAG_RECURSION)
-    strcat (level_prefix, " (recursed)");
-  if (log_level & ALERT_LEVELS)
-    strcat (level_prefix, " **");
-
-#ifdef G_OS_WIN32
-  win32_keep_fatal_message = (log_level & G_LOG_FLAG_FATAL) != 0;
-#endif
-  return to_stdout ? 1 : 2;
 }
 
+/**
+ * g_test_assert_expected_messages:
+ *
+ * Asserts that all messages previously indicated via
+ * g_test_expect_message() have been seen and suppressed.
+ *
+ * Since: 2.34
+ */
+
 void
 _g_log_fallback_handler (const gchar   *log_domain,
                         GLogLevelFlags log_level,
index 3cd173c..90287d3 100644 (file)
@@ -296,6 +296,16 @@ void
 g_test_log_set_fatal_handler            (GTestLogFatalFunc log_func,
                                          gpointer          user_data);
 
+void    g_test_expect_message                    (const gchar    *log_domain,
+                                                  GLogLevelFlags  log_level,
+                                                  const gchar    *pattern);
+void    g_test_assert_expected_messages_internal (const char     *domain,
+                                                  const char     *file,
+                                                  int             line,
+                                                  const char     *func);
+
+#define g_test_assert_expected_messages() g_test_assert_expected_messages_internal (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC)
+
 G_END_DECLS
 
 #endif /* __G_TEST_UTILS_H__ */
index 2b257f2..76e358c 100644 (file)
@@ -236,6 +236,91 @@ test_fatal_log_handler (void)
   g_test_trap_assert_failed ();
 }
 
+static void
+expected_messages_helper (void)
+{
+  g_warning ("This is a %d warning", g_random_int ());
+  g_return_if_reached ();
+}
+
+static void
+test_expected_messages (void)
+{
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      expected_messages_helper ();
+      exit (0);
+    }
+  g_test_trap_assert_failed ();
+  g_test_trap_assert_stderr ("*This is a * warning*");
+  g_test_trap_assert_stderr_unmatched ("*should not be reached*");
+
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+                             "This is a * warning");
+      expected_messages_helper ();
+      exit (0);
+    }
+  g_test_trap_assert_failed ();
+  g_test_trap_assert_stderr_unmatched ("*This is a * warning*");
+  g_test_trap_assert_stderr ("*should not be reached*");
+
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "*should not be *");
+      expected_messages_helper ();
+      exit (0);
+    }
+  g_test_trap_assert_failed ();
+  g_test_trap_assert_stderr_unmatched ("*should not be reached*");
+  g_test_trap_assert_stderr ("*Did not see expected message CRITICAL*should not be *WARNING*This is a * warning*");
+
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+                             "This is a * warning");
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "*should not be reached");
+      expected_messages_helper ();
+      g_test_assert_expected_messages ();
+      exit (0);
+    }
+  g_test_trap_assert_passed ();
+  g_test_trap_assert_stderr ("");
+
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+                             "This is a * warning");
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "*should not be reached");
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "nope");
+      expected_messages_helper ();
+      /* If we don't assert, it won't notice the missing message */
+      exit (0);
+    }
+  g_test_trap_assert_passed ();
+  g_test_trap_assert_stderr ("");
+
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+                             "This is a * warning");
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "*should not be reached");
+      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+                             "nope");
+      expected_messages_helper ();
+      g_test_assert_expected_messages ();
+      exit (0);
+    }
+  g_test_trap_assert_failed ();
+  g_test_trap_assert_stderr ("*Did not see expected message CRITICAL*nope*");
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -255,6 +340,7 @@ main (int   argc,
   if (g_test_slow())
     g_test_add_func ("/forking/timeout", test_fork_timeout);
   g_test_add_func ("/misc/fatal-log-handler", test_fatal_log_handler);
+  g_test_add_func ("/misc/expected-messages", test_expected_messages);
 
   return g_test_run();
 }