mtest: align correctly tests with wide Unicode characters
This correctly formats tests with CJK names or, well, emoji.  It is not perfect
(for example it does not correctly format emoji that are variations of 1-wide
characters), but it is as good as most terminal emulators.
@@ -34,6 +34,7 @@ import sys
import textwrap
import time
import typing as T
+import unicodedata
import xml.etree.ElementTree as et
from . import build
@@ -61,6 +62,14 @@ def is_windows() -> bool:
def is_cygwin() -> bool:
return sys.platform == 'cygwin'
+UNIWIDTH_MAPPING = {'F': 2, 'H': 1, 'W': 2, 'Na': 1, 'N': 1, 'A': 1}
+def uniwidth(s: str) -> int:
+ result = 0
+ for c in s:
+ w = unicodedata.east_asian_width(c)
+ result += UNIWIDTH_MAPPING[w]
+ return result
+
def determine_worker_count() -> int:
varname = 'MESON_TESTTHREADS'
if varname in os.environ:
@@ -1272,12 +1281,13 @@ class TestHarness:
l.log(self, result)
def format(self, result: TestRun, colorize: bool) -> str:
- result_str = '{num:{numlen}}/{testcount} {name:{name_max_len}} {res} {dur:.2f}s'.format(
+ extra_name_width = self.name_max_len + 1 - uniwidth(result.name)
+ result_str = '{num:{numlen}}/{testcount} {name}{extra_name_padding}{res} {dur:.2f}s'.format(
numlen=len(str(self.test_count)),
num=result.num,
testcount=self.test_count,
- name_max_len=self.name_max_len,
name=result.name,
+ extra_name_padding=' ' * max(1, extra_name_width),
res=result.res.get_text(colorize),
dur=result.duration)
if result.res is TestResult.FAIL:
@@ -1315,7 +1325,7 @@ class TestHarness:
sys.exit(125)
self.test_count = len(tests)
- self.name_max_len = max([len(self.get_pretty_suite(test)) for test in tests])
+ self.name_max_len = max([uniwidth(self.get_pretty_suite(test)) for test in tests])
self.run_tests(tests)
return self.total_failure_count()
