Re: [PATCH v8 18/18] perf test: Truncate printed test descriptions dynamically to avoid terminal wrapping

From: Arnaldo Carvalho de Melo

Date: Thu Jun 04 2026 - 10:53:42 EST


On Tue, Jun 02, 2026 at 10:41:29AM -0700, Ian Rogers wrote:
> When test descriptions are extremely long (e.g., the truncated perf.data
> graceful handling test is 103 characters long), they wrap across terminal
> boundaries.
>
> Because the ANSI escape code to delete the line (PERF_COLOR_DELETE_LINE)
> only clears a single terminal line, visual wrapping leaves orphan
> wrapped lines on the screen, which results in the test description being
> printed multiple times.
>
> Resolve this by checking the terminal width (get_term_dimensions) and
> dynamically truncating the printed test description to fit within the
> available columns, leaving safety space for the prefix index and status
> suffix.
>
> JUnit XML output and the failure summary report still print the full,
> untruncated test descriptions.
>
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>

I processed the first 17 patches this one has this problem, please take
a look:L

GENSKEL /tmp/build/perf-tools-next/util/bpf_skel/lock_contention.skel.h
CC /tmp/build/perf-tools-next/util/bpf_lock_contention.o
tests/builtin-test.c: In function ‘print_test_result.isra’:
tests/builtin-test.c:420:40: error: ‘%-*s’ directive output may be truncated writing between 20 and 65505 bytes into a region of size 256 [-Werror=format-truncation=]
420 | snprintf(buf, buf_sz, "%-*s", width, desc);
| ^~~~
In file included from /usr/include/stdio.h:974,
from /home/acme/git/perf-tools-next/tools/include/linux/panic.h:6,
from /home/acme/git/perf-tools-next/tools/include/linux/kernel.h:11,
from /home/acme/git/perf-tools-next/tools/include/linux/list.h:7,
from /home/acme/git/perf-tools-next/tools/perf/util/config.h:6,
from tests/builtin-test.c:26:
In function ‘snprintf’,
inlined from ‘format_test_description’ at tests/builtin-test.c:420:3,
inlined from ‘print_test_result.isra’ at tests/builtin-test.c:459:3:
/usr/include/bits/stdio2.h:68:10: note: ‘__builtin___snprintf_chk’ output between 21 and 65506 bytes into a destination of size 256
68 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
tests/builtin-test.c: In function ‘print_test_result.isra’:
tests/builtin-test.c:420:40: error: ‘%-*s’ directive output may be truncated writing between 20 and 65507 bytes into a region of size 256 [-Werror=format-truncation=]
420 | snprintf(buf, buf_sz, "%-*s", width, desc);
| ^~~~
In function ‘snprintf’,
inlined from ‘format_test_description’ at tests/builtin-test.c:420:3,
inlined from ‘print_test_result.isra’ at tests/builtin-test.c:466:3:
/usr/include/bits/stdio2.h:68:10: note: ‘__builtin___snprintf_chk’ output between 21 and 65508 bytes into a destination of size 256
68 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | __glibc_objsize (__s), __fmt,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70 | __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[4]: *** [/home/acme/git/perf-tools-next/tools/build/Makefile.build:95: /tmp/build/perf-tools-next/tests/builtin-test.o] Error 1
make[3]: *** [/home/acme/git/perf-tools-next/tools/build/Makefile.build:158: tests] Error 2
make[2]: *** [Makefile.perf:566: /tmp/build/perf-tools-next/perf-test-in.o] Error 2
make[2]: *** Waiting for unfinished jobs....
LD /tmp/build/perf-tools-next/util/perf-util-in.o
LD /tmp/build/perf-tools-next/perf-util-in.o
make[1]: *** [Makefile.perf:288: sub-make] Error 2
make: *** [Makefile:122: install-bin] Error 2
make: Leaving directory '/home/acme/git/perf-tools-next/tools/perf'
⬢ [acme@toolbx perf-tools-next]$
acme@number:~/git/perf-tools-next$

acme@number:~/git/perf-tools-next$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/16/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,objc,obj-c++,ada,go,d,m2,cobol,algol68,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugzilla.redhat.com/ --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-libstdcxx-backtrace --with-libstdcxx-zoneinfo=/usr/share/zoneinfo --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl=/builddir/build/BUILD/gcc-16.1.1-build/gcc-16.1.1-20260515/obj-x86_64-redhat-linux/isl-install --enable-offload-targets=nvptx-none,amdgcn-amdhsa --enable-offload-defaulted --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-tls=gnu2 --with-arch_32=i686 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1 --disable-libssp
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.1.1 20260515 (Red Hat 16.1.1-2) (GCC)
acme@number:~/git/perf-tools-next$


