Skip to content

Commit 2162039

Browse files
authored
Merge pull request #12142 from ethereum/bash-assert-and-stack-traces
Assert and stack traces for Bash scripts
2 parents e6e30f8 + 0280c8d commit 2162039

File tree

2 files changed

+91
-24
lines changed

2 files changed

+91
-24
lines changed

scripts/common.sh

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
# (c) 2016-2019 solidity contributors.
2020
# ------------------------------------------------------------------------------
2121

22+
# The fail() function defined below requires set -e to be enabled.
23+
set -e
24+
25+
# Save the initial working directory so that printStackTrace() can access it even if the sourcing
26+
# changes directory. The paths returned by `caller` are relative to it.
27+
_initial_work_dir=$(pwd)
28+
2229
if [ "$CIRCLECI" ]
2330
then
2431
export TERM="${TERM:-xterm}"
@@ -33,12 +40,63 @@ else
3340
function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; }
3441
fi
3542

43+
function printStackTrace
44+
{
45+
printWarning ""
46+
printWarning "Stack trace:"
47+
48+
local frame=1
49+
while caller "$frame" > /dev/null
50+
do
51+
local lineNumber line file function
52+
53+
# `caller` returns something that could already be printed as a stacktrace but we can make
54+
# it more readable by rearranging the components.
55+
# NOTE: This assumes that paths do not contain spaces.
56+
lineNumber=$(caller "$frame" | cut --delimiter " " --field 1)
57+
function=$(caller "$frame" | cut --delimiter " " --field 2)
58+
file=$(caller "$frame" | cut --delimiter " " --field 3)
59+
60+
# Paths in the output from `caller` can be relative or absolute (depends on how the path
61+
# with which the script was invoked) and if they're relative, they're not necessarily
62+
# relative to the current working dir. This is a heuristic that will work if they're absolute,
63+
# relative to current dir, or relative to the dir that was current when the script started.
64+
# If neither works, it gives up.
65+
line=$(
66+
{
67+
tail "--lines=+${lineNumber}" "$file" ||
68+
tail "--lines=+${lineNumber}" "${_initial_work_dir}/${file}"
69+
} 2> /dev/null |
70+
head --lines=1 |
71+
sed -e 's/^[[:space:]]*//'
72+
) || line="<failed to find source line>"
73+
74+
>&2 printf " %s:%d in function %s()\n" "$file" "$lineNumber" "$function"
75+
>&2 printf " %s\n" "$line"
76+
77+
((frame++))
78+
done
79+
}
80+
3681
function fail()
3782
{
3883
printError "$@"
84+
85+
# Using return rather than exit lets the invoking code handle the failure by suppressing the exit code.
3986
return 1
4087
}
4188

89+
function assertFail()
90+
{
91+
printError ""
92+
(( $# == 0 )) && printError "Assertion failed."
93+
(( $# == 1 )) && printError "Assertion failed: $1"
94+
printStackTrace
95+
96+
# Intentionally using exit here because assertion failures are not supposed to be handled.
97+
exit 2
98+
}
99+
42100
function msg_on_error()
43101
{
44102
local error_message
@@ -67,7 +125,7 @@ function msg_on_error()
67125
shift
68126
;;
69127
*)
70-
fail "Invalid option for msg_on_error: $1"
128+
assertFail "Invalid option for msg_on_error: $1"
71129
;;
72130
esac
73131
done
@@ -85,24 +143,30 @@ function msg_on_error()
85143
rm "$stdout_file" "$stderr_file"
86144
return 0
87145
else
88-
printError "Command failed: $SOLC ${command[*]}"
146+
printError ""
147+
printError "Command failed: ${error_message}"
148+
printError " command: $SOLC ${command[*]}"
89149
if [[ -s "$stdout_file" ]]
90150
then
91-
printError "stdout:"
151+
printError "--- stdout ---"
152+
printError "-----------"
92153
>&2 cat "$stdout_file"
154+
printError "--------------"
93155
else
94-
printError "stdout: <EMPTY>"
156+
printError " stdout: <EMPTY>"
95157
fi
96158
if [[ -s "$stderr_file" ]]
97159
then
98-
printError "stderr:"
160+
printError "--- stderr ---"
99161
>&2 cat "$stderr_file"
162+
printError "--------------"
100163
else
101-
printError "stderr: <EMPTY>"
164+
printError " stderr: <EMPTY>"
102165
fi
103166

104-
printError "$error_message"
105167
rm "$stdout_file" "$stderr_file"
168+
169+
printStackTrace
106170
return 1
107171
fi
108172
}

test/cmdlineTests.sh

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ echo "Using solc binary at ${SOLC}"
9292
INTERACTIVE=true
9393
if ! tty -s || [ "$CI" ]
9494
then
95-
INTERACTIVE=""
95+
INTERACTIVE=false
9696
fi
9797

9898
# extend stack size in case we run via ASAN
@@ -123,7 +123,7 @@ function update_expectation {
123123

124124
function ask_expectation_update
125125
{
126-
if [[ $INTERACTIVE != "" ]]
126+
if [[ $INTERACTIVE == true ]]
127127
then
128128
local newExpectation="${1}"
129129
local expectationFile="${2}"
@@ -142,12 +142,13 @@ function ask_expectation_update
142142
e*) "$editor" "$expectationFile"; break;;
143143
u*) update_expectation "$newExpectation" "$expectationFile"; break;;
144144
s*) return;;
145-
q*) exit 1;;
145+
q*) fail;;
146146
esac
147147
done
148148
fi
149149
else
150-
exit 1
150+
[[ $INTERACTIVE == false ]] || assertFail
151+
fail
151152
fi
152153
}
153154

