From 02e6afe4c4956fd667dfefc04e8f129b5b84f709 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Wed, 15 Nov 2023 20:04:16 -0500 Subject: [PATCH] help_print_optstring: Test fullwidth/halfwidth character output. This function is supposed to support proper alignment with translated strings possibly containing fullwidth characters, but we have no test coverage of that aspect whatsoever. Let's try to have at least some basic tests, which is made a bit tricky since Autotest/m4sh busts the locale (but we can work around this problem). --- Makefile.am | 22 ++++++++++--- configure.ac | 6 +++- snippet/test-nls.at | 77 +++++++++++++++++++++++++++++++++++++++++++++ t/.gitignore | 1 + t/nls/gettext.h | 17 ++++++++++ t/nls/mbswidth.c | 50 +++++++++++++++++++++++++++++ t/nls/mbswidth.h | 18 +++++++++++ tests/functions.at | 17 ++++++++++ testsuite.at | 3 +- 9 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 snippet/test-nls.at create mode 100644 t/nls/gettext.h create mode 100644 t/nls/mbswidth.c create mode 100644 t/nls/mbswidth.h diff --git a/Makefile.am b/Makefile.am index e064ddf..e859c4d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -# Copyright © 2015 Nick Bowler +# Copyright © 2015, 2019, 2021-2023 Nick Bowler # # License WTFPL2: Do What The Fuck You Want To Public License, version 2. # This is free software: you are free to do what the fuck you want to. @@ -6,7 +6,7 @@ ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -I$(top_srcdir)/src +AM_CPPFLAGS = -I$(top_srcdir)/src check_LIBRARIES = t/libdummy.a t/libempty.a @@ -19,19 +19,33 @@ t_packtests64_SOURCES = t/packtests64.c src/pack.c src/tap.c if HAVE_STRUCT_OPTION check_PROGRAMS += t/helpdesc t/helpopt t/helpopt2 + +if HAVE_WCWIDTH +check_PROGRAMS += t/helpopt3 +endif endif EXTRA_LIBRARIES = libglohelp.a libglohelp_a_SOURCES = src/help.c -libglohelp_a_CFLAGS = -DHELP_GETOPT_LONG_ONLY +libglohelp_a_CPPFLAGS = -DHELP_GETOPT_LONG_ONLY libglohelp_a_SHORTNAME = glo +EXTRA_LIBRARIES += libnlshelp.a +libnlshelp_a_SOURCES = src/help.c t/nls/mbswidth.c +libnlshelp_a_CPPFLAGS = -DENABLE_NLS -I$(top_srcdir)/t/nls +libnlshelp_a_SHORTNAME = nls + t_helpdesc_SOURCES = t/helpdesc.c src/help.c src/tap.c t_helpopt_SOURCES = t/helpopt.c src/help.c src/tap.c + t_helpopt2_SOURCES = t/helpopt.c src/tap.c t_helpopt2_LDADD = $(libglohelp_a_OBJECTS) EXTRA_t_helpopt2_DEPENDENCIES = $(t_helpopt2_LDADD) +t_helpopt3_SOURCES = t/helpopt.c src/tap.c +t_helpopt3_LDADD = $(libnlshelp_a_OBJECTS) +EXTRA_t_helpopt3_DEPENDENCIES = $(t_helpopt3_LDADD) + check_PROGRAMS += t/copysym t_copysym_SOURCES = t/copysym.c src/tap.c t_copysym_LDADD = $(libnlscopysym_a_OBJECTS) @@ -39,7 +53,7 @@ EXTRA_t_copysym_DEPENDENCIES = $(t_copysym_LDADD) EXTRA_LIBRARIES += libnlscopysym.a libnlscopysym_a_SOURCES = src/copysym.c -libnlscopysym_a_CFLAGS = -DENABLE_NLS +libnlscopysym_a_CPPFLAGS = -DENABLE_NLS libnlscopysym_a_SHORTNAME = nls DISTCLEANFILES = diff --git a/configure.ac b/configure.ac index 47abd44..0aabe47 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -dnl Copyright © 2015, 2019-2022 Nick Bowler +dnl Copyright © 2015, 2019-2023 Nick Bowler dnl dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. dnl This is free software: you are free to do what the fuck you want to. @@ -20,6 +20,10 @@ AM_SILENT_RULES([yes]) DX_INIT([.]) +AC_USE_SYSTEM_EXTENSIONS +AC_CHECK_FUNCS_ONCE([wcwidth]) +AM_CONDITIONAL([HAVE_WCWIDTH], [test x"$ac_cv_func_wcwidth" = x"yes"]) + AC_CONFIG_TESTDIR([.]) DX_PROG_AUTOTEST AM_CONDITIONAL([HAVE_AUTOTEST], [test x"$dx_cv_autotest_works" = x"yes"]) diff --git a/snippet/test-nls.at b/snippet/test-nls.at new file mode 100644 index 0000000..103cb5b --- /dev/null +++ b/snippet/test-nls.at @@ -0,0 +1,77 @@ +# Copyright © 2023 Nick Bowler +# +# Helper macros for finding a locale using a specific character set. +# +# License WTFPL2: Do What The Fuck You Want To Public License, version 2. +# This is free software: you are free to do what the fuck you want to. +# There is NO WARRANTY, to the extent permitted by law. + +# TEST_UTF8_LOCALE([variable], [action-if-found], [action-if-not-found]) +# +# Try to find a supported UTF-8 locale. If any is found, the given shell +# variable is set to the name of one such locale, and action-if-found is +# expanded. +# +# If no suitable locale is found, then variable is set to the empty string +# and action-if-not-found is expanded. If no action is specified, the +# default action in this scenario is to skip the current test group. +# +# Since m4sh overrides LC_ALL, it is recommended to use the locale by +# setting LC_ALL to the detected value when running a program. +m4_defun([TEST_UTF8_LOCALE], +[AS_VAR_SET([$1], [`_TEST_UTF8_LOCALE`]) +AS_VAR_IF([$1], [""], [m4_default([$3], [AT_SKIP_IF([:])])], [$2])]) + +m4_defun([_TEST_UTF8_LOCALE], [m4_require([_TEST_NLS_SETUP])dnl +test_find_locale_charmap '[[Uu][Tt][Ff]-*8]']) + +# Output shell function definitions to implement locale detection. +# +# - test_check_locale_charmap name regex +# +# Returns success if setting LC_ALL to the given locale name results in a +# charmap string that matches the given regex (as interpreted by 'grep'). +# +# - test_find_locale_charmap regex +# +# Try to find any locale for which test_check_locale_charmap is successful +# with the given regex. If a matching locale is found, it is printed to +# standard output. +# +# By default, the user's own LC_CTYPE is preferred. Otherwise, the first +# match in the output of "locale -a" is chosen. + +m4_defun_once([_TEST_NLS_SETUP], [m4_divert_push([HEADER-COPYRIGHT]) +# save user locale setting before m4sh clobbers it +_orig_LC_ALL=$LC_ALL + +m4_divert([PREPARE_TESTS]) +test_check_locale_charmap () { + LC_ALL=$[1] locale -ck charmap 2>&AS_MESSAGE_LOG_FD | grep "$[2]" >/dev/null 2>&1 +} + +test_find_locale_charmap () { + save_LC_ALL=$LC_ALL + LC_ALL=$_orig_LC_ALL + re=$[1] + + init_ctype=`locale 2>&AS_MESSAGE_LOG_FD | $AWK -F= '$[1] == "LC_CTYPE" { + $[1] = ""; sub(/^ "/, ""); sub(/"$/, ""); print + }'` + + LC_ALL=$save_LC_ALL + if test_check_locale_charmap "$init_ctype" "$re"; then + AS_ECHO(["$init_ctype"]) + return; + fi + + set x `locale -a | grep "$re"`; shift + for arg; do + if test_check_locale_charmap "$arg" "$re"; then + AS_ECHO(["$arg"]) + return; + fi + done +} +m4_divert_pop([PREPARE_TESTS]) +]) diff --git a/t/.gitignore b/t/.gitignore index 7f15441..8b12d4a 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -2,5 +2,6 @@ /helpdesc /helpopt /helpopt2 +/helpopt3 /packtest[su] /packtest[su]64 diff --git a/t/nls/gettext.h b/t/nls/gettext.h new file mode 100644 index 0000000..b8a8060 --- /dev/null +++ b/t/nls/gettext.h @@ -0,0 +1,17 @@ +/* + * Copyright © 2023 Nick Bowler + * + * Stub gettext macros for test purposes. + * + * License WTFPL2: Do What The Fuck You Want To Public License, version 2. + * This is free software: you are free to do what the fuck you want to. + * There is NO WARRANTY, to the extent permitted by law. + */ + +#ifndef TEST_NLS_GETTEXT_H_ +#define TEST_NLS_GETTEXT_H_ + +#define gettext(s) (s) +#define pgettext_expr(a, s) (s) + +#endif diff --git a/t/nls/mbswidth.c b/t/nls/mbswidth.c new file mode 100644 index 0000000..e8e7c33 --- /dev/null +++ b/t/nls/mbswidth.c @@ -0,0 +1,50 @@ +/* + * Copyright © 2023 Nick Bowler + * + * Simplified mbsnwidth for test purposes. + * + * License WTFPL2: Do What The Fuck You Want To Public License, version 2. + * This is free software: you are free to do what the fuck you want to. + * There is NO WARRANTY, to the extent permitted by law. + */ + +#include +#include +#include + +#include "mbswidth.h" + +int mbsnwidth(const char *buf, size_t buflen, int flags) +{ + static int initialized; + mbstate_t ps; + int ret = 0; + + if (!initialized) { + setlocale(LC_CTYPE, ""); + initialized = 1; + } + + memset(&ps, 0, sizeof ps); + while (buflen > 0) { + wchar_t wc; + size_t l; + int w; + + l = mbrtowc(&wc, buf, buflen, &ps); + if (l == (size_t)-1 || l == (size_t)-2) + return -1; + else if (l == 0) + break; + + buflen -= l; + buf += l; + + w = wcwidth(wc); + if (w < 0) + return -1; + ret += w; + } + + return ret; +} diff --git a/t/nls/mbswidth.h b/t/nls/mbswidth.h new file mode 100644 index 0000000..1c4fc56 --- /dev/null +++ b/t/nls/mbswidth.h @@ -0,0 +1,18 @@ +/* + * Copyright © 2023 Nick Bowler + * + * Simplified mbsnwidth for test purposes. + * + * License WTFPL2: Do What The Fuck You Want To Public License, version 2. + * This is free software: you are free to do what the fuck you want to. + * There is NO WARRANTY, to the extent permitted by law. + */ + +#ifndef TEST_NLS_MBSWIDTH_H_ +#define TEST_NLS_MBSWIDTH_H_ + +#include + +int mbsnwidth(const char *buf, size_t n, int flags); + +#endif diff --git a/tests/functions.at b/tests/functions.at index 165376c..fd1e10f 100644 --- a/tests/functions.at +++ b/tests/functions.at @@ -110,6 +110,23 @@ AT_CHECK([m4_join([ ], AT_CLEANUP +AT_SETUP([help_print_optstring (NLS fullwidth/halfwidth)]) +AT_KEYWORDS([help nls]) + +AT_SKIP_IF([test ! -x "$builddir/t/helpopt3"]) +TEST_UTF8_LOCALE([locale_utf8]) + +AT_CHECK([m4_join([ ], + [LC_ALL=$locale_utf8 "$builddir/t/helpopt3"], + [--全角], + [--ハンカク], + )], [0], +[[ --全角 8 + --ハンカク 8 +]]) + +AT_CLEANUP + AT_BANNER([Miscellaneous functions]) TEST_TAP_SIMPLE([copyright_symbol], [copysym], [], []) diff --git a/testsuite.at b/testsuite.at index 2076d70..7e93427 100644 --- a/testsuite.at +++ b/testsuite.at @@ -1,5 +1,5 @@ AT_COPYRIGHT([dnl -Copyright © 2015,2019-2023 Nick Bowler +Copyright © 2015, 2019-2023 Nick Bowler License WTFPL2: Do What The Fuck You Want To Public License, version 2. This is free software: you are free to do what the fuck you want to. There is NO WARRANTY, to the extent permitted by law.]) @@ -8,6 +8,7 @@ AT_INIT AT_COLOR_TESTS m4_include([snippet/test-tap.at]) +m4_include([snippet/test-nls.at]) m4_divert_push([PREPARE_TESTS])dnl # Reduce influence from the toplevel "make" invocation on test cases. -- 2.43.2