diff mbox series

[pushed] diagnostics: use unicode art for interprocedural depth

Message ID 20240516013000.2967650-4-dmalcolm@redhat.com
State New
Headers show
Series [pushed] diagnostics: use unicode art for interprocedural depth | expand

Commit Message

David Malcolm May 16, 2024, 1:30 a.m. UTC
Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Successful run of analyzer integration tests on x86_64-pc-linux-gnu.
Pushed to trunk as r15-535-ge656656e711949.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c: Update expected
	output to use unicode for depth indication.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c: Likewise.

gcc/ChangeLog:
	* text-art/theme.cc (ascii_theme::get_cppchar): Add
	cell_kind::INTERPROCEDURAL_*.
	(unicode_theme::get_cppchar): Likewise.
	* text-art/theme.h (theme::cell_kind): Likewise.
	* tree-diagnostic-path.cc:
	(thread_event_printer::print_swimlane_for_event_range): Use the
	above to get characters for indicating interprocedural stack
	depth activity, falling back to ascii.
	(selftest::test_interprocedural_path_1): Test with both ascii
	and unicode themes.
	(selftest::test_interprocedural_path_2): Likewise.
	(selftest::test_recursion): Likewise.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 .../analyzer/out-of-bounds-diagram-1-emoji.c  |  26 +-
 .../out-of-bounds-diagram-1-unicode.c         |  26 +-
 gcc/text-art/theme.cc                         |  30 ++
 gcc/text-art/theme.h                          |  10 +
 gcc/tree-diagnostic-path.cc                   | 381 ++++++++++++------
 5 files changed, 331 insertions(+), 142 deletions(-)
diff mbox series

Patch

diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c
index 7b4ecf0d6b0c..8d22e4109628 100644
--- a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c
@@ -18,19 +18,19 @@  void int_arr_write_element_after_end_off_by_one(int32_t x)
    arr[10] = x;
    ~~~~~~~~^~~
   event 1 (depth 0)
-    |
-    | int32_t arr[10];
-    |         ^~~
-    |         |
-    |         (1) capacity: 40 bytes
-    |
-    +--> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
-           |
-           |   arr[10] = x;
-           |   ~~~~~~~~^~~
-           |           |
-           |           (2) ⚠️  out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
-           |
+    │
+    │ int32_t arr[10];
+    │         ^~~
+    │         |
+    │         (1) capacity: 40 bytes
+    │
+    └──> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
+           │
+           │   arr[10] = x;
+           │   ~~~~~~~~^~~
+           │           |
+           │           (2) ⚠️  out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
+           │
    { dg-end-multiline-output "" } */
 
 /* { dg-begin-multiline-output "" }
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c
index 71f66ff87c9e..58c4a7bedf34 100644
--- a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c
@@ -18,19 +18,19 @@  void int_arr_write_element_after_end_off_by_one(int32_t x)
    arr[10] = x;
    ~~~~~~~~^~~
   event 1 (depth 0)
-    |
-    | int32_t arr[10];
-    |         ^~~
-    |         |
-    |         (1) capacity: 40 bytes
-    |
-    +--> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
-           |
-           |   arr[10] = x;
-           |   ~~~~~~~~^~~
-           |           |
-           |           (2) out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
-           |
+    │
+    │ int32_t arr[10];
+    │         ^~~
+    │         |
+    │         (1) capacity: 40 bytes
+    │
+    └──> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
+           │
+           │   arr[10] = x;
+           │   ~~~~~~~~^~~
+           │           |
+           │           (2) out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
+           │
    { dg-end-multiline-output "" } */
 
 /* { dg-begin-multiline-output "" }
diff --git a/gcc/text-art/theme.cc b/gcc/text-art/theme.cc
index 4ac0cae92e26..cba4c585c469 100644
--- a/gcc/text-art/theme.cc
+++ b/gcc/text-art/theme.cc
@@ -125,6 +125,21 @@  ascii_theme::get_cppchar (enum cell_kind kind) const
     case cell_kind::Y_ARROW_UP_TAIL:
     case cell_kind::Y_ARROW_DOWN_TAIL:
       return '|';
+
+    case cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT:
+      return '+';
+    case cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE:
+      return '-';
+    case cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT:
+      return '>';
+    case cell_kind::INTERPROCEDURAL_DEPTH_MARKER:
+      return '|';
+    case cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT:
+      return '<';
+    case cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE:
+      return '-';
+    case cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT:
+      return '+';
     }
 }
 
@@ -180,5 +195,20 @@  unicode_theme::get_cppchar (enum cell_kind kind) const
     case cell_kind::Y_ARROW_UP_TAIL:
     case cell_kind::Y_ARROW_DOWN_TAIL:
       return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+
+    case cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT:
+      return 0x2514; /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT  */
+    case cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE:
+      return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+    case cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT:
+      return '>';
+    case cell_kind::INTERPROCEDURAL_DEPTH_MARKER:
+      return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+    case cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT:
+      return '<';
+    case cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE:
+      return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+    case cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT:
+      return 0x2518; /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT.  */
     }
 }
