gtestutils: Define a custom g_print handler for TAP

When using TAP output in gtest, all the printed strings should be commented
not to break the spec, so enforce this at GTest level, by ensuring that
all the lines written via g_print() will be in the commented form and will
respect the subtest indentation.

As per this we can remove some custom code in g_test_log() as now there are
very few cases in which we need to use the default print handler which is
now private when testing.
This commit is contained in:
Marco Trevisan (Treviño) 2023-01-14 01:47:07 +01:00
parent 3d1736c828
commit 8b7b5f5457
4 changed files with 250 additions and 61 deletions

View File

@ -848,7 +848,10 @@ static void gtest_default_log_handler (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer unused_data);
static void g_test_tap_print (unsigned subtest_level,
gboolean commented,
const char *format,
...) G_GNUC_PRINTF (3, 4);
static const char * const g_test_result_names[] = {
"OK",
@ -915,8 +918,90 @@ static GTestConfig mutable_test_config_vars = {
};
const GTestConfig * const g_test_config_vars = &mutable_test_config_vars;
static gboolean no_g_set_prgname = FALSE;
static GPrintFunc g_default_print_func = NULL;
/* --- functions --- */
static inline gboolean
is_subtest (void)
{
return test_is_subtest || test_in_forked_child || test_in_subprocess;
}
static void
g_test_print_handler_full (const gchar *string,
gboolean use_tap_format,
gboolean is_tap_comment,
unsigned subtest_level)
{
g_assert (string != NULL);
if (G_LIKELY (use_tap_format) && strchr (string, '\n') != NULL)
{
static gboolean last_had_final_newline = TRUE;
GString *output = g_string_new_len (NULL, strlen (string) + 2);
const char *line = string;
do
{
const char *next = strchr (line, '\n');
if (last_had_final_newline && (next || *line != '\0'))
{
for (unsigned l = 0; l < subtest_level; ++l)
g_string_append (output, TAP_SUBTEST_PREFIX);
if G_LIKELY (is_tap_comment)
g_string_append (output, "# ");
}
if (next)
{
next += 1; /* Include the newline */
g_string_append_len (output, line, next - line);
}
else
{
g_string_append (output, line);
last_had_final_newline = (*line == '\0');
}
line = next;
}
while (line != NULL);
g_default_print_func (output->str);
g_string_free (g_steal_pointer (&output), TRUE);
}
else
{
g_default_print_func (string);
}
}
static void
g_test_print_handler (const gchar *string)
{
g_test_print_handler_full (string, test_tap_log, TRUE, is_subtest () ? 1 : 0);
}
static void
g_test_tap_print (unsigned subtest_level,
gboolean commented,
const char *format,
...)
{
va_list args;
char *string;
va_start (args, format);
string = g_strdup_vprintf (format, args);
va_end (args);
g_test_print_handler_full (string, TRUE, commented, subtest_level);
g_free (string);
}
const char*
g_test_log_type_name (GTestLogType log_type)
{
@ -997,23 +1082,33 @@ g_test_log (GTestLogType lbit,
gchar *astrings[3] = { NULL, NULL, NULL };
guint8 *dbuffer;
guint32 dbufferlen;
gboolean is_subtest;
const char *tap_prefix;
unsigned subtest_level;
is_subtest = test_is_subtest || test_in_forked_child || test_in_subprocess;
tap_prefix = test_tap_log && is_subtest ? TAP_SUBTEST_PREFIX : "";
if (g_once_init_enter (&g_default_print_func))
{
g_once_init_leave (&g_default_print_func,
g_set_print_handler (g_test_print_handler));
g_assert_nonnull (g_default_print_func);
}
subtest_level = is_subtest () ? 1 : 0;
switch (lbit)
{
case G_TEST_LOG_START_BINARY:
if (test_tap_log)
{
if (!is_subtest)
g_print ("TAP version 13\n");
if (!is_subtest ())
{
g_test_tap_print (0, FALSE, "TAP version 13\n");
}
else
g_print ("# Subtest: %s\n", test_argv0);
{
g_test_tap_print (subtest_level > 0 ? subtest_level - 1 : 0, TRUE,
"Subtest: %s\n", test_argv0);
}
g_print ("%s# random seed: %s\n", tap_prefix, string2);
g_print ("random seed: %s\n", string2);
}
else if (g_test_verbose ())
{
@ -1026,9 +1121,9 @@ g_test_log (GTestLogType lbit,
/* We only print the TAP "plan" (1..n) ahead of time if we did
* not use the -p option to select specific tests to be run. */
if (string1[0] != 0)
g_print ("%s# Start of %s tests\n", tap_prefix, string1);
g_print ("Start of %s tests\n", string1);
else if (test_paths == NULL)
g_print ("%s1..%d\n", tap_prefix, test_count);
g_test_tap_print (subtest_level, FALSE, "1..%d\n", test_count);
}
break;
case G_TEST_LOG_STOP_SUITE:
@ -1038,9 +1133,9 @@ g_test_log (GTestLogType lbit,
* we were using -p, we need to print how many tests we ran at
* the end instead. */
if (string1[0] != 0)
g_print ("%s# End of %s tests\n", tap_prefix, string1);
g_print ("End of %s tests\n", string1);
else if (test_paths != NULL)
g_print ("%s1..%d\n", tap_prefix, test_run_count);
g_test_tap_print (subtest_level, FALSE, "1..%d\n", test_run_count);
}
break;
case G_TEST_LOG_STOP_CASE:
@ -1061,7 +1156,7 @@ g_test_log (GTestLogType lbit,
else
tap_output = g_string_new ("ok");
if (is_subtest)
if (is_subtest ())
g_string_prepend (tap_output, TAP_SUBTEST_PREFIX);
g_string_append_printf (tap_output, " %d %s", test_run_count, string1);
@ -1072,7 +1167,8 @@ g_test_log (GTestLogType lbit,
else if (result == G_TEST_RUN_FAILURE && string2 != NULL)
g_string_append_printf (tap_output, " - %s", string2);
g_print ("%s\n", tap_output->str);
g_string_append_c (tap_output, '\n');
g_default_print_func (tap_output->str);
g_string_free (g_steal_pointer (&tap_output), TRUE);
}
else if (g_test_verbose ())
@ -1082,7 +1178,7 @@ g_test_log (GTestLogType lbit,
if (fail && test_mode_fatal)
{
if (test_tap_log)
g_print ("Bail out!\n");
g_test_tap_print (0, FALSE, "Bail out!\n");
g_abort ();
}
if (result == G_TEST_RUN_SKIPPED || result == G_TEST_RUN_INCOMPLETE)
@ -1091,56 +1187,25 @@ g_test_log (GTestLogType lbit,
case G_TEST_LOG_SKIP_CASE:
if (test_tap_log)
{
g_print ("%sok %d %s # SKIP\n", tap_prefix,
test_run_count, string1);
g_test_tap_print (subtest_level, FALSE, "ok %d %s # SKIP\n",
test_run_count, string1);
}
break;
case G_TEST_LOG_MIN_RESULT:
if (test_tap_log)
g_print ("%s# min perf: %s\n", tap_prefix, string1);
g_print ("min perf: %s\n", string1);
else if (g_test_verbose ())
g_print ("(MINPERF:%s)\n", string1);
break;
case G_TEST_LOG_MAX_RESULT:
if (test_tap_log)
g_print ("%s# max perf: %s\n", tap_prefix, string1);
g_print ("max perf: %s\n", string1);
else if (g_test_verbose ())
g_print ("(MAXPERF:%s)\n", string1);
break;
case G_TEST_LOG_MESSAGE:
if (test_tap_log)
{
if (strchr (string1, '\n') == NULL)
g_print ("%s# %s\n", tap_prefix, string1);
else
{
GString *output = g_string_new (NULL);
const char *line = string1;
do
{
const char *next = strchr (line, '\n');
g_string_append (output, tap_prefix);
g_string_append (output, "# ");
if (next)
{
g_string_append_len (output, line, next - line + 1);
line = next + 1;
}
else
{
g_string_append (output, line);
g_string_append_c (output, '\n');
line = next;
}
}
while (line != NULL);
g_print ("%s", output->str);
g_string_free (g_steal_pointer (&output), TRUE);
}
}
g_print ("%s\n", string1);
else if (g_test_verbose ())
g_print ("(MSG: %s)\n", string1);
break;
@ -1153,10 +1218,15 @@ g_test_log (GTestLogType lbit,
while ((line = strchr (line, '\n')))
*(line++) = ' ';
if (is_subtest)
g_print ("%sBail out! %s\nBail out!\n", tap_prefix, message);
if (is_subtest ())
{
g_test_tap_print (subtest_level, FALSE, "Bail out! %s\n", message);
g_test_tap_print (0, FALSE, "Bail out!\n");
}
else
g_print ("Bail out! %s\n", message);
{
g_test_tap_print (0, FALSE, "Bail out! %s\n", message);
}
g_free (message);
}
@ -3082,7 +3152,7 @@ test_should_run (const char *test_path,
if (g_test_verbose ())
{
if (test_tap_log)
g_print ("# skipping: %s\n", test_run_name);
g_print ("skipping: %s\n", test_run_name);
else
g_print ("GTest: skipping: %s\n", test_run_name);
}
@ -3932,7 +4002,7 @@ g_test_trap_subprocess (const char *test_path,
if (g_test_verbose ())
{
if (test_tap_log)
g_print ("# subprocess: %s\n", test_path);
g_print ("subprocess: %s\n", test_path);
else
g_print ("GTest: subprocess: %s\n", test_path);
}

View File

@ -374,18 +374,20 @@ test_print_handler (void)
g_print ("bu ba");
g_assert_cmpint (my_print_count, ==, 1);
g_set_print_handler (NULL);
if (g_test_subprocess ())
{
g_set_print_handler (NULL);
old_print_handler ("default handler\n");
g_print ("bu ba\n");
return;
}
g_set_print_handler (old_print_handler);
g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
g_test_trap_assert_stdout ("*default handler" LINE_END "*");
g_test_trap_assert_stdout ("*bu ba" LINE_END "*");
g_test_trap_assert_stdout_unmatched ("*# default handler" LINE_END "*");
g_test_trap_assert_stdout_unmatched ("*# bu ba" LINE_END "*");
g_test_trap_has_passed ();
}
@ -401,15 +403,15 @@ test_printerr_handler (void)
g_printerr ("bu ba");
g_assert_cmpint (my_print_count, ==, 1);
g_set_printerr_handler (NULL);
if (g_test_subprocess ())
{
g_set_printerr_handler (NULL);
old_printerr_handler ("default handler\n");
g_printerr ("bu ba\n");
return;
}
g_set_printerr_handler (old_printerr_handler);
g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
g_test_trap_assert_stderr ("*default handler" LINE_END "*");
g_test_trap_assert_stderr ("*bu ba" LINE_END "*");

View File

@ -93,6 +93,16 @@ test_message (void)
g_test_message ("\nTests that multi\nline\nmessage\nworks with leading and trailing too\n");
}
static void
test_print (void)
{
g_print ("Tests that single line message works\n");
g_print ("test that multiple\nlines ");
g_print ("can be ");
g_print ("written ");
g_print ("separately\n");
}
int
main (int argc,
char *argv[])
@ -212,6 +222,10 @@ main (int argc,
{
g_test_add_func ("/message", test_message);
}
else if (g_strcmp0 (argv1, "print") == 0)
{
g_test_add_func ("/print", test_print);
}
else
{
g_assert_not_reached ();

View File

@ -2428,6 +2428,107 @@ test_tap_subtest_message (void)
g_ptr_array_unref (argv);
}
static void
test_tap_print (void)
{
const char *testing_helper;
GPtrArray *argv;
GError *error = NULL;
int status;
gchar *output;
char **output_lines;
char **envp;
g_test_summary ("Test the output of g_print() from the TAP output of a test.");
testing_helper = g_test_get_filename (G_TEST_BUILT, "testing-helper" EXEEXT, NULL);
argv = g_ptr_array_new ();
g_ptr_array_add (argv, (char *) testing_helper);
g_ptr_array_add (argv, "print");
g_ptr_array_add (argv, "--tap");
g_ptr_array_add (argv, NULL);
/* Remove the G_TEST_ROOT_PROCESS env so it will be considered a standalone test */
envp = g_get_environ ();
g_assert_nonnull (g_environ_getenv (envp, "G_TEST_ROOT_PROCESS"));
envp = g_environ_unsetenv (g_steal_pointer (&envp), "G_TEST_ROOT_PROCESS");
g_spawn_sync (NULL, (char **) argv->pdata, envp,
G_SPAWN_STDERR_TO_DEV_NULL,
NULL, NULL, &output, NULL, &status,
&error);
g_assert_no_error (error);
g_spawn_check_wait_status (status, &error);
g_assert_no_error (error);
const char *expected_tap_header = "\n1..1\n";
const char *interesting_lines = strstr (output, expected_tap_header);
g_assert_nonnull (interesting_lines);
interesting_lines += strlen (expected_tap_header);
output_lines = g_strsplit (interesting_lines, "\n", -1);
g_assert_cmpuint (g_strv_length (output_lines), >=, 3);
guint i = 0;
g_assert_cmpstr (output_lines[i++], ==, "# Tests that single line message works");
g_assert_cmpstr (output_lines[i++], ==, "# test that multiple");
g_assert_cmpstr (output_lines[i++], ==, "# lines can be written separately");
g_free (output);
g_strfreev (envp);
g_strfreev (output_lines);
g_ptr_array_unref (argv);
}
static void
test_tap_subtest_print (void)
{
const char *testing_helper;
GPtrArray *argv;
GError *error = NULL;
int status;
gchar *output;
char **output_lines;
g_test_summary ("Test the output of g_test_print() from the TAP output of a sub-test.");
testing_helper = g_test_get_filename (G_TEST_BUILT, "testing-helper" EXEEXT, NULL);
argv = g_ptr_array_new ();
g_ptr_array_add (argv, (char *) testing_helper);
g_ptr_array_add (argv, "print");
g_ptr_array_add (argv, "--tap");
g_ptr_array_add (argv, NULL);
g_spawn_sync (NULL, (char **) argv->pdata, NULL,
G_SPAWN_STDERR_TO_DEV_NULL,
NULL, NULL, &output, NULL, &status,
&error);
g_assert_no_error (error);
g_spawn_check_wait_status (status, &error);
g_assert_no_error (error);
const char *expected_tap_header = "\n" TAP_SUBTEST_PREFIX "1..1\n";
const char *interesting_lines = strstr (output, expected_tap_header);
g_assert_nonnull (interesting_lines);
interesting_lines += strlen (expected_tap_header);
output_lines = g_strsplit (interesting_lines, "\n", -1);
g_assert_cmpuint (g_strv_length (output_lines), >=, 3);
guint i = 0;
g_assert_cmpstr (output_lines[i++], ==, TAP_SUBTEST_PREFIX "# Tests that single line message works");
g_assert_cmpstr (output_lines[i++], ==, TAP_SUBTEST_PREFIX "# test that multiple");
g_assert_cmpstr (output_lines[i++], ==, TAP_SUBTEST_PREFIX "# lines can be written separately");
g_free (output);
g_strfreev (output_lines);
g_ptr_array_unref (argv);
}
static void
test_tap_error (void)
{
@ -2753,6 +2854,8 @@ main (int argc,
g_test_add_func ("/tap/subtest/summary", test_tap_subtest_summary);
g_test_add_func ("/tap/message", test_tap_message);
g_test_add_func ("/tap/subtest/message", test_tap_subtest_message);
g_test_add_func ("/tap/print", test_tap_print);
g_test_add_func ("/tap/subtest/print", test_tap_subtest_print);
g_test_add_func ("/tap/error", test_tap_error);
g_test_add_func ("/tap/subtest/error", test_tap_subtest_error);
g_test_add_func ("/tap/error-and-pass", test_tap_error_and_pass);