mirror of
git://sourceware.org/git/glibc.git
synced 2026-09-08 23:58:31 +08:00
stdio-common: Verify printf format tests with Python rather than AWK
The formatted printf output tests verify their records against GNU AWK, relying on it to provide an implementation of format processing that is independent from ours. AWK has to run in the bignum mode for the floating-point conversions, because otherwise it uses the system sprintf(3) internally and we end up verifying our code against itself. That in turn makes gawk compiled with MPFR support a requirement for testing the library at all. Beyond that gawk mishandles a number of cases which the AWK script then has to undo by hand: the extraneous leading 0 produced for the alternative form with the octal conversion, the 0 produced where no characters are expected for the hexadecimal conversions, the missing + and space characters for a zero value with the precision of zero, and a collection of sign, flag and field width anomalies for Inf and NaN values. Each such workaround suppresses whatever we might get wrong in the same place. The a, A, b, and B conversions cannot be verified at all, because gawk either does not handle them or produces different output. Replace the AWK script with an equivalent one written in Python, which is already a requirement for building the library. Rather than calling into any formatting routine it computes the reference output directly, using exact integer and rational arithmetic. Working exactly means the result does not depend on the range or precision of any host floating-point type, so the wider types are handled without arbitrary-precision arithmetic having to be built into the interpreter, and none of the workarounds listed above are needed: the corner cases they cover are computed correctly. The same property removes the reason the a, A, b, and B conversions had to be left out; adding them is left for the commits that follow. Rendered digits are memoized per value, without which the exact arithmetic makes the long double conversions slower than AWK. As the capability probes only ever detected gawk build options, they go away along with the unsupported status they could produce, so the f and F conversions are now always verified rather than silently skipped where gawk was built without them. Drop the corresponding note on MPFR from the installation instructions. Tested on x86_64-linux-gnu, where all 576 results continue to pass. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
This commit is contained in:
committed by
Adhemerval Zanella
parent
e1643c8df3
commit
dc6e310e79
@@ -522,10 +522,6 @@ build the GNU C Library:
|
||||
version 5.4.1 is the newest verified to work to build the GNU C
|
||||
Library.
|
||||
|
||||
Testing the GNU C Library requires 'gawk' to be compiled with
|
||||
support for high precision arithmetic via the 'MPFR'
|
||||
multiple-precision floating-point computation library.
|
||||
|
||||
* GNU 'bison' 2.7 or later
|
||||
|
||||
'bison' is used to generate the 'yacc' parser code in the 'intl'
|
||||
|
||||
@@ -567,10 +567,6 @@ function, which was introduced in version 3.1.2 of @code{gawk}.
|
||||
As of release time, @code{gawk} version 5.4.1 is the newest verified
|
||||
to work to build @theglibc{}.
|
||||
|
||||
Testing the GNU C Library requires @code{gawk} to be compiled with
|
||||
support for high precision arithmetic via the @code{MPFR}
|
||||
multiple-precision floating-point computation library.
|
||||
|
||||
@item
|
||||
GNU @code{bison} 2.7 or later
|
||||
|
||||
|
||||
@@ -633,7 +633,7 @@ $(objpfx)tst-printf.out: tst-printf.sh $(objpfx)tst-printf
|
||||
|
||||
# We can't split a quoted recipe line, so do it via an auxiliary variable.
|
||||
make-tst-printf-format-out = \
|
||||
AWK='$(AWK)' BASH='$(BASH)' \
|
||||
PYTHON='$(PYTHON)' BASH='$(BASH)' \
|
||||
$(BASH) $< $@ $(common-objpfx) \
|
||||
'$(run-program-prefix-before-env) \
|
||||
$(run-program-env) \
|
||||
@@ -642,7 +642,7 @@ make-tst-printf-format-out = \
|
||||
$(run-program-prefix-after-env)'
|
||||
$(objpfx)tst-printf-format-%.out: \
|
||||
tst-printf-format.sh $(foreach c,$(convs),tst-printf-format-$(c).sh) \
|
||||
$(foreach f,$(xprintf-srcs),$(objpfx)$(f)) tst-printf-format.awk
|
||||
$(foreach f,$(xprintf-srcs),$(objpfx)$(f)) tst-printf-format.py
|
||||
$(make-tst-printf-format-out) > $@; \
|
||||
$(evaluate-test)
|
||||
|
||||
|
||||
@@ -23,12 +23,10 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
echo Verifying c
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-c c |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion c output error, first line:\n/") 2>&1 ||
|
||||
exit 1
|
||||
|
||||
@@ -23,8 +23,6 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=0
|
||||
|
||||
for f in d i; do
|
||||
@@ -32,7 +30,7 @@ for f in d i; do
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-char $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -24,51 +24,15 @@ format=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
# For floating-point formats we need to use the bignum mode even if the
|
||||
# regular mode would do, because GAWK in the latter mode uses sprintf(3)
|
||||
# internally to process the conversion requested, so any bug in our code
|
||||
# would then be verified against itself, defeating the objective of doing
|
||||
# the verification against an independent implementation.
|
||||
AWK="env LC_ALL=C ${AWK:-awk} -M"
|
||||
status=0
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="-1.79769313486231570814527423731704357e+308"
|
||||
val=$(echo "$ref" | $AWK '{ printf "%.35e\n", $1 }' 2>&1) &&
|
||||
test "$val" = "$ref" && status=0
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
|
||||
# Check for any additional conversions that AWK handles conditionally
|
||||
# according to its version and/or the environment it has been built in.
|
||||
# The 'A' and 'a' conversions are not suitable to use at this point, as
|
||||
# output produced by AWK is different apparently due to a subtlety in
|
||||
# rounding, so do not try them.
|
||||
declare -A conversion_disabled
|
||||
ref="-inf"
|
||||
for f in f F; do
|
||||
conversion_disabled[$f]=true
|
||||
val=$(echo "$ref" | $AWK '{ printf "%'$f'\n", $1 }' 2>&1) &&
|
||||
test "${val^^}" = "${ref^^}" && unset conversion_disabled[$f]
|
||||
done
|
||||
|
||||
if test "${conversion_disabled[$format]+set}" = set; then
|
||||
echo Unsupported $format
|
||||
status=77
|
||||
else
|
||||
echo Verifying $format
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-double $format |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
head -n 1 |
|
||||
sed "s/^/Conversion $format output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
fi
|
||||
echo Verifying $format
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-double $format |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 |
|
||||
sed "s/^/Conversion $format output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
|
||||
exit $status
|
||||
|
||||
@@ -23,29 +23,14 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="-2147483648"
|
||||
for AWK in "$AWK -M" "$AWK"; do
|
||||
val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
|
||||
test "$val" = "$ref" && status=0 && break
|
||||
done
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
status=0
|
||||
|
||||
for f in d i; do
|
||||
echo Verifying $f
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-int $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -24,51 +24,15 @@ format=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
# For floating-point formats we need to use the bignum mode even if the
|
||||
# regular mode would do, because GAWK in the latter mode uses sprintf(3)
|
||||
# internally to process the conversion requested, so any bug in our code
|
||||
# would then be verified against itself, defeating the objective of doing
|
||||
# the verification against an independent implementation.
|
||||
AWK="env LC_ALL=C ${AWK:-awk} -M"
|
||||
status=0
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="-1.18973149535723176508575932662800702e+4932"
|
||||
val=$(echo "$ref" | $AWK '{ PREC=113; printf "%.35e\n", $1 }' 2>&1) &&
|
||||
test "$val" = "$ref" && status=0
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
|
||||
# Check for any additional conversions that AWK handles conditionally
|
||||
# according to its version and/or the environment it has been built in.
|
||||
# The 'A' and 'a' conversions are not suitable to use at this point, as
|
||||
# output produced by AWK is different apparently due to a subtlety in
|
||||
# rounding, so do not try them.
|
||||
declare -A conversion_disabled
|
||||
ref="-inf"
|
||||
for f in f F; do
|
||||
conversion_disabled[$f]=true
|
||||
val=$(echo "$ref" | $AWK '{ printf "%'$f'\n", $1 }' 2>&1) &&
|
||||
test "${val^^}" = "${ref^^}" && unset conversion_disabled[$f]
|
||||
done
|
||||
|
||||
if test "${conversion_disabled[$format]+set}" = set; then
|
||||
echo Unsupported $format
|
||||
status=77
|
||||
else
|
||||
echo Verifying $format
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ldouble $format |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
head -n 1 |
|
||||
sed "s/^/Conversion $format output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
fi
|
||||
echo Verifying $format
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ldouble $format |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 |
|
||||
sed "s/^/Conversion $format output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
|
||||
exit $status
|
||||
|
||||
@@ -23,29 +23,14 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="9223372036854775807"
|
||||
for AWK in "$AWK -M" "$AWK"; do
|
||||
val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
|
||||
test "$val" = "$ref" && status=0 && break
|
||||
done
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
status=0
|
||||
|
||||
for f in d i; do
|
||||
echo Verifying $f
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-llong $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,29 +23,14 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="9223372036854775807"
|
||||
for AWK in "$AWK -M" "$AWK"; do
|
||||
val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
|
||||
test "$val" = "$ref" && status=0 && break
|
||||
done
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
status=0
|
||||
|
||||
for f in d i; do
|
||||
echo Verifying $f
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-long $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,12 +23,10 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
echo Verifying s
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-s s |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion s output error, first line:\n/") 2>&1 ||
|
||||
exit 1
|
||||
|
||||
@@ -23,8 +23,6 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=0
|
||||
|
||||
for f in d i; do
|
||||
@@ -32,7 +30,7 @@ for f in d i; do
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-short $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,8 +23,6 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=0
|
||||
|
||||
for f in o u x X; do
|
||||
@@ -32,7 +30,7 @@ for f in o u x X; do
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-uchar $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,29 +23,14 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="4294967295"
|
||||
for AWK in "$AWK -M" "$AWK"; do
|
||||
val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
|
||||
test "$val" = "$ref" && status=0 && break
|
||||
done
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
status=0
|
||||
|
||||
for f in o u x X; do
|
||||
echo Verifying $f
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-uint $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,29 +23,14 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="18446744073709551615"
|
||||
for AWK in "$AWK -M" "$AWK"; do
|
||||
val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
|
||||
test "$val" = "$ref" && status=0 && break
|
||||
done
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
status=0
|
||||
|
||||
for f in o u x X; do
|
||||
echo Verifying $f
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ullong $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,29 +23,14 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=77
|
||||
|
||||
# Verify that AWK can handle the range required. It also catches:
|
||||
# "gawk: warning: -M ignored: MPFR/GMP support not compiled in"
|
||||
# message produced where bignum support is not there, which is the
|
||||
# only indication as the use of '-M' does not affect the exit status
|
||||
# in this case.
|
||||
ref="18446744073709551615"
|
||||
for AWK in "$AWK -M" "$AWK"; do
|
||||
val=$(echo "$ref" | $AWK '{ printf "%d\n", $1 }' 2>&1) || continue
|
||||
test "$val" = "$ref" && status=0 && break
|
||||
done
|
||||
|
||||
test $status -eq 0 || { echo "No working AWK found" && exit $status; }
|
||||
status=0
|
||||
|
||||
for f in o u x X; do
|
||||
echo Verifying $f
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ulong $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -23,8 +23,6 @@ xprintf=$1; shift
|
||||
common_objpfx=$1; shift
|
||||
test_program_prefix=$1; shift
|
||||
|
||||
AWK="env LC_ALL=C ${AWK:-awk}"
|
||||
|
||||
status=0
|
||||
|
||||
for f in o u x X; do
|
||||
@@ -32,7 +30,7 @@ for f in o u x X; do
|
||||
(set -o pipefail
|
||||
${test_program_prefix} \
|
||||
${common_objpfx}stdio-common/tst-printf-format-${xprintf}-ushort $f |
|
||||
$AWK -f tst-printf-format.awk 2>&1 |
|
||||
${PYTHON:-python3} tst-printf-format.py 2>&1 |
|
||||
head -n 1 | sed "s/^/Conversion $f output error, first line:\n/") 2>&1 ||
|
||||
status=1
|
||||
done
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
# Testing of printf conversions.
|
||||
# Copyright (C) 2024-2026 Free Software Foundation, Inc.
|
||||
# This file is part of the GNU C Library.
|
||||
|
||||
# The GNU C Library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
# The GNU C Library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with the GNU C Library; if not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
|
||||
BEGIN {
|
||||
FS = ":"
|
||||
}
|
||||
|
||||
/^prec:/ {
|
||||
PREC = $2
|
||||
next
|
||||
}
|
||||
|
||||
/^val:/ {
|
||||
val = $2
|
||||
# Prepend "+" for +Inf or +NaN value lacking a sign, because gawk
|
||||
# interpretes them as strings rather than numeric values in the
|
||||
# non-bignum mode unless a sign has been explicitly given. Keep
|
||||
# original 'val' for reporting.
|
||||
value = gensub(/^(INF|NAN|inf|nan)/, "+\\1", 1, val)
|
||||
# Neither changes between the conversions applied to this value.
|
||||
value_infnan = value ~ /(INF|NAN|inf|nan)/
|
||||
value_zero = value == 0
|
||||
next
|
||||
}
|
||||
|
||||
/^%/ {
|
||||
# Discard the trailing empty field, used to improve legibility of data.
|
||||
input = $--NF
|
||||
format = $1
|
||||
width = $2
|
||||
precision = "." $(NF - 1)
|
||||
# Discard any negative precision, which is to be taken as if omitted.
|
||||
sub(/\.-.*/, "", precision)
|
||||
# Simplify handling and paste the precision and width specified as
|
||||
# arguments to '*' directly into the format.
|
||||
sub(/\.\*/, precision, format)
|
||||
sub(/\*/, width, format)
|
||||
# Discard length modifiers. They are only relevant to C data types.
|
||||
sub(/([DHLjhltz]|wf?[1-9][0-9]*)/, "", format)
|
||||
# Discard the '#' flag with the octal conversion if output starts with
|
||||
# 0 in the absence of this flag. In that case no extra 0 is supposed
|
||||
# to be produced, but gawk prepends it anyway.
|
||||
if (index(format, "#") && format ~ /#.*o/)
|
||||
{
|
||||
tmpfmt = gensub(/#/, "", "g", format)
|
||||
tmpout = sprintf(tmpfmt, value)
|
||||
if (tmpout ~ /^ *0/)
|
||||
format = tmpfmt
|
||||
}
|
||||
# Likewise with the hexadecimal conversion where zero value with the
|
||||
# precision of zero is supposed to produce no characters, but gawk
|
||||
# outputs 0 instead.
|
||||
else if (index(format, "#") && format ~ /#.*[Xx]/)
|
||||
{
|
||||
tmpfmt = gensub(/#/, "", "g", format)
|
||||
tmpout = sprintf(tmpfmt, value)
|
||||
if (tmpout ~ /^ *$/)
|
||||
format = tmpfmt
|
||||
}
|
||||
# AWK interpretes input opportunistically as a number, which interferes
|
||||
# with how the 'c' conversion works: "a" input will result in "a" output
|
||||
# however "0" input will result in "^@" output rather than "0". Force
|
||||
# the value to be interpreted as a string then, by appending "".
|
||||
output = sprintf(format, value "")
|
||||
# Make up for various anomalies with the handling of +/-Inf and +/-NaN
|
||||
# values and reprint the output produced using the string conversion,
|
||||
# with the field width carried over and the relevant flags handled by
|
||||
# hand.
|
||||
if (value_infnan && format ~ /[EFGefg]/)
|
||||
{
|
||||
minus = format ~ /-/ ? "-" : ""
|
||||
sign = value ~ /-/ ? "-" : format ~ /\+/ ? "+" : format ~ / / ? " " : ""
|
||||
if (format ~ /^%[^\.1-9]*[1-9][0-9]*/)
|
||||
width = gensub(/^%[^\.1-9]*([1-9][0-9]*).*$/, "\\1", 1, format)
|
||||
else
|
||||
width = ""
|
||||
output = gensub(/[-+ ]/, "", "g", output)
|
||||
output = sprintf("%" minus width "s", sign output)
|
||||
}
|
||||
# Produce "+" where the '+' flag has been used with a signed integer
|
||||
# conversion for zero value, observing any field width in effect.
|
||||
# In that case "+" is always supposed to be produced, but with the
|
||||
# precision of zero gawk in the non-bignum mode produces any padding
|
||||
# requested only.
|
||||
else if (value_zero && format ~ /\+.*[di]/)
|
||||
{
|
||||
output = gensub(/^( *) $/, format ~ /-/ ? "+\\1" : "\\1+", 1, output)
|
||||
output = gensub(/^$/, "+", 1, output)
|
||||
}
|
||||
# Produce " " where the space flag has been used with a signed integer
|
||||
# conversion for zero value. In that case at least one " " is
|
||||
# supposed to be produced, but with the precision of zero gawk in the
|
||||
# non-bignum mode produces nothing.
|
||||
else if (value_zero && format ~ / .*[di]/)
|
||||
{
|
||||
output = gensub(/^$/, " ", 1, output)
|
||||
}
|
||||
if (output != input)
|
||||
{
|
||||
printf "(\"%s\"%s%s, %s) => \"%s\", expected \"%s\"\n", \
|
||||
$1, (NF > 2 ? ", " $2 : ""), (NF > 3 ? ", " $3 : ""), val, \
|
||||
input, output > "/dev/stderr"
|
||||
status = 1
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
{
|
||||
printf "unrecognized input: \"%s\"\n", $0 > "/dev/stderr"
|
||||
status = 1
|
||||
}
|
||||
|
||||
END {
|
||||
exit status
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
#!/usr/bin/python3
|
||||
# Verification of formatted printf output.
|
||||
# Copyright (C) 2024-2026 Free Software Foundation, Inc.
|
||||
# This file is part of the GNU C Library.
|
||||
#
|
||||
# The GNU C Library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# The GNU C Library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with the GNU C Library; if not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Verify records of formatted printf output.
|
||||
|
||||
The record stream produced by tst-printf-format-skeleton.c is read from
|
||||
standard input and each record's output is checked against a reference
|
||||
value computed here. Diagnostics for records that do not match are written
|
||||
to standard error and a nonzero exit status is returned.
|
||||
|
||||
This is deliberately an independent implementation of the ISO C format
|
||||
processing rules: every conversion is computed with exact integer and
|
||||
rational arithmetic and nothing defers to the C library, so a bug in printf
|
||||
cannot verify itself. Working exactly also means no dependence on the
|
||||
range or precision of any host floating-point type, so the wider types are
|
||||
handled without arbitrary-precision support having to be built into the
|
||||
interpreter.
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from fractions import Fraction
|
||||
|
||||
# Conversions grouped by the C type of the corresponding argument.
|
||||
FLOAT_CONVS = frozenset("eEfFgG")
|
||||
INT_CONVS = frozenset("diouxX")
|
||||
|
||||
# The conversion specifier selects the base an integer is written in.
|
||||
INT_FORMATS = {"o": "%o", "u": "%d", "x": "%x", "X": "%X"}
|
||||
|
||||
# Flags and length modifiers accepted in a conversion specification.
|
||||
FLAG_CHARS = "-+ #0"
|
||||
LENGTH_CHARS = "hlLqjzt"
|
||||
|
||||
# log10(2), used only to seed the decimal exponent search.
|
||||
LOG10_2 = 0.3010299956639812
|
||||
|
||||
|
||||
def diagnose(text):
|
||||
"""Report TEXT on standard error.
|
||||
|
||||
Records hold arbitrary bytes, taken as Latin-1 on the way in, so put
|
||||
them back the same way rather than through whatever encoding the
|
||||
locale would otherwise select."""
|
||||
sys.stderr.buffer.write(text.encode("latin-1") + b"\n")
|
||||
sys.stderr.buffer.flush()
|
||||
|
||||
|
||||
def round_ratio(num, den):
|
||||
"""Round the non-negative ratio NUM/DEN to the nearest integer, with
|
||||
ties going to even, matching the default rounding mode."""
|
||||
quot, rem = divmod(num, den)
|
||||
rem *= 2
|
||||
if rem > den or (rem == den and quot & 1):
|
||||
quot += 1
|
||||
return quot
|
||||
|
||||
|
||||
def scale_ratio(num, den, power):
|
||||
"""Return NUM/DEN multiplied by 10**POWER, as a pair of integers."""
|
||||
if power >= 0:
|
||||
return num * 10 ** power, den
|
||||
return num, den * 10 ** -power
|
||||
|
||||
|
||||
def decimal_to_binary(text, mant_bits):
|
||||
"""Convert the decimal string TEXT to the nearest value having MANT_BITS
|
||||
of significand, returned as an exact Fraction.
|
||||
|
||||
The generator prints reference values with enough digits to reproduce
|
||||
the original, so this recovers the exact value converted."""
|
||||
fr = Fraction(text)
|
||||
if fr == 0:
|
||||
return fr
|
||||
neg = fr < 0
|
||||
num, den = (-fr.numerator if neg else fr.numerator), fr.denominator
|
||||
# Scale so that 2**(mant_bits-1) <= num/den < 2**mant_bits.
|
||||
exp = num.bit_length() - den.bit_length() - mant_bits
|
||||
if exp >= 0:
|
||||
den <<= exp
|
||||
else:
|
||||
num <<= -exp
|
||||
while num >= den << mant_bits:
|
||||
den <<= 1
|
||||
exp += 1
|
||||
while num < den << (mant_bits - 1):
|
||||
num <<= 1
|
||||
exp -= 1
|
||||
mant = round_ratio(num, den)
|
||||
if mant >> mant_bits:
|
||||
mant >>= 1
|
||||
exp += 1
|
||||
fr = Fraction(mant << exp) if exp >= 0 else Fraction(mant, 1 << -exp)
|
||||
return -fr if neg else fr
|
||||
|
||||
|
||||
def decimal_digits(fr, ndigits):
|
||||
"""Round the positive Fraction FR to NDIGITS significant decimal digits.
|
||||
|
||||
Return the digit string and the decimal exponent X such that the value
|
||||
is D[0].D[1:] * 10**X, i.e. X is the exponent an 'e' conversion prints."""
|
||||
num, den = fr.numerator, fr.denominator
|
||||
# Seed from the binary magnitude; the estimate is within one decade.
|
||||
x = int((num.bit_length() - den.bit_length()) * LOG10_2)
|
||||
while True:
|
||||
hi_num, hi_den = scale_ratio(num, den, -(x + 1))
|
||||
if hi_num < hi_den:
|
||||
break
|
||||
x += 1
|
||||
while True:
|
||||
lo_num, lo_den = scale_ratio(num, den, -x)
|
||||
if lo_num >= lo_den:
|
||||
break
|
||||
x -= 1
|
||||
digits = round_ratio(*scale_ratio(num, den, ndigits - 1 - x))
|
||||
if digits >= 10 ** ndigits:
|
||||
# Rounding carried into the next decade, e.g. 9.99 -> 10.0.
|
||||
digits //= 10
|
||||
x += 1
|
||||
return str(digits).zfill(ndigits), x
|
||||
|
||||
|
||||
class Spec:
|
||||
"""A parsed conversion specification."""
|
||||
|
||||
__slots__ = ("minus", "plus", "space", "alt", "zero", "width", "prec",
|
||||
"conv", "neg_zero")
|
||||
|
||||
def __init__(self, fmt, args):
|
||||
"""Parse FMT, taking values for any '*' from ARGS in order."""
|
||||
self.minus = self.plus = self.space = self.alt = self.zero = False
|
||||
self.neg_zero = False
|
||||
i = 1 # skip '%'
|
||||
while i < len(fmt) and fmt[i] in FLAG_CHARS:
|
||||
char = fmt[i]
|
||||
self.minus |= char == "-"
|
||||
self.plus |= char == "+"
|
||||
self.space |= char == " "
|
||||
self.alt |= char == "#"
|
||||
self.zero |= char == "0"
|
||||
i += 1
|
||||
|
||||
self.width = 0
|
||||
if i < len(fmt) and fmt[i] == "*":
|
||||
self.width = int(args.pop(0))
|
||||
i += 1
|
||||
# A negative field width is a '-' flag with a positive width.
|
||||
if self.width < 0:
|
||||
self.minus = True
|
||||
self.width = -self.width
|
||||
else:
|
||||
start = i
|
||||
while i < len(fmt) and fmt[i].isdigit():
|
||||
i += 1
|
||||
if i > start:
|
||||
self.width = int(fmt[start:i])
|
||||
|
||||
self.prec = None
|
||||
if i < len(fmt) and fmt[i] == ".":
|
||||
i += 1
|
||||
if i < len(fmt) and fmt[i] == "*":
|
||||
self.prec = int(args.pop(0))
|
||||
i += 1
|
||||
# A negative precision is taken as if it were omitted.
|
||||
if self.prec < 0:
|
||||
self.prec = None
|
||||
else:
|
||||
start = i
|
||||
while i < len(fmt) and fmt[i].isdigit():
|
||||
i += 1
|
||||
self.prec = int(fmt[start:i]) if i > start else 0
|
||||
|
||||
# Length modifiers only select the C type of the argument, which the
|
||||
# generator has already applied; they do not affect the output.
|
||||
while i < len(fmt) and fmt[i] in LENGTH_CHARS:
|
||||
i += 1
|
||||
self.conv = fmt[i]
|
||||
|
||||
def pad(self, body, zero_ok=True, sign=""):
|
||||
"""Apply the field width to SIGN followed by BODY."""
|
||||
count = self.width - len(sign) - len(body)
|
||||
if count <= 0:
|
||||
return sign + body
|
||||
if self.minus:
|
||||
return sign + body + " " * count
|
||||
if self.zero and zero_ok:
|
||||
return sign + "0" * count + body
|
||||
return " " * count + sign + body
|
||||
|
||||
def sign_of(self, neg):
|
||||
"""The character introducing a signed conversion of a value whose
|
||||
sign is NEG, honoring the '+' and space flags."""
|
||||
if neg:
|
||||
return "-"
|
||||
if self.plus:
|
||||
return "+"
|
||||
if self.space:
|
||||
return " "
|
||||
return ""
|
||||
|
||||
|
||||
def convert_int(value, spec):
|
||||
conv = spec.conv
|
||||
if conv in "di":
|
||||
neg = value < 0
|
||||
digits = str(-value if neg else value)
|
||||
sign = spec.sign_of(neg)
|
||||
else:
|
||||
digits = INT_FORMATS[conv] % value
|
||||
sign = ""
|
||||
if spec.prec is not None:
|
||||
# A zero value converted with a precision of zero produces no
|
||||
# characters at all.
|
||||
if value == 0 and spec.prec == 0:
|
||||
digits = ""
|
||||
digits = digits.rjust(spec.prec, "0")
|
||||
if spec.alt:
|
||||
if conv == "o" and not digits.startswith("0"):
|
||||
digits = "0" + digits
|
||||
elif conv in "xX" and value != 0:
|
||||
# The base prefix precedes any '0' flag padding, so it pads
|
||||
# along with the sign rather than with the digits.
|
||||
sign = "0x" if conv == "x" else "0X"
|
||||
# An explicit precision defeats the '0' flag.
|
||||
return spec.pad(digits, zero_ok=spec.prec is None, sign=sign)
|
||||
|
||||
|
||||
def convert_string(value, spec):
|
||||
if spec.prec is not None:
|
||||
value = value[:spec.prec]
|
||||
return spec.pad(value, zero_ok=False)
|
||||
|
||||
|
||||
def convert_char(value, spec):
|
||||
return spec.pad(value[:1], zero_ok=False)
|
||||
|
||||
|
||||
def convert_special(kind, neg, spec):
|
||||
"""Convert an infinity or a NaN. The field is padded with spaces
|
||||
whether or not the '0' flag was given, and the alternative form has no
|
||||
effect."""
|
||||
body = kind.upper() if spec.conv in "EFG" else kind
|
||||
return spec.pad(body, zero_ok=False, sign=spec.sign_of(neg))
|
||||
|
||||
|
||||
def render_f(fr, prec, alt, strip):
|
||||
"""Render the non-negative Fraction FR in the style of 'f'."""
|
||||
scaled = round_ratio(*scale_ratio(fr.numerator, fr.denominator, prec))
|
||||
text = str(scaled).zfill(prec + 1)
|
||||
if prec:
|
||||
whole, frac = text[:-prec], text[-prec:]
|
||||
else:
|
||||
whole, frac = text, ""
|
||||
if strip:
|
||||
frac = frac.rstrip("0")
|
||||
if frac or alt:
|
||||
return whole + "." + frac
|
||||
return whole
|
||||
|
||||
|
||||
def render_e(fr, prec, alt, upper, strip):
|
||||
"""Render the non-negative Fraction FR in the style of 'e'."""
|
||||
if fr == 0:
|
||||
digits, exp = "0" * (prec + 1), 0
|
||||
else:
|
||||
digits, exp = decimal_digits(fr, prec + 1)
|
||||
whole, frac = digits[0], digits[1:]
|
||||
if strip:
|
||||
frac = frac.rstrip("0")
|
||||
mant = whole + "." + frac if frac or alt else whole
|
||||
return "%s%s%s%02d" % (mant, "E" if upper else "e",
|
||||
"-" if exp < 0 else "+", abs(exp))
|
||||
|
||||
|
||||
def convert_float(value, spec, cache):
|
||||
"""Convert VALUE, an exact Fraction. CACHE memoizes rendered digits for
|
||||
the value currently being converted."""
|
||||
conv = spec.conv
|
||||
neg = value < 0 or (value == 0 and spec.neg_zero)
|
||||
fr = -value if value < 0 else value
|
||||
prec = 6 if spec.prec is None else spec.prec
|
||||
upper = conv in "EFG"
|
||||
|
||||
# The generator iterates a large number of flag and field width
|
||||
# combinations over each value, so the same digits are called for over
|
||||
# and over. They depend only on the magnitude, the precision and the
|
||||
# alternative form, which is what keeps the wider types inexpensive
|
||||
# despite the arithmetic being exact.
|
||||
key = (conv, prec, spec.alt)
|
||||
body = cache.get(key)
|
||||
if body is None:
|
||||
if conv in "gG":
|
||||
sig = 1 if prec == 0 else prec
|
||||
_, exp = decimal_digits(fr, sig) if fr != 0 else ("", 0)
|
||||
if -4 <= exp < sig:
|
||||
body = render_f(fr, sig - 1 - exp, spec.alt,
|
||||
strip=not spec.alt)
|
||||
else:
|
||||
body = render_e(fr, sig - 1, spec.alt, upper,
|
||||
strip=not spec.alt)
|
||||
elif conv in "eE":
|
||||
body = render_e(fr, prec, spec.alt, upper, strip=False)
|
||||
else:
|
||||
body = render_f(fr, prec, spec.alt, strip=False)
|
||||
cache[key] = body
|
||||
|
||||
return spec.pad(body, zero_ok=True, sign=spec.sign_of(neg))
|
||||
|
||||
|
||||
class Block:
|
||||
"""The state a run of records shares: the C type they exercise and the
|
||||
single value they all convert."""
|
||||
|
||||
__slots__ = ("mant_bits", "val_text", "value", "special",
|
||||
"neg_zero", "cache")
|
||||
|
||||
def __init__(self):
|
||||
self.mant_bits = 0
|
||||
self.set_value("")
|
||||
|
||||
def set_value(self, text):
|
||||
"""Begin a run of records converting the value written as TEXT."""
|
||||
self.val_text = text
|
||||
self.value = None
|
||||
self.special = None
|
||||
self.neg_zero = False
|
||||
self.cache = {} # digits memoized by convert_float
|
||||
|
||||
def interpret(self, conv):
|
||||
"""Interpret the value text as the C type conversion CONV takes."""
|
||||
if conv in FLOAT_CONVS:
|
||||
lowered = self.val_text.lower()
|
||||
if "inf" in lowered or "nan" in lowered:
|
||||
self.special = "nan" if "nan" in lowered else "inf"
|
||||
else:
|
||||
self.value = decimal_to_binary(self.val_text, self.mant_bits)
|
||||
self.neg_zero = self.val_text.lstrip().startswith("-")
|
||||
elif conv in INT_CONVS:
|
||||
self.value = int(self.val_text)
|
||||
else:
|
||||
self.value = self.val_text
|
||||
|
||||
def expect(self, spec):
|
||||
"""The output SPEC is required to produce for this block's value."""
|
||||
conv = spec.conv
|
||||
# Which C type the value has follows from the conversion specifier,
|
||||
# which is not known until the first record of the block, so the
|
||||
# text is interpreted here rather than where it was read.
|
||||
if self.value is None and self.special is None:
|
||||
self.interpret(conv)
|
||||
spec.neg_zero = self.neg_zero
|
||||
if conv in FLOAT_CONVS:
|
||||
if self.special is not None:
|
||||
return convert_special(self.special, self.neg_zero, spec)
|
||||
return convert_float(self.value, spec, self.cache)
|
||||
if conv in INT_CONVS:
|
||||
return convert_int(self.value, spec)
|
||||
if conv == "c":
|
||||
return convert_char(self.value, spec)
|
||||
return convert_string(self.value, spec)
|
||||
|
||||
|
||||
def check(raw, block):
|
||||
"""Check one record against the value BLOCK holds, reporting a mismatch
|
||||
on standard error. Return whether the record was in order."""
|
||||
# Records are colon separated and end with an empty field, included to
|
||||
# make the data easier to read. What precedes the output are the format
|
||||
# and then any width and precision supplied as arguments.
|
||||
fields = raw.split(":")
|
||||
fields.pop()
|
||||
fmt, actual, args = fields[0], fields[-1], fields[1:-1]
|
||||
|
||||
expect = block.expect(Spec(fmt, list(args)))
|
||||
if expect == actual:
|
||||
return True
|
||||
diagnose('("%s"%s, %s) => "%s", expected "%s"'
|
||||
% (fmt, "".join(", " + arg for arg in args), block.val_text,
|
||||
actual, expect))
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
# The wider types at the precisions exercised produce integers well past
|
||||
# the default limit on conversion to a string. Versions that predate
|
||||
# the limit have no such call and need no lifting.
|
||||
if hasattr(sys, "set_int_max_str_digits"):
|
||||
sys.set_int_max_str_digits(0)
|
||||
|
||||
# Records hold arbitrary bytes, so read them as Latin-1 rather than
|
||||
# through whatever encoding the locale would otherwise select.
|
||||
stdin = io.TextIOWrapper(sys.stdin.buffer, encoding="latin-1",
|
||||
newline="\n")
|
||||
|
||||
block = Block()
|
||||
status = 0
|
||||
|
||||
for raw in stdin:
|
||||
raw = raw.rstrip("\n")
|
||||
|
||||
if raw.startswith("prec:"):
|
||||
block.mant_bits = int(raw[5:])
|
||||
elif raw.startswith("val:"):
|
||||
block.set_value(raw[4:])
|
||||
elif raw.startswith("%"):
|
||||
if not check(raw, block):
|
||||
status = 1
|
||||
else:
|
||||
diagnose('unrecognized input: "%s"' % raw)
|
||||
status = 1
|
||||
|
||||
return status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except BrokenPipeError:
|
||||
# The wrappers pipe into 'head', which stops reading once it has
|
||||
# the first diagnostic. Point what is left of standard error at
|
||||
# the null device, so that flushing at exit does not run into the
|
||||
# same failure and report it a second time.
|
||||
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stderr.fileno())
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user