diff --git a/gcc/text-art/theme.h b/gcc/text-art/theme.h
index 62845ac8074b..dd50f5a5e416 100644
--- a/gcc/text-art/theme.h
+++ b/gcc/text-art/theme.h
@@ -63,6 +63,16 @@  class theme
     Y_ARROW_UP_TAIL,
     Y_ARROW_DOWN_HEAD,
     Y_ARROW_DOWN_TAIL,
+
+    /* The interprocedural depth indications shown in execution paths
+       with DPF_INLINE_EVENTS.  */
+    INTERPROCEDURAL_PUSH_FRAME_LEFT,   /* e.g. "+".  */
+    INTERPROCEDURAL_PUSH_FRAME_MIDDLE, /* e.g. "-".  */
+    INTERPROCEDURAL_PUSH_FRAME_RIGHT,  /* e.g. ">".  */
+    INTERPROCEDURAL_DEPTH_MARKER,       /* e.g. "|".  */
+    INTERPROCEDURAL_POP_FRAMES_LEFT,   /* e.g. "<".  */
+    INTERPROCEDURAL_POP_FRAMES_MIDDLE, /* e.g. "-".  */
+    INTERPROCEDURAL_POP_FRAMES_RIGHT  /* e.g. "+".  */
   };
 
   virtual ~theme () = default;
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index bc90aaf321cc..9ae5191774ec 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -437,6 +437,15 @@  public:
       = colorize_start (pp_show_color (pp), line_color);
     const char *end_line_color = colorize_stop (pp_show_color (pp));
 
+    text_art::ascii_theme fallback_theme;
+    text_art::theme *theme = dc->get_diagram_theme ();
+    if (!theme)
+      theme = &fallback_theme;
+
+    cppchar_t depth_marker_char = theme->get_cppchar
+      (text_art::theme::cell_kind::INTERPROCEDURAL_DEPTH_MARKER);
+    /* e.g. "|".  */
+
     const bool interprocedural_p = m_per_thread_summary.interprocedural_p ();
 
     write_indent (pp, m_cur_indent);
@@ -446,11 +455,21 @@  public:
 	  {
 	    gcc_assert (interprocedural_p);
 	    /* Show pushed stack frame(s).  */
-	    const char *push_prefix = "+--> ";
+	    cppchar_t left = theme->get_cppchar
+	      (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT);
+	    cppchar_t middle = theme->get_cppchar
+	      (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE);
+	    cppchar_t right = theme->get_cppchar
+	      (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT);
+	    /* e.g. "+--> ".  */
 	    pp_string (pp, start_line_color);
-	    pp_string (pp, push_prefix);
+	    pp_unicode_character (pp, left);
+	    pp_unicode_character (pp, middle);
+	    pp_unicode_character (pp, middle);
+	    pp_unicode_character (pp, right);
+	    pp_space (pp);
 	    pp_string (pp, end_line_color);
-	    m_cur_indent += strlen (push_prefix);
+	    m_cur_indent += 5;
 	  }
       }
     if (range->m_fndecl)