> ---
> tools/perf/tests/builtin-test.c | 63 +++++++++++++++++++++++++++++----
> 1 file changed, 56 insertions(+), 7 deletions(-)
>
> diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
> index b64fc2204f22..edb4eaa695f1 100644
> --- a/tools/perf/tests/builtin-test.c
> +++ b/tools/perf/tests/builtin-test.c
> @@ -20,6 +20,8 @@
> #include <sys/wait.h>
> #include <sys/stat.h>
> #include <sys/time.h>
> +#include <sys/ioctl.h>
> +#include "util/term.h"
> #include "builtin.h"
> #include "config.h"
> #include "hist.h"
> @@ -404,19 +406,66 @@ static char *xml_escape(const char *str)
> return res ? res : strdup("");
> }
>
> +static const char *format_test_description(const char *desc, int width, int max_desc_width,
> + char *buf, size_t buf_sz)
> +{
> + int len = strlen(desc);
> +
> + if (width > max_desc_width)
> + width = max_desc_width;
> +
> + if (len > max_desc_width) {
> + snprintf(buf, buf_sz, "%.*s...", max_desc_width - 3, desc);
> + } else {
> + snprintf(buf, buf_sz, "%-*s", width, desc);
> + }
> + return buf;
> +}
> +
> static int print_test_result(struct test_suite *t, int curr_suite, int curr_test_case,
> int result, int width, int running,
> const char *err_output, double elapsed)
> {
> + char desc_buf[256];
> + const char *desc = test_description(t, curr_test_case);
> + struct winsize ws;
> + int max_desc_area_width;
> + int target_desc_area_width;
> + int desc_padding;
> +
> + get_term_dimensions(&ws);
> + /*
> + * Total terminal columns minus space for status e.g. " Running (12 active)"
> + * which is 20 chars, plus a margin of 3 chars = 23 chars.
> + */
> + max_desc_area_width = ws.ws_col - 23;
> + if (max_desc_area_width < 40)
> + max_desc_area_width = 40;
> +
> + /* Standard test has prefix "%3d: " which is 5 chars */
> + target_desc_area_width = width + 5;
> + if (target_desc_area_width > max_desc_area_width)
> + target_desc_area_width = max_desc_area_width;
> +
> if (test_suite__num_test_cases(t) > 1) {
> char prefix[32];
> int len = snprintf(prefix, sizeof(prefix), "%3d.%1d:",
> curr_suite + 1, curr_test_case + 1);
> - int subw = len >= 4 ? width + 4 - len : width;
>
> - pr_info("%s %-*s:", prefix, subw, test_description(t, curr_test_case));
> - } else
> - pr_info("%3d: %-*s:", curr_suite + 1, width, test_description(t, curr_test_case));
> + desc_padding = target_desc_area_width - (len + 1);
> + if (desc_padding < 20)
> + desc_padding = 20;
> +
> + format_test_description(desc, desc_padding, desc_padding, desc_buf, sizeof(desc_buf));
> + pr_info("%s %s:", prefix, desc_buf);
> + } else {
> + desc_padding = target_desc_area_width - 5;
> + if (desc_padding < 20)
> + desc_padding = 20;
> +
> + format_test_description(desc, desc_padding, desc_padding, desc_buf, sizeof(desc_buf));
> + pr_info("%3d: %s:", curr_suite + 1, desc_buf);
> + }
>
> switch (result) {
> case TEST_RUNNING:
> @@ -700,7 +749,7 @@ static void finish_test(struct child_test **child_tests, int running_test, int c
> * sub test names.
> */
> if (test_suite__num_test_cases(t) > 1 && curr_test_case == 0)
> - pr_info("%3d: %-*s:\n", curr_suite + 1, width, test_description(t, -1));
> + pr_info("%3d: %s:\n", curr_suite + 1, test_description(t, -1));
>
> /*
> * Busy loop reading from the child's stdout/stderr that are set to be
> @@ -976,7 +1025,7 @@ static int finish_tests_parallel(struct child_test **child_tests, size_t num_tes
> if (next_child) {
> if (test_suite__num_test_cases(next_child->test) > 1 &&
> last_suite_printed != next_child->suite_num) {
> - pr_info("%3d: %-*s:\n", next_child->suite_num + 1, width,
> + pr_info("%3d: %s:\n", next_child->suite_num + 1,
> test_description(next_child->test, -1));
> last_suite_printed = next_child->suite_num;
> }
> @@ -1040,7 +1089,7 @@ static int finish_tests_parallel(struct child_test **child_tests, size_t num_tes
>
> if (test_suite__num_test_cases(child->test) > 1 &&
> last_suite_printed != child->suite_num) {
> - pr_info("%3d: %-*s:\n", child->suite_num + 1, width,
> + pr_info("%3d: %s:\n", child->suite_num + 1,
> test_description(child->test, -1));
> last_suite_printed = child->suite_num;
> }
> --
> 2.54.0.1013.g208068f2d8-goog