]> git.ipfire.org Git - thirdparty/git.git/commitdiff
chainlint.pl: complain about loops lacking explicit failure handling
authorEric Sunshine <sunshine@sunshineco.com>
Thu, 1 Sep 2022 00:29:50 +0000 (00:29 +0000)
committerJunio C Hamano <gitster@pobox.com>
Thu, 1 Sep 2022 17:07:41 +0000 (10:07 -0700)
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>
12 files changed:
t/chainlint.pl
t/chainlint/complex-if-in-cuddled-loop.expect
t/chainlint/for-loop.expect
t/chainlint/loop-detect-failure.expect [new file with mode: 0644]
t/chainlint/loop-detect-failure.test [new file with mode: 0644]
t/chainlint/loop-detect-status.expect [new file with mode: 0644]
t/chainlint/loop-detect-status.test [new file with mode: 0644]
t/chainlint/loop-in-if.expect
t/chainlint/nested-loop-detect-failure.expect [new file with mode: 0644]
t/chainlint/nested-loop-detect-failure.test [new file with mode: 0644]
t/chainlint/semicolon.expect
t/chainlint/while-loop.expect

index a76a09ecf5e2945b6e10ca42851245a6d91fcd5a..674b3ddf69633e6af6919c5018ef614a1c8e247f 100755 (executable)
@@ -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+|\$\?)$/],
index 2fca1834095817a0aba1bce38a5a837dea0debae..dac2d0fd1d9037e9ebed8be9439bbafe334a1960 100644 (file)
@@ -4,6 +4,6 @@
      :
    else
      echo >file
-   fi
+   fi ?!LOOP?!
  done) &&
 test ! -f file
index 6671b8cd842de110882342fc4ee359eb7e4d1375..a5810c9bddd8352c74f3213be1fd54d1986a72f4 100644 (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
 )
diff --git a/t/chainlint/loop-detect-failure.expect b/t/chainlint/loop-detect-failure.expect
new file mode 100644 (file)
index 0000000..a66025c
--- /dev/null
@@ -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
diff --git a/t/chainlint/loop-detect-failure.test b/t/chainlint/loop-detect-failure.test
new file mode 100644 (file)
index 0000000..b9791cc
--- /dev/null
@@ -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
diff --git a/t/chainlint/loop-detect-status.expect b/t/chainlint/loop-detect-status.expect
new file mode 100644 (file)
index 0000000..0ad23bb
--- /dev/null
@@ -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
diff --git a/t/chainlint/loop-detect-status.test b/t/chainlint/loop-detect-status.test
new file mode 100644 (file)
index 0000000..1c6c23c
--- /dev/null
@@ -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
index e1be42376c5ef480c791222dc0cc75d4d01fe1ce..6c5d6e5b2438ef826b15b45de67544f72f633543 100644 (file)
@@ -4,7 +4,7 @@
                while true
                do
                        echo "pop" ?!AMP?!
-                       echo "glup"
+                       echo "glup" ?!LOOP?!
                done ?!AMP?!
                foo
        fi ?!AMP?!
diff --git a/t/chainlint/nested-loop-detect-failure.expect b/t/chainlint/nested-loop-detect-failure.expect
new file mode 100644 (file)
index 0000000..4793a0e
--- /dev/null
@@ -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
diff --git a/t/chainlint/nested-loop-detect-failure.test b/t/chainlint/nested-loop-detect-failure.test
new file mode 100644 (file)
index 0000000..e6f0c1a
--- /dev/null
@@ -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
index ed0b3707ae90139de0aec8e4ccd8d08301cff1dc..3aa2259f36c172b1da6e67d6a533da8238db7830 100644 (file)
@@ -15,5 +15,5 @@
 ) &&
 (cd foo &&
        for i in a b c; do
-               echo;
+               echo; ?!LOOP?!
        done)
index 0d3a9b3d128940a9515d9924964e9efeeaf48152..f272aa21fee1950a97a9eeddee9436a11c4e1f3f 100644 (file)
@@ -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
 )