@@ -473,7 +492,7 @@  public:
       {
 	write_indent (pp, m_cur_indent + per_frame_indent);
 	pp_string (pp, start_line_color);
-	pp_string (pp, "|");
+	pp_unicode_character (pp, depth_marker_char);
 	pp_string (pp, end_line_color);
 	pp_newline (pp);
 
@@ -483,7 +502,7 @@  public:
 	  pretty_printer tmp_pp;
 	  write_indent (&tmp_pp, m_cur_indent + per_frame_indent);
 	  pp_string (&tmp_pp, start_line_color);
-	  pp_string (&tmp_pp, "|");
+	  pp_unicode_character (&tmp_pp, depth_marker_char);
 	  pp_string (&tmp_pp, end_line_color);
 	  prefix = xstrdup (pp_formatted_text (&tmp_pp));
 	}
@@ -494,7 +513,7 @@  public:
 
 	write_indent (pp, m_cur_indent + per_frame_indent);
 	pp_string (pp, start_line_color);
-	pp_string (pp, "|");
+	pp_unicode_character (pp, depth_marker_char);
 	pp_string (pp, end_line_color);
 	pp_newline (pp);
       }
@@ -510,9 +529,15 @@  public:
 		/* Show returning from stack frame(s), by printing
 		   something like:
 		   "                   |\n"
-		   "     <------------ +\n"
+		   "     <-------------+\n"
 		   "     |\n".  */
 		gcc_assert (interprocedural_p);
+		cppchar_t left = theme->get_cppchar
+		  (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT);
+		cppchar_t middle = theme->get_cppchar
+		  (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE);
+		cppchar_t right = theme->get_cppchar
+		  (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT);
 		int vbar_for_next_frame
 		  = *m_vbar_column_for_depth.get (next_range->m_stack_depth);
 
@@ -520,18 +545,18 @@  public:
 		  = vbar_for_next_frame - per_frame_indent;
 		write_indent (pp, vbar_for_next_frame);
 		pp_string (pp, start_line_color);
-		pp_character (pp, '<');
+		pp_unicode_character (pp, left);
 		for (int i = indent_for_next_frame + per_frame_indent;
 		     i < m_cur_indent + per_frame_indent - 1; i++)
-		  pp_character (pp, '-');
-		pp_character (pp, '+');
+		  pp_unicode_character (pp, middle);
+		pp_unicode_character (pp, right);
 		pp_string (pp, end_line_color);
 		pp_newline (pp);
 		m_cur_indent = indent_for_next_frame;
 
 		write_indent (pp, vbar_for_next_frame);
 		pp_string (pp, start_line_color);
-		pp_character (pp, '|');
+		pp_unicode_character (pp, depth_marker_char);
 		pp_string (pp, end_line_color);
 		pp_newline (pp);
 	      }
@@ -864,59 +889,119 @@  test_interprocedural_path_1 (pretty_printer *event_pp)
   path_summary summary (path, false);
   ASSERT_EQ (summary.get_num_ranges (), 9);
 
