Skip to content

Commit fd4094c

Browse files
sunshinecogitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 832c68b commit fd4094c

12 files changed

+153
-7
lines changed

t/chainlint.pl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,17 @@ sub match_ending {
482482
return undef;
483483
}
484484

485+
sub parse_loop_body {
486+
my $self = shift @_;
487+
my @tokens = $self->SUPER::parse_loop_body(@_);
488+
# did loop signal failure via "|| return" or "|| exit"?
489+
return @tokens if !@tokens || grep(/^(?:return|exit|\$\?)$/, @tokens);
490+
# flag missing "return/exit" handling explicit failure in loop body
491+
my $n = find_non_nl(\@tokens);
492+
splice(@tokens, $n + 1, 0, '?!LOOP?!');
493+
return @tokens;
494+
}
495+
485496
my @safe_endings = (
486497
[qr/^(?:&&|\|\||\||&)$/],
487498
[qr/^(?:exit|return)$/, qr/^(?:\d+|\$\?)$/],

t/chainlint/complex-if-in-cuddled-loop.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
:
55
else
66
echo >file
7-
fi
7+
fi ?!LOOP?!
88
done) &&
99
test ! -f file

t/chainlint/for-loop.expect

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
for i in a b c
33
do
44
echo $i ?!AMP?!
5-
cat <<-EOF
5+
cat <<-EOF ?!LOOP?!
66
done ?!AMP?!
77
for i in a b c; do
88
echo $i &&
9-
cat $i
9+
cat $i ?!LOOP?!
1010
done
1111
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
git init r1 &&
2+
for n in 1 2 3 4 5
3+
do
4+
echo "This is file: $n" > r1/file.$n &&
5+
git -C r1 add file.$n &&
6+
git -C r1 commit -m "$n" || return 1
7+
done &&
8+
9+
git init r2 &&
10+
for n in 1000 10000
11+
do
12+
printf "%"$n"s" X > r2/large.$n &&
13+
git -C r2 add large.$n &&
14+
git -C r2 commit -m "$n" ?!LOOP?!
15+
done

t/chainlint/loop-detect-failure.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
git init r1 &&
2+
# LINT: loop handles failure explicitly with "|| return 1"
3+
for n in 1 2 3 4 5
4+
do
5+
echo "This is file: $n" > r1/file.$n &&
6+
git -C r1 add file.$n &&
7+
git -C r1 commit -m "$n" || return 1
8+
done &&
9+
10+
git init r2 &&
11+
# LINT: loop fails to handle failure explicitly with "|| return 1"
12+
for n in 1000 10000
13+
do
14+
printf "%"$n"s" X > r2/large.$n &&
15+
git -C r2 add large.$n &&
16+
git -C r2 commit -m "$n"
17+
done

t/chainlint/loop-detect-status.expect

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
( while test $i -le $blobcount
2+
do
3+
printf "Generating blob $i/$blobcount\r" >& 2 &&
4+
printf "blob\nmark :$i\ndata $blobsize\n" &&
5+
6+
printf "%-${blobsize}s" $i &&
7+
echo "M 100644 :$i $i" >> commit &&
8+
i=$(($i+1)) ||
9+
echo $? > exit-status
10+
done &&
11+
echo "commit refs/heads/main" &&
12+
echo "author A U Thor <[email protected]> 123456789 +0000" &&
13+
echo "committer C O Mitter <[email protected]> 123456789 +0000" &&
14+
echo "data 5" &&
15+
echo ">2gb" &&
16+
cat commit ) |
17+
git fast-import --big-file-threshold=2 &&
18+
test ! -f exit-status

t/chainlint/loop-detect-status.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# LINT: "$?" handled explicitly within loop body
2+
(while test $i -le $blobcount
3+
do
4+
printf "Generating blob $i/$blobcount\r" >&2 &&
5+
printf "blob\nmark :$i\ndata $blobsize\n" &&
6+
#test-tool genrandom $i $blobsize &&
7+
printf "%-${blobsize}s" $i &&
8+
echo "M 100644 :$i $i" >> commit &&
9+
i=$(($i+1)) ||
10+
echo $? > exit-status
11+
done &&
12+
echo "commit refs/heads/main" &&
13+
echo "author A U Thor <[email protected]> 123456789 +0000" &&
14+
echo "committer C O Mitter <[email protected]> 123456789 +0000" &&
15+
echo "data 5" &&
16+
echo ">2gb" &&
17+
cat commit) |
18+
git fast-import --big-file-threshold=2 &&
19+
test ! -f exit-status

t/chainlint/loop-in-if.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
while true
55
do
66
echo "pop" ?!AMP?!
7-
echo "glup"
7+
echo "glup" ?!LOOP?!
88
done ?!AMP?!
99
foo
1010
fi ?!AMP?!
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
for i in 0 1 2 3 4 5 6 7 8 9 ;
2+
do
3+
for j in 0 1 2 3 4 5 6 7 8 9 ;
4+
do
5+
echo "$i$j" > "path$i$j" ?!LOOP?!
6+
done ?!LOOP?!
7+
done &&
8+
9+
for i in 0 1 2 3 4 5 6 7 8 9 ;
10+
do
11+
for j in 0 1 2 3 4 5 6 7 8 9 ;
12+
do
13+
echo "$i$j" > "path$i$j" || return 1
14+
done
15+
done &&
16+
17+
for i in 0 1 2 3 4 5 6 7 8 9 ;
18+
do
19+
for j in 0 1 2 3 4 5 6 7 8 9 ;
20+
do
21+
echo "$i$j" > "path$i$j" ?!LOOP?!
22+
done || return 1
23+
done &&
24+
25+
for i in 0 1 2 3 4 5 6 7 8 9 ;
26+
do
27+
for j in 0 1 2 3 4 5 6 7 8 9 ;
28+
do
29+
echo "$i$j" > "path$i$j" || return 1
30+
done || return 1
31+
done
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# LINT: neither loop handles failure explicitly with "|| return 1"
2+
for i in 0 1 2 3 4 5 6 7 8 9;
3+
do
4+
for j in 0 1 2 3 4 5 6 7 8 9;
5+
do
6+
echo "$i$j" >"path$i$j"
7+
done
8+
done &&
9+
10+
# LINT: inner loop handles failure explicitly with "|| return 1"
11+
for i in 0 1 2 3 4 5 6 7 8 9;
12+
do
13+
for j in 0 1 2 3 4 5 6 7 8 9;
14+
do
15+
echo "$i$j" >"path$i$j" || return 1
16+
done
17+
done &&
18+
19+
# LINT: outer loop handles failure explicitly with "|| return 1"
20+
for i in 0 1 2 3 4 5 6 7 8 9;
21+
do
22+
for j in 0 1 2 3 4 5 6 7 8 9;
23+
do
24+
echo "$i$j" >"path$i$j"
25+
done || return 1
26+
done &&
27+
28+
# LINT: inner & outer loops handles failure explicitly with "|| return 1"
29+
for i in 0 1 2 3 4 5 6 7 8 9;
30+
do
31+
for j in 0 1 2 3 4 5 6 7 8 9;
32+
do
33+
echo "$i$j" >"path$i$j" || return 1
34+
done || return 1
35+
done

0 commit comments

Comments
 (0)