chainlint.pl: complain about loops lacking explicit failure handling
Shell `for` and `while` loops do not terminate automatically just because a command fails within the loop body. Instead, the loop continues to iterate and eventually returns the exit status of the final command of the final iteration, which may not be the command which failed, thus it is possible for failures to go undetected. Consequently, it is important for test authors to explicitly handle failure within the loop body by terminating the loop manually upon failure. This can be done by returning a non-zero exit code from within the loop body (i.e. `|| return 1`) or exiting (i.e. `|| exit 1`) if the loop is within a subshell, or by manually checking `$?` and taking some appropriate action. Therefore, add logic to detect and complain about loops which lack explicit `return` or `exit`, or `$?` check. Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
832c68b3c2
commit
fd4094c3ca
@ -482,6 +482,17 @@ sub match_ending {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub parse_loop_body {
|
||||
my $self = shift @_;
|
||||
my @tokens = $self->SUPER::parse_loop_body(@_);
|
||||
# did loop signal failure via "|| return" or "|| exit"?
|
||||
return @tokens if !@tokens || grep(/^(?:return|exit|\$\?)$/, @tokens);
|
||||
# flag missing "return/exit" handling explicit failure in loop body
|
||||
my $n = find_non_nl(\@tokens);
|
||||
splice(@tokens, $n + 1, 0, '?!LOOP?!');
|
||||
return @tokens;
|
||||
}
|
||||
|
||||
my @safe_endings = (
|
||||
[qr/^(?:&&|\|\||\||&)$/],
|
||||
[qr/^(?:exit|return)$/, qr/^(?:\d+|\$\?)$/],
|
||||
|
@ -4,6 +4,6 @@
|
||||
:
|
||||
else
|
||||
echo >file
|
||||
fi
|
||||
fi ?!LOOP?!
|
||||
done) &&
|
||||
test ! -f file
|
||||
|
@ -2,10 +2,10 @@
|
||||
for i in a b c
|
||||
do
|
||||
echo $i ?!AMP?!
|
||||
cat <<-EOF
|
||||
cat <<-EOF ?!LOOP?!
|
||||
done ?!AMP?!
|
||||
for i in a b c; do
|
||||
echo $i &&
|
||||
cat $i
|
||||
cat $i ?!LOOP?!
|
||||
done
|
||||
)
|
||||
|
15
t/chainlint/loop-detect-failure.expect
Normal file
15
t/chainlint/loop-detect-failure.expect
Normal file
@ -0,0 +1,15 @@
|
||||
git init r1 &&
|
||||
for n in 1 2 3 4 5
|
||||
do
|
||||
echo "This is file: $n" > r1/file.$n &&
|
||||
git -C r1 add file.$n &&
|
||||
git -C r1 commit -m "$n" || return 1
|
||||
done &&
|
||||
|
||||
git init r2 &&
|
||||
for n in 1000 10000
|
||||
do
|
||||
printf "%"$n"s" X > r2/large.$n &&
|
||||
git -C r2 add large.$n &&
|
||||
git -C r2 commit -m "$n" ?!LOOP?!
|
||||
done
|
17
t/chainlint/loop-detect-failure.test
Normal file
17
t/chainlint/loop-detect-failure.test
Normal file
@ -0,0 +1,17 @@
|
||||
git init r1 &&
|
||||
# LINT: loop handles failure explicitly with "|| return 1"
|
||||
for n in 1 2 3 4 5
|
||||
do
|
||||
echo "This is file: $n" > r1/file.$n &&
|
||||
git -C r1 add file.$n &&
|
||||
git -C r1 commit -m "$n" || return 1
|
||||
done &&
|
||||
|
||||
git init r2 &&
|
||||
# LINT: loop fails to handle failure explicitly with "|| return 1"
|
||||
for n in 1000 10000
|
||||
do
|
||||
printf "%"$n"s" X > r2/large.$n &&
|
||||
git -C r2 add large.$n &&
|
||||
git -C r2 commit -m "$n"
|
||||
done
|
18
t/chainlint/loop-detect-status.expect
Normal file
18
t/chainlint/loop-detect-status.expect
Normal file
@ -0,0 +1,18 @@
|
||||
( while test $i -le $blobcount
|
||||
do
|
||||
printf "Generating blob $i/$blobcount\r" >& 2 &&
|
||||
printf "blob\nmark :$i\ndata $blobsize\n" &&
|
||||
|
||||
printf "%-${blobsize}s" $i &&
|
||||
echo "M 100644 :$i $i" >> commit &&
|
||||
i=$(($i+1)) ||
|
||||
echo $? > exit-status
|
||||
done &&
|
||||
echo "commit refs/heads/main" &&
|
||||
echo "author A U Thor <author@email.com> 123456789 +0000" &&
|
||||
echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
|
||||
echo "data 5" &&
|
||||
echo ">2gb" &&
|
||||
cat commit ) |
|
||||
git fast-import --big-file-threshold=2 &&
|
||||
test ! -f exit-status
|
19
t/chainlint/loop-detect-status.test
Normal file
19
t/chainlint/loop-detect-status.test
Normal file
@ -0,0 +1,19 @@
|
||||
# LINT: "$?" handled explicitly within loop body
|
||||
(while test $i -le $blobcount
|
||||
do
|
||||
printf "Generating blob $i/$blobcount\r" >&2 &&
|
||||
printf "blob\nmark :$i\ndata $blobsize\n" &&
|
||||
#test-tool genrandom $i $blobsize &&
|
||||
printf "%-${blobsize}s" $i &&
|
||||
echo "M 100644 :$i $i" >> commit &&
|
||||
i=$(($i+1)) ||
|
||||
echo $? > exit-status
|
||||
done &&
|
||||
echo "commit refs/heads/main" &&
|
||||
echo "author A U Thor <author@email.com> 123456789 +0000" &&
|
||||
echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
|
||||
echo "data 5" &&
|
||||
echo ">2gb" &&
|
||||
cat commit) |
|
||||
git fast-import --big-file-threshold=2 &&
|
||||
test ! -f exit-status
|
@ -4,7 +4,7 @@
|
||||
while true
|
||||
do
|
||||
echo "pop" ?!AMP?!
|
||||
echo "glup"
|
||||
echo "glup" ?!LOOP?!
|
||||
done ?!AMP?!
|
||||
foo
|
||||
fi ?!AMP?!
|
||||
|
31
t/chainlint/nested-loop-detect-failure.expect
Normal file
31
t/chainlint/nested-loop-detect-failure.expect
Normal file
@ -0,0 +1,31 @@
|
||||
for i in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
echo "$i$j" > "path$i$j" ?!LOOP?!
|
||||
done ?!LOOP?!
|
||||
done &&
|
||||
|
||||
for i in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
echo "$i$j" > "path$i$j" || return 1
|
||||
done
|
||||
done &&
|
||||
|
||||
for i in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
echo "$i$j" > "path$i$j" ?!LOOP?!
|
||||
done || return 1
|
||||
done &&
|
||||
|
||||
for i in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9 ;
|
||||
do
|
||||
echo "$i$j" > "path$i$j" || return 1
|
||||
done || return 1
|
||||
done
|
35
t/chainlint/nested-loop-detect-failure.test
Normal file
35
t/chainlint/nested-loop-detect-failure.test
Normal file
@ -0,0 +1,35 @@
|
||||
# LINT: neither loop handles failure explicitly with "|| return 1"
|
||||
for i in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
echo "$i$j" >"path$i$j"
|
||||
done
|
||||
done &&
|
||||
|
||||
# LINT: inner loop handles failure explicitly with "|| return 1"
|
||||
for i in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
echo "$i$j" >"path$i$j" || return 1
|
||||
done
|
||||
done &&
|
||||
|
||||
# LINT: outer loop handles failure explicitly with "|| return 1"
|
||||
for i in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
echo "$i$j" >"path$i$j"
|
||||
done || return 1
|
||||
done &&
|
||||
|
||||
# LINT: inner & outer loops handles failure explicitly with "|| return 1"
|
||||
for i in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
for j in 0 1 2 3 4 5 6 7 8 9;
|
||||
do
|
||||
echo "$i$j" >"path$i$j" || return 1
|
||||
done || return 1
|
||||
done
|
@ -15,5 +15,5 @@
|
||||
) &&
|
||||
(cd foo &&
|
||||
for i in a b c; do
|
||||
echo;
|
||||
echo; ?!LOOP?!
|
||||
done)
|
||||
|
@ -2,10 +2,10 @@
|
||||
while true
|
||||
do
|
||||
echo foo ?!AMP?!
|
||||
cat <<-EOF
|
||||
cat <<-EOF ?!LOOP?!
|
||||
done ?!AMP?!
|
||||
while true; do
|
||||
echo foo &&
|
||||
cat bar
|
||||
cat bar ?!LOOP?!
|
||||
done
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user