This new module provides a program that executes a program, canonicalizing newlines in the standard output and/or standard error. It's useful in test suites that are meant to run also on Windows, because it makes it easy to deal with the necessary CRLF -> LF canonicalization there.
2025-08-04 Bruno Haible <[email protected]> nlcanon: Add tests. * tests/test-nlcanon.sh: New file. * modules/nlcanon-tests: New file. * tests/init.sh (setup_): Adjust also top_builddir, if set. nlcanon: New module. * build-aux/nlcanon.sh.in: New file, with a function func_tmpdir taken from build-aux/csharpexec.sh.in. * modules/nlcanon: New file.
>From ef6e2fcf72e6f90296f92de437754c33832cfdc3 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 4 Aug 2025 12:44:14 +0200 Subject: [PATCH 1/2] nlcanon: New module. * build-aux/nlcanon.sh.in: New file, with a function func_tmpdir taken from build-aux/csharpexec.sh.in. * modules/nlcanon: New file. --- ChangeLog | 7 +++ build-aux/nlcanon.sh.in | 130 ++++++++++++++++++++++++++++++++++++++++ modules/nlcanon | 22 +++++++ 3 files changed, 159 insertions(+) create mode 100644 build-aux/nlcanon.sh.in create mode 100644 modules/nlcanon diff --git a/ChangeLog b/ChangeLog index 370aefef3f..ece157234f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2025-08-04 Bruno Haible <[email protected]> + + nlcanon: New module. + * build-aux/nlcanon.sh.in: New file, with a function func_tmpdir taken + from build-aux/csharpexec.sh.in. + * modules/nlcanon: New file. + 2025-08-01 Collin Funk <[email protected]> doc: Mention the copy_file_range bug. diff --git a/build-aux/nlcanon.sh.in b/build-aux/nlcanon.sh.in new file mode 100644 index 0000000000..d5373f0b6c --- /dev/null +++ b/build-aux/nlcanon.sh.in @@ -0,0 +1,130 @@ +#!/bin/sh +# Execute a program, canonicalizing newlines in the standard output and/or +# standard error. + +# Copyright (C) 2025 Free Software Foundation, Inc. +# Written by Bruno Haible <[email protected]>, 2025. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# Usage: /bin/sh nlcanon.sh PLATFORMS STREAMS PROGRAM [ARGUMENTS] +# where +# PLATFORMS is one of +# all for all platforms +# windows-based for Cygwin and native Windows +# windows for native Windows +# STREAMS is one of +# stdout for standard output +# stderr for standard error +# stdout,stderr for both standard output and standard error +# PROGRAM [ARGUMENTS] is the command to execute. + +# func_tmpdir +# creates a temporary directory. +# Sets variable +# - tmp pathname of freshly created temporary directory +func_tmpdir () +{ + # Use the environment variable TMPDIR, falling back to /tmp. This allows + # users to specify a different temporary directory, for example, if their + # /tmp is filled up or too small. + : "${TMPDIR=/tmp}" + { + # Use the mktemp program if available. If not available, hide the error + # message. + tmp=`(umask 077 && mktemp -d -q "$TMPDIR/gtXXXXXX") 2>/dev/null` && + test -n "$tmp" && test -d "$tmp" + } || + { + # Use a simple mkdir command. It is guaranteed to fail if the directory + # already exists. $RANDOM is bash specific and expands to empty in shells + # other than bash, ksh and zsh. Its use does not increase security; + # rather, it minimizes the probability of failure in a very cluttered /tmp + # directory. + tmp=$TMPDIR/gt$$-$RANDOM + (umask 077 && mkdir "$tmp") + } || + { + echo "$0: cannot create a temporary directory in $TMPDIR" >&2 + { (exit 1); exit 1; } + } +} + +host_os='@host_os@' + +platforms="$1" +streams="$2" +shift +shift + +case "$platforms" in + all | windows-based | windows) ;; + *) echo "nlcanon.sh: Invalid PLATFORMS argument" 1>&2; exit 1 ;; +esac + +case "$streams" in + stdout | stderr | stdout,stderr | stderr,stdout ) ;; + *) echo "nlcanon.sh: Invalid STREAMS argument" 1>&2; exit 1 ;; +esac + +if case "$platforms" in + all) + true + ;; + windows-based) + case "$host_os" in + cygwin* | mingw* | windows*) true ;; + *) false ;; + esac + ;; + windows) + case "$host_os" in + mingw* | windows*) true ;; + *) false ;; + esac + ;; + esac +then + + # We need a temporary file, to save the exit code. + # Since there is no portable atomic 'mktemp' command, and since the only + # safe non-atomic way to create a temporary file is in a temporary directory, + # we need a temporary directory. + func_tmpdir + func_cleanup_tmpfiles() + { + rm -rf "$tmp" + } + trap func_cleanup_tmpfiles HUP INT QUIT PIPE TERM + trap 'exit_status=$?; func_cleanup_tmpfiles; exit $exit_status' EXIT + exitcode_file="$tmp/exit" + + # This is not a program. This is art. :D) + case "$streams" in + stdout) + { "$@"; echo "$?" > "$exitcode_file"; } | { sed -e 's/\r$//' 2>/dev/null; } + ;; + stderr) + { { "$@" 2>&1 1>&3; echo "$?" > "$exitcode_file"; } | { sed -e 's/\r$//' 2>/dev/null; }; } 3>&1 1>&2 + ;; + *) # both + { { "$@" 2>&1 1>&3; echo "$?" > "$exitcode_file"; } | { sed -e 's/\r$//' 2>/dev/null; }; } 3>&1 1>&2 | { sed -e 's/\r$//' 2>/dev/null; } + ;; + esac + exit `cat "$exitcode_file"` + +else + # No newline canonicalization is requested. + exec "$@" +fi diff --git a/modules/nlcanon b/modules/nlcanon new file mode 100644 index 0000000000..02190818d6 --- /dev/null +++ b/modules/nlcanon @@ -0,0 +1,22 @@ +Description: +Execute a program, canonicalizing newlines in the standard output and/or +standard error. + +Files: +build-aux/nlcanon.sh.in + +Depends-on: + +configure.ac: +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_CONFIG_FILES([nlcanon.sh:build-aux/nlcanon.sh.in]) + +Makefile.am: + +Include: + +License: +GPLed build tool + +Maintainer: +all -- 2.50.1
>From c7fd19908502c4c49e1d5beb73242ac96d256dbc Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 4 Aug 2025 13:23:54 +0200 Subject: [PATCH 2/2] nlcanon: Add tests. * tests/test-nlcanon.sh: New file. * modules/nlcanon-tests: New file. * tests/init.sh (setup_): Adjust also top_builddir, if set. --- ChangeLog | 5 ++++ modules/nlcanon-tests | 10 +++++++ tests/init.sh | 10 ++++++- tests/test-nlcanon.sh | 67 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 modules/nlcanon-tests create mode 100755 tests/test-nlcanon.sh diff --git a/ChangeLog b/ChangeLog index ece157234f..867b5886a9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2025-08-04 Bruno Haible <[email protected]> + nlcanon: Add tests. + * tests/test-nlcanon.sh: New file. + * modules/nlcanon-tests: New file. + * tests/init.sh (setup_): Adjust also top_builddir, if set. + nlcanon: New module. * build-aux/nlcanon.sh.in: New file, with a function func_tmpdir taken from build-aux/csharpexec.sh.in. diff --git a/modules/nlcanon-tests b/modules/nlcanon-tests new file mode 100644 index 0000000000..bfcecda38e --- /dev/null +++ b/modules/nlcanon-tests @@ -0,0 +1,10 @@ +Files: +tests/test-nlcanon.sh + +Depends-on: +test-framework-sh + +Makefile.am: +TESTS += test-nlcanon.sh +TESTS_ENVIRONMENT += \ + top_builddir='@top_builddir@' diff --git a/tests/init.sh b/tests/init.sh index d695020545..d3ce11dbee 100644 --- a/tests/init.sh +++ b/tests/init.sh @@ -428,13 +428,21 @@ setup_ () test_dir_=`mktempd_ "$initial_cwd_" "$pfx_-$ME_.XXXX"` \ || fail_ "failed to create temporary directory in $initial_cwd_" cd "$test_dir_" || fail_ "failed to cd to temporary directory" - # Set variables srcdir, builddir, for the convenience of the test. + # Set variables srcdir, builddir, and optionally top_builddir, + # for the convenience of the test. case $srcdir in /* | ?:*) ;; *) srcdir="../$srcdir" ;; esac builddir=".." export srcdir builddir + if test -n "$top_builddir"; then + case $top_builddir in + /* | ?:*) ;; + *) top_builddir="../$top_builddir" ;; + esac + export top_builddir + fi # As autoconf-generated configure scripts do, ensure that IFS # is defined initially, so that saving and restoring $IFS works. diff --git a/tests/test-nlcanon.sh b/tests/test-nlcanon.sh new file mode 100755 index 0000000000..ddcfc1cd9f --- /dev/null +++ b/tests/test-nlcanon.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +if test $# != 0; then + # Callee. + printf 'stdout-contents\r\n' + printf 'stderr-contents\r\n' 1>&2 + exit $1 +else + # Unit test. + . "${srcdir=.}/init.sh"; path_prepend_ . + + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42 + # Test the exit code. + test $? = 42 || Exit 11 + # Test standard output. + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42 | grep stdout + test $? = 0 || Exit 12 + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42 | grep stderr + test $? != 0 || Exit 13 + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42 | tr '\r' r | grep contentsr + test $? != 0 || Exit 14 + # Test standard error. + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | grep stdout + test $? != 0 || Exit 15 + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | grep stderr + test $? = 0 || Exit 16 + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | tr '\r' r | grep contentsr + test $? = 0 || Exit 17 + + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42 + # Test the exit code. + test $? = 42 || Exit 21 + # Test standard output. + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42 | grep stdout + test $? = 0 || Exit 22 + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42 | grep stderr + test $? != 0 || Exit 23 + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42 | tr '\r' r | grep contentsr + test $? = 0 || Exit 24 + # Test standard error. + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | grep stdout + test $? != 0 || Exit 25 + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | grep stderr + test $? = 0 || Exit 26 + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stderr "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | tr '\r' r | grep contentsr + test $? != 0 || Exit 27 + + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42 + # Test the exit code. + test $? = 42 || Exit 31 + # Test standard output. + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42 | grep stdout + test $? = 0 || Exit 32 + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42 | grep stderr + test $? != 0 || Exit 33 + $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42 | tr '\r' r | grep contentsr + test $? != 0 || Exit 34 + # Test standard error. + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | grep stdout + test $? != 0 || Exit 35 + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | grep stderr + test $? = 0 || Exit 36 + { $BOURNE_SHELL ${top_builddir}/nlcanon.sh all stdout,stderr "${srcdir}/test-nlcanon.sh" 42; } 2>&1 >/dev/null | tr '\r' r | grep contentsr + test $? != 0 || Exit 37 + + Exit 0 +fi -- 2.50.1