@@ -252,7 +253,7 @@ EOF
252253
printError "Incorrect exit code. Expected $exit_code_expected but got $exitCode."
253254

254255
[[ $exit_code_expectation_file != "" ]] && ask_expectation_update "$exitCode" "$exit_code_expectation_file"
255-
[[ $exit_code_expectation_file == "" ]] && exit 1
256+
[[ $exit_code_expectation_file == "" ]] && fail
256257
fi
257258

258259
if [[ "$(cat "$stdout_path")" != "${stdout_expected}" ]]
@@ -266,7 +267,7 @@ EOF
266267
printError "When running $solc_command"
267268

268269
[[ $stdout_expectation_file != "" ]] && ask_expectation_update "$(cat "$stdout_path")" "$stdout_expectation_file"
269-
[[ $stdout_expectation_file == "" ]] && exit 1
270+
[[ $stdout_expectation_file == "" ]] && fail
270271
fi
271272

272273
if [[ "$(cat "$stderr_path")" != "${stderr_expected}" ]]
@@ -280,7 +281,7 @@ EOF
280281
printError "When running $solc_command"
281282

282283
[[ $stderr_expectation_file != "" ]] && ask_expectation_update "$(cat "$stderr_path")" "$stderr_expectation_file"
283-
[[ $stderr_expectation_file == "" ]] && exit 1
284+
[[ $stderr_expectation_file == "" ]] && fail
284285
fi
285286

286287
rm "$stdout_path" "$stderr_path"
@@ -300,10 +301,10 @@ function test_solc_assembly_output()
300301
if [ -z "$empty" ]
301302
then
302303
printError "Incorrect assembly output. Expected: "
303-
echo -e "${expected}"
304+
>&2 echo -e "${expected}"
304305
printError "with arguments ${solc_args[*]}, but got:"
305-
echo "${output}"
306-
exit 1
306+
>&2 echo "${output}"
307+
fail
307308
fi
308309
}
309310

@@ -373,7 +374,7 @@ printTask "Running general commandline tests..."
373374
then
374375
printError "Ambiguous input. Found input files in multiple formats:"
375376
echo -e "${inputFiles}"
376-
exit 1
377+
fail
377378
fi
378379

379380
# Use printf to get rid of the trailing newline
@@ -475,7 +476,8 @@ echo "Done."
475476

476477
printTask "Testing library checksum..."
477478
echo '' | msg_on_error --no-stdout "$SOLC" - --link --libraries a=0x90f20564390eAe531E810af625A22f51385Cd222
478-
echo '' | "$SOLC" - --link --libraries a=0x80f20564390eAe531E810af625A22f51385Cd222 &>/dev/null && exit 1
479+
echo '' | "$SOLC" - --link --libraries a=0x80f20564390eAe531E810af625A22f51385Cd222 &>/dev/null && \
480+
fail "solc --link did not reject a library address with an invalid checksum."
479481

480482
printTask "Testing long library names..."
481483
echo '' | msg_on_error --no-stdout "$SOLC" - --link --libraries aveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylonglibraryname=0x90f20564390eAe531E810af625A22f51385Cd222
@@ -503,7 +505,8 @@ SOLTMPDIR=$(mktemp -d)
503505
# First time it works
504506
echo 'contract C {}' | msg_on_error --no-stderr "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create"
505507
# Second time it fails
506-
echo 'contract C {}' | "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create" 2>/dev/null && exit 1
508+
echo 'contract C {}' | "$SOLC" - --bin -o "$SOLTMPDIR/non-existing-stuff-to-create" 2>/dev/null && \
509+
fail "solc did not refuse to overwrite $SOLTMPDIR/non-existing-stuff-to-create."
507510
# Unless we force
508511
echo 'contract C {}' | msg_on_error --no-stderr "$SOLC" - --overwrite --bin -o "$SOLTMPDIR/non-existing-stuff-to-create"
509512
)
@@ -517,8 +520,8 @@ printTask "Testing assemble, yul, strict-assembly and optimize..."
517520

518521
# Test options above in conjunction with --optimize.
519522
# Using both, --assemble and --optimize should fail.
520-
echo '{}' | "$SOLC" - --assemble --optimize &>/dev/null && exit 1
521-
echo '{}' | "$SOLC" - --yul --optimize &>/dev/null && exit 1
523+
echo '{}' | "$SOLC" - --assemble --optimize &>/dev/null && fail "solc --assemble --optimize did not fail as expected."
524+
echo '{}' | "$SOLC" - --yul --optimize &>/dev/null && fail "solc --yul --optimize did not fail as expected."
522525

523526
# Test yul and strict assembly output
524527
# Non-empty code results in non-empty binary representation with optimizations turned off,
@@ -563,8 +566,8 @@ SOLTMPDIR=$(mktemp -d)
563566
cd "$SOLTMPDIR"
564567
if ! "$REPO_ROOT/scripts/ASTImportTest.sh"
565568
then
566-
rm -rf "$SOLTMPDIR"
567-
exit 1
569+
rm -r "$SOLTMPDIR"
570+
fail
568571
fi
569572
)
570573
rm -r "$SOLTMPDIR"

0 commit comments

Comments
 (0)