[glib] gtestutils: add g_text_expect_message()



commit 25ac137c0a47ccc5214dabeaa41da18dac2b0cee
Author: Dan Winship <danw gnome org>
Date:   Mon Jul 30 16:05:08 2012 -0400

    gtestutils: add g_text_expect_message()
    
    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 |    4 +
 glib/glib.symbols                     |    2 +
 glib/gmessages.c                      |  466 +++++++++++++++++++++------------
 glib/gtestutils.h                     |   10 +
 glib/tests/testing.c                  |   86 ++++++
 5 files changed, 397 insertions(+), 171 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index a7f0a65..2d7f232 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -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
 
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 49c6442..9609798 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -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
diff --git a/glib/gmessages.c b/glib/gmessages.c
index b1697d6..c83d3e8 100644
--- a/glib/gmessages.c
+++ b/glib/gmessages.c
@@ -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,
diff --git a/glib/gtestutils.h b/glib/gtestutils.h
index 3cd173c..90287d3 100644
--- a/glib/gtestutils.h
+++ b/glib/gtestutils.h
@@ -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__ */
diff --git a/glib/tests/testing.c b/glib/tests/testing.c
index 2b257f2..76e358c 100644
--- a/glib/tests/testing.c
+++ b/glib/tests/testing.c
@@ -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();
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]