-  test_diagnostic_context dc;
-  print_path_summary_as_text (&summary, &dc, true);
-  ASSERT_STREQ
-    ("  `test': events 1-2 (depth 0)\n"
-     "    |\n"
-     "    | (1): entering `test'\n"
-     "    | (2): calling `make_boxed_int'\n"
-     "    |\n"
-     "    +--> `make_boxed_int': events 3-4 (depth 1)\n"
-     "           |\n"
-     "           | (3): entering `make_boxed_int'\n"
-     "           | (4): calling `wrapped_malloc'\n"
-     "           |\n"
-     "           +--> `wrapped_malloc': events 5-6 (depth 2)\n"
-     "                  |\n"
-     "                  | (5): entering `wrapped_malloc'\n"
-     "                  | (6): calling malloc\n"
-     "                  |\n"
-     "    <-------------+\n"
-     "    |\n"
-     "  `test': events 7-8 (depth 0)\n"
-     "    |\n"
-     "    | (7): returning to `test'\n"
-     "    | (8): calling `free_boxed_int'\n"
-     "    |\n"
-     "    +--> `free_boxed_int': events 9-10 (depth 1)\n"
-     "           |\n"
-     "           | (9): entering `free_boxed_int'\n"
-     "           | (10): calling `wrapped_free'\n"
-     "           |\n"
-     "           +--> `wrapped_free': events 11-12 (depth 2)\n"
-     "                  |\n"
-     "                  | (11): entering `wrapped_free'\n"
-     "                  | (12): calling free\n"
-     "                  |\n"
-     "    <-------------+\n"
-     "    |\n"
-     "  `test': events 13-14 (depth 0)\n"
-     "    |\n"
-     "    | (13): returning to `test'\n"
-     "    | (14): calling `free_boxed_int'\n"
-     "    |\n"
-     "    +--> `free_boxed_int': events 15-16 (depth 1)\n"
-     "           |\n"
-     "           | (15): entering `free_boxed_int'\n"
-     "           | (16): calling `wrapped_free'\n"
-     "           |\n"
-     "           +--> `wrapped_free': events 17-18 (depth 2)\n"
-     "                  |\n"
-     "                  | (17): entering `wrapped_free'\n"
-     "                  | (18): calling free\n"
-     "                  |\n",
-     pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    print_path_summary_as_text (&summary, &dc, true);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "    |\n"
+       "    | (1): entering `test'\n"
+       "    | (2): calling `make_boxed_int'\n"
+       "    |\n"
+       "    +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "           |\n"
+       "           | (3): entering `make_boxed_int'\n"
+       "           | (4): calling `wrapped_malloc'\n"
+       "           |\n"
+       "           +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                  |\n"
+       "                  | (5): entering `wrapped_malloc'\n"
+       "                  | (6): calling malloc\n"
+       "                  |\n"
+       "    <-------------+\n"
+       "    |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "    |\n"
+       "    | (7): returning to `test'\n"
+       "    | (8): calling `free_boxed_int'\n"
+       "    |\n"
+       "    +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "           |\n"
+       "           | (9): entering `free_boxed_int'\n"
+       "           | (10): calling `wrapped_free'\n"
+       "           |\n"
+       "           +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                  |\n"
+       "                  | (11): entering `wrapped_free'\n"
+       "                  | (12): calling free\n"
+       "                  |\n"
+       "    <-------------+\n"
+       "    |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "    |\n"
+       "    | (13): returning to `test'\n"
+       "    | (14): calling `free_boxed_int'\n"
+       "    |\n"
+       "    +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "           |\n"
+       "           | (15): entering `free_boxed_int'\n"
+       "           | (16): calling `wrapped_free'\n"
+       "           |\n"
+       "           +--> `wrapped_free': events 17-18 (depth 2)\n"
+       "                  |\n"
+       "                  | (17): entering `wrapped_free'\n"
+       "                  | (18): calling free\n"
+       "                  |\n",
+       pp_formatted_text (dc.printer));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    print_path_summary_as_text (&summary, &dc, true);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "    │\n"
+       "    │ (1): entering `test'\n"
+       "    │ (2): calling `make_boxed_int'\n"
+       "    │\n"
+       "    └──> `make_boxed_int': events 3-4 (depth 1)\n"
+       "           │\n"
+       "           │ (3): entering `make_boxed_int'\n"
+       "           │ (4): calling `wrapped_malloc'\n"
+       "           │\n"
+       "           └──> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                  │\n"
+       "                  │ (5): entering `wrapped_malloc'\n"
+       "                  │ (6): calling malloc\n"
+       "                  │\n"
+       "    <─────────────┘\n"
+       "    │\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "    │\n"
+       "    │ (7): returning to `test'\n"
+       "    │ (8): calling `free_boxed_int'\n"
+       "    │\n"
+       "    └──> `free_boxed_int': events 9-10 (depth 1)\n"
+       "           │\n"
+       "           │ (9): entering `free_boxed_int'\n"
+       "           │ (10): calling `wrapped_free'\n"
+       "           │\n"
+       "           └──> `wrapped_free': events 11-12 (depth 2)\n"
+       "                  │\n"
+       "                  │ (11): entering `wrapped_free'\n"
+       "                  │ (12): calling free\n"
+       "                  │\n"
+       "    <─────────────┘\n"
+       "    │\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "    │\n"
+       "    │ (13): returning to `test'\n"
+       "    │ (14): calling `free_boxed_int'\n"
+       "    │\n"
+       "    └──> `free_boxed_int': events 15-16 (depth 1)\n"
+       "           │\n"
+       "           │ (15): entering `free_boxed_int'\n"
+       "           │ (16): calling `wrapped_free'\n"
+       "           │\n"
+       "           └──> `wrapped_free': events 17-18 (depth 2)\n"
+       "                  │\n"
+       "                  │ (17): entering `wrapped_free'\n"
+       "                  │ (18): calling free\n"
+       "                  │\n",
+       pp_formatted_text (dc.printer));
+  }
+
 }
 
 /* Example where we pop the stack to an intermediate frame, rather than the
@@ -946,35 +1031,70 @@  test_interprocedural_path_2 (pretty_printer *event_pp)
   path_summary summary (path, false);
   ASSERT_EQ (summary.get_num_ranges (), 5);
 
-  test_diagnostic_context dc;
-  print_path_summary_as_text (&summary, &dc, true);
-  ASSERT_STREQ
-    ("  `foo': events 1-2 (depth 0)\n"
-     "    |\n"
-     "    | (1): entering `foo'\n"
-     "    | (2): calling `bar'\n"
-     "    |\n"
-     "    +--> `bar': events 3-4 (depth 1)\n"
-     "           |\n"
-     "           | (3): entering `bar'\n"
-     "           | (4): calling `baz'\n"
-     "           |\n"
-     "           +--> `baz': event 5 (depth 2)\n"
-     "                  |\n"
-     "                  | (5): entering `baz'\n"
-     "                  |\n"
-     "           <------+\n"
-     "           |\n"
-     "         `bar': events 6-7 (depth 1)\n"
-     "           |\n"
-     "           | (6): returning to `bar'\n"
-     "           | (7): calling `baz'\n"
-     "           |\n"
-     "           +--> `baz': event 8 (depth 2)\n"
-     "                  |\n"
-     "                  | (8): entering `baz'\n"
-     "                  |\n",
-     pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    print_path_summary_as_text (&summary, &dc, true);
+    ASSERT_STREQ
+      ("  `foo': events 1-2 (depth 0)\n"
+       "    |\n"
+       "    | (1): entering `foo'\n"
+       "    | (2): calling `bar'\n"
+       "    |\n"
+       "    +--> `bar': events 3-4 (depth 1)\n"
+       "           |\n"
+       "           | (3): entering `bar'\n"
+       "           | (4): calling `baz'\n"
+       "           |\n"
+       "           +--> `baz': event 5 (depth 2)\n"
+       "                  |\n"
+       "                  | (5): entering `baz'\n"
+       "                  |\n"
+       "           <------+\n"
+       "           |\n"
+       "         `bar': events 6-7 (depth 1)\n"
+       "           |\n"
+       "           | (6): returning to `bar'\n"
+       "           | (7): calling `baz'\n"
+       "           |\n"
+       "           +--> `baz': event 8 (depth 2)\n"
+       "                  |\n"
+       "                  | (8): entering `baz'\n"
+       "                  |\n",
+       pp_formatted_text (dc.printer));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    print_path_summary_as_text (&summary, &dc, true);
+    ASSERT_STREQ
+      ("  `foo': events 1-2 (depth 0)\n"
+       "    │\n"
+       "    │ (1): entering `foo'\n"
+       "    │ (2): calling `bar'\n"
+       "    │\n"
+       "    └──> `bar': events 3-4 (depth 1)\n"
+       "           │\n"
+       "           │ (3): entering `bar'\n"
+       "           │ (4): calling `baz'\n"
+       "           │\n"
+       "           └──> `baz': event 5 (depth 2)\n"
+       "                  │\n"
+       "                  │ (5): entering `baz'\n"
+       "                  │\n"
+       "           <──────┘\n"
+       "           │\n"
+       "         `bar': events 6-7 (depth 1)\n"
+       "           │\n"
+       "           │ (6): returning to `bar'\n"
+       "           │ (7): calling `baz'\n"
+       "           │\n"
+       "           └──> `baz': event 8 (depth 2)\n"
+       "                  │\n"
+       "                  │ (8): entering `baz'\n"
+       "                  │\n",
+       pp_formatted_text (dc.printer));
+  }
 }
 
 /* Verify that print_path_summary is sane in the face of a recursive
@@ -998,29 +1118,58 @@  test_recursion (pretty_printer *event_pp)
   path_summary summary (path, false);
   ASSERT_EQ (summary.get_num_ranges (), 4);
 
-  test_diagnostic_context dc;
-  print_path_summary_as_text (&summary, &dc, true);
-  ASSERT_STREQ
-    ("  `factorial': events 1-2 (depth 0)\n"
-     "    |\n"
-     "    | (1): entering `factorial'\n"
-     "    | (2): calling `factorial'\n"
-     "    |\n"
-     "    +--> `factorial': events 3-4 (depth 1)\n"
-     "           |\n"
-     "           | (3): entering `factorial'\n"
-     "           | (4): calling `factorial'\n"
-     "           |\n"
-     "           +--> `factorial': events 5-6 (depth 2)\n"
-     "                  |\n"
-     "                  | (5): entering `factorial'\n"
-     "                  | (6): calling `factorial'\n"
-     "                  |\n"
-     "                  +--> `factorial': event 7 (depth 3)\n"
-     "                         |\n"
-     "                         | (7): entering `factorial'\n"
-     "                         |\n",
-     pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    print_path_summary_as_text (&summary, &dc, true);
+    ASSERT_STREQ
+      ("  `factorial': events 1-2 (depth 0)\n"
+       "    |\n"
+       "    | (1): entering `factorial'\n"
+       "    | (2): calling `factorial'\n"
+       "    |\n"
+       "    +--> `factorial': events 3-4 (depth 1)\n"
+       "           |\n"
+       "           | (3): entering `factorial'\n"
+       "           | (4): calling `factorial'\n"
+       "           |\n"
+       "           +--> `factorial': events 5-6 (depth 2)\n"
+       "                  |\n"
+       "                  | (5): entering `factorial'\n"
+       "                  | (6): calling `factorial'\n"
+       "                  |\n"
+       "                  +--> `factorial': event 7 (depth 3)\n"
+       "                         |\n"
+       "                         | (7): entering `factorial'\n"
+       "                         |\n",
+       pp_formatted_text (dc.printer));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    print_path_summary_as_text (&summary, &dc, true);
+    ASSERT_STREQ
+      ("  `factorial': events 1-2 (depth 0)\n"
+       "    │\n"
+       "    │ (1): entering `factorial'\n"
+       "    │ (2): calling `factorial'\n"
+       "    │\n"
+       "    └──> `factorial': events 3-4 (depth 1)\n"
+       "           │\n"
+       "           │ (3): entering `factorial'\n"
+       "           │ (4): calling `factorial'\n"
+       "           │\n"
+       "           └──> `factorial': events 5-6 (depth 2)\n"
+       "                  │\n"
+       "                  │ (5): entering `factorial'\n"
+       "                  │ (6): calling `factorial'\n"
+       "                  │\n"
+       "                  └──> `factorial': event 7 (depth 3)\n"
+       "                         │\n"
+       "                         │ (7): entering `factorial'\n"
+       "                         │\n",
+       pp_formatted_text (dc.printer));
+  }
 }
 
 /* Run all of the selftests within this file.  */