Merge pull request #12142 from ethereum/bash-assert-and-stack-traces

Assert and stack traces for Bash scripts
This commit is contained in:
Kamil Śliwak 2021-10-26 10:44:39 +02:00 committed by GitHub
commit 2162039c50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 91 additions and 24 deletions

View File

@ -19,6 +19,13 @@
# (c) 2016-2019 solidity contributors. # (c) 2016-2019 solidity contributors.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# The fail() function defined below requires set -e to be enabled.
set -e
# Save the initial working directory so that printStackTrace() can access it even if the sourcing
# changes directory. The paths returned by `caller` are relative to it.
_initial_work_dir=$(pwd)
if [ "$CIRCLECI" ] if [ "$CIRCLECI" ]
then then
export TERM="${TERM:-xterm}" export TERM="${TERM:-xterm}"
@ -33,12 +40,63 @@ else
function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; } function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; }
fi fi
function printStackTrace
{
printWarning ""
printWarning "Stack trace:"
local frame=1
while caller "$frame" > /dev/null
do
local lineNumber line file function
# `caller` returns something that could already be printed as a stacktrace but we can make
# it more readable by rearranging the components.
# NOTE: This assumes that paths do not contain spaces.
lineNumber=$(caller "$frame" | cut --delimiter " " --field 1)
function=$(caller "$frame" | cut --delimiter " " --field 2)
file=$(caller "$frame" | cut --delimiter " " --field 3)
# Paths in the output from `caller` can be relative or absolute (depends on how the path
# with which the script was invoked) and if they're relative, they're not necessarily
# relative to the current working dir. This is a heuristic that will work if they're absolute,
# relative to current dir, or relative to the dir that was current when the script started.
# If neither works, it gives up.
line=$(
{
tail "--lines=+${lineNumber}" "$file" ||
tail "--lines=+${lineNumber}" "${_initial_work_dir}/${file}"
} 2> /dev/null |
head --lines=1 |
sed -e 's/^[[:space:]]*//'
) || line="<failed to find source line>"
>&2 printf " %s:%d in function %s()\n" "$file" "$lineNumber" "$function"
>&2 printf " %s\n" "$line"
((frame++))
done
}
function fail() function fail()
{ {
printError "$@" printError "$@"
# Using return rather than exit lets the invoking code handle the failure by suppressing the exit code.
return 1 return 1
} }
function assertFail()
{
printError ""
(( $# == 0 )) && printError "Assertion failed."
(( $# == 1 )) && printError "Assertion failed: $1"
printStackTrace
# Intentionally using exit here because assertion failures are not supposed to be handled.
exit 2
}
function msg_on_error() function msg_on_error()
{ {
local error_message local error_message
@ -67,7 +125,7 @@ function msg_on_error()
shift shift
;; ;;
*) *)
fail "Invalid option for msg_on_error: $1" assertFail "Invalid option for msg_on_error: $1"
;; ;;
esac esac
done done
@ -85,24 +143,30 @@ function msg_on_error()
rm "$stdout_file" "$stderr_file" rm "$stdout_file" "$stderr_file"
return 0 return 0
else else
printError "Command failed: $SOLC ${command[*]}" printError ""
printError "Command failed: ${error_message}"
printError " command: $SOLC ${command[*]}"
if [[ -s "$stdout_file" ]] if [[ -s "$stdout_file" ]]
then then
printError "stdout:" printError "--- stdout ---"
printError "-----------"
>&2 cat "$stdout_file" >&2 cat "$stdout_file"
printError "--------------"
else else
printError " stdout: <EMPTY>" printError " stdout: <EMPTY>"
fi fi
if [[ -s "$stderr_file" ]] if [[ -s "$stderr_file" ]]
then then
printError "stderr:" printError "--- stderr ---"
>&2 cat "$stderr_file" >&2 cat "$stderr_file"
printError "--------------"
else else
printError " stderr: <EMPTY>" printError " stderr: <EMPTY>"
fi fi
printError "$error_message"
rm "$stdout_file" "$stderr_file" rm "$stdout_file" "$stderr_file"
printStackTrace
return 1 return 1
fi fi
} }

View File

@ -92,7 +92,7 @@ echo "Using solc binary at ${SOLC}"
INTERACTIVE=true INTERACTIVE=true
if ! tty -s || [ "$CI" ] if ! tty -s || [ "$CI" ]
then then
INTERACTIVE="" INTERACTIVE=false
fi fi
# extend stack size in case we run via ASAN # extend stack size in case we run via ASAN
@ -123,7 +123,7 @@ function update_expectation {
function ask_expectation_update function ask_expectation_update
{ {
if [[ $INTERACTIVE != "" ]] if [[ $INTERACTIVE == true ]]
then then
local newExpectation="${1}" local newExpectation="${1}"
local expectationFile="${2}" local expectationFile="${2}"
@ -142,12 +142,13 @@ function ask_expectation_update
e*) "$editor" "$expectationFile"; break;; e*) "$editor" "$expectationFile"; break;;
u*) update_expectation "$newExpectation" "$expectationFile"; break;; u*) update_expectation "$newExpectation" "$expectationFile"; break;;
s*) return;; s*) return;;
q*) exit 1;; q*) fail;;
esac esac
done done
fi fi
else else
exit 1 [[ $INTERACTIVE == false ]] || assertFail
fail
fi fi
} }
@ -252,7 +253,7 @@ EOF
printError "Incorrect exit code. Expected $exit_code_expected but got $exitCode." printError "Incorrect exit code. Expected $exit_code_expected but got $exitCode."
[[ $exit_code_expectation_file != "" ]] && ask_expectation_update "$exitCode" "$exit_code_expectation_file" [[ $exit_code_expectation_file != "" ]] && ask_expectation_update "$exitCode" "$exit_code_expectation_file"
[[ $exit_code_expectation_file == "" ]] && exit 1 [[ $exit_code_expectation_file == "" ]] && fail
fi fi
if [[ "$(cat "$stdout_path")" != "${stdout_expected}" ]] if [[ "$(cat "$stdout_path")" != "${stdout_expected}" ]]
@ -266,7 +267,7 @@ EOF
printError "When running $solc_command" printError "When running $solc_command"
[[ $stdout_expectation_file != "" ]] && ask_expectation_update "$(cat "$stdout_path")" "$stdout_expectation_file" [[ $stdout_expectation_file != "" ]] && ask_expectation_update "$(cat "$stdout_path")" "$stdout_expectation_file"
[[ $stdout_expectation_file == "" ]] && exit 1 [[ $stdout_expectation_file == "" ]] && fail
fi fi
if [[ "$(cat "$stderr_path")" != "${stderr_expected}" ]] if [[ "$(cat "$stderr_path")" != "${stderr_expected}" ]]
@ -280,7 +281,7 @@ EOF
printError "When running $solc_command" printError "When running $solc_command"
[[ $stderr_expectation_file != "" ]] && ask_expectation_update "$(cat "$stderr_path")" "$stderr_expectation_file" [[ $stderr_expectation_file != "" ]] && ask_expectation_update "$(cat "$stderr_path")" "$stderr_expectation_file"
[[ $stderr_expectation_file == "" ]] && exit 1 [[ $stderr_expectation_file == "" ]] && fail
fi fi
rm "$stdout_path" "$stderr_path" rm "$stdout_path" "$stderr_path"
@ -300,10 +301,10 @@ function test_solc_assembly_output()
if [ -z "$empty" ] if [ -z "$empty" ]
then then
printError "Incorrect assembly output. Expected: " printError "Incorrect assembly output. Expected: "
echo -e "${expected}" >&2 echo -e "${expected}"
printError "with arguments ${solc_args[*]}, but got:" printError "with arguments ${solc_args[*]}, but got:"
echo "${output}" >&2 echo "${output}"
exit 1 fail
fi fi
} }
@ -373,7 +374,7 @@ printTask "Running general commandline tests..."
then then
printError "Ambiguous input. Found input files in multiple formats:" printError "Ambiguous input. Found input files in multiple formats:"
echo -e "${inputFiles}" echo -e "${inputFiles}"
exit 1 fail
fi fi
# Use printf to get rid of the trailing newline # Use printf to get rid of the trailing newline
@ -475,7 +476,8 @@ echo "Done."
printTask "Testing library checksum..." printTask "Testing library checksum..."
echo '' | msg_on_error --no-stdout "$SOLC" - --link --libraries a=0x90f20564390eAe531E810af625A22f51385Cd222 echo '' | msg_on_error --no-stdout "$SOLC" - --link --libraries a=0x90f20564390eAe531E810af625A22f51385Cd222
echo '' | "$SOLC" - --link --libraries a=0x80f20564390eAe531E810af625A22f51385Cd222 &>/dev/null && exit 1 echo '' | "$SOLC" - --link --libraries a=0x80f20564390eAe531E810af625A22f51385Cd222 &>/dev/null && \
fail "solc --link did not reject a library address with an invalid checksum."
printTask "Testing long library names..." printTask "Testing long library names..."
echo '' | msg_on_error --no-stdout "$SOLC" - --link --libraries aveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylonglibraryname=0x90f20564390eAe531E810af625A22f51385Cd222 echo '' | msg_on_error --no-stdout "$SOLC" - --link --libraries aveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylonglibraryname=0x90f20564390eAe531E810af625A22f51385Cd222
@ -503,7 +505,8 @@ SOLTMPDIR=$(mktemp -d)
# First time it works # First time it works
echo 'contract C {}' | msg_on_error --no-stderr "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create" echo 'contract C {}' | msg_on_error --no-stderr "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create"
# Second time it fails # Second time it fails
echo 'contract C {}' | "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create" 2>/dev/null && exit 1 echo 'contract C {}' | "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create" 2>/dev/null && \
fail "solc did not refuse to overwrite $SOLTMPDIR/non-existing-stuff-to-create."
# Unless we force # Unless we force
echo 'contract C {}' | msg_on_error --no-stderr "$SOLC" - --overwrite --bin -o "$SOLTMPDIR/non-existing-stuff-to-create" echo 'contract C {}' | msg_on_error --no-stderr "$SOLC" - --overwrite --bin -o "$SOLTMPDIR/non-existing-stuff-to-create"
) )
@ -517,8 +520,8 @@ printTask "Testing assemble, yul, strict-assembly and optimize..."
# Test options above in conjunction with --optimize. # Test options above in conjunction with --optimize.
# Using both, --assemble and --optimize should fail. # Using both, --assemble and --optimize should fail.
echo '{}' | "$SOLC" - --assemble --optimize &>/dev/null && exit 1 echo '{}' | "$SOLC" - --assemble --optimize &>/dev/null && fail "solc --assemble --optimize did not fail as expected."
echo '{}' | "$SOLC" - --yul --optimize &>/dev/null && exit 1 echo '{}' | "$SOLC" - --yul --optimize &>/dev/null && fail "solc --yul --optimize did not fail as expected."
# Test yul and strict assembly output # Test yul and strict assembly output
# Non-empty code results in non-empty binary representation with optimizations turned off, # Non-empty code results in non-empty binary representation with optimizations turned off,
@ -563,8 +566,8 @@ SOLTMPDIR=$(mktemp -d)
cd "$SOLTMPDIR" cd "$SOLTMPDIR"
if ! "$REPO_ROOT/scripts/ASTImportTest.sh" if ! "$REPO_ROOT/scripts/ASTImportTest.sh"
then then
rm -rf "$SOLTMPDIR" rm -r "$SOLTMPDIR"
exit 1 fail
fi fi
) )
rm -r "$SOLTMPDIR" rm -r "$SOLTMPDIR"