diff mbox series

[11/19] tests: fix broken &&-chains in `$(...)` command substitutions

Message ID 20211209051115.52629-12-sunshine@sunshineco.com (mailing list archive)
State Accepted
Commit c576868eaff8cc4c7fb5cf8b787756b16fde268b
Headers show
Series tests: fix broken &&-chains & abort loops on error | expand

Commit Message

Eric Sunshine Dec. 9, 2021, 5:11 a.m. UTC
The top-level &&-chain checker built into t/test-lib.sh causes tests to
magically exit with code 117 if the &&-chain is broken. However, it has
the shortcoming that the magic does not work within `{...}` groups,
`(...)` subshells, `$(...)` substitutions, or within bodies of compound
statements, such as `if`, `for`, `while`, `case`, etc. `chainlint.sed`
partly fills in the gap by catching broken &&-chains in `(...)`
subshells, but bugs can still lurk behind broken &&-chains in the other
cases.

Fix broken &&-chains in `$(...)` command substitutions in order to
reduce the number of possible lurking bugs.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 contrib/subtree/t/t7900-subtree.sh |  2 +-
 t/t0005-signals.sh                 |  2 +-
 t/t0060-path-utils.sh              |  4 ++--
 t/t1006-cat-file.sh                | 10 +++++-----
 t/t3600-rm.sh                      |  2 +-
 t/t7010-setup.sh                   |  2 +-
 6 files changed, 11 insertions(+), 11 deletions(-)

Comments

Elijah Newren Dec. 9, 2021, 4:44 p.m. UTC | #1
On Wed, Dec 8, 2021 at 11:39 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
>
> The top-level &&-chain checker built into t/test-lib.sh causes tests to
> magically exit with code 117 if the &&-chain is broken. However, it has
> the shortcoming that the magic does not work within `{...}` groups,
> `(...)` subshells, `$(...)` substitutions, or within bodies of compound
> statements, such as `if`, `for`, `while`, `case`, etc. `chainlint.sed`
> partly fills in the gap by catching broken &&-chains in `(...)`
> subshells, but bugs can still lurk behind broken &&-chains in the other
> cases.
>
> Fix broken &&-chains in `$(...)` command substitutions in order to
> reduce the number of possible lurking bugs.
>
> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---
>  contrib/subtree/t/t7900-subtree.sh |  2 +-
>  t/t0005-signals.sh                 |  2 +-
>  t/t0060-path-utils.sh              |  4 ++--
>  t/t1006-cat-file.sh                | 10 +++++-----
>  t/t3600-rm.sh                      |  2 +-
>  t/t7010-setup.sh                   |  2 +-
>  6 files changed, 11 insertions(+), 11 deletions(-)
>
> diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
> index 4153b65321..1c1f76f04a 100755
> --- a/contrib/subtree/t/t7900-subtree.sh
> +++ b/contrib/subtree/t/t7900-subtree.sh
> @@ -1445,7 +1445,7 @@ test_expect_success 'subtree descendant check' '
>         ) &&
>         test_create_commit "$test_count" folder_subtree/0 &&
>         test_create_commit "$test_count" folder_subtree/b &&
> -       cherry=$(cd "$test_count"; git rev-parse HEAD) &&
> +       cherry=$(cd "$test_count" && git rev-parse HEAD) &&
>         (
>                 cd "$test_count" &&
>                 git checkout branch
> diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
> index a5ec6a0315..eba75a2490 100755
> --- a/t/t0005-signals.sh
> +++ b/t/t0005-signals.sh
> @@ -48,7 +48,7 @@ test_expect_success !MINGW 'a constipated git dies with SIGPIPE' '
>  '
>
>  test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' '
> -       OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) &&
> +       OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) &&

Shouldn't the second ';' be replaced with '&&' as well?

>         test_match_signal 13 "$OUT"
>  '
>
> diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
> index 34d1061f32..71a5d370cc 100755
> --- a/t/t0060-path-utils.sh
> +++ b/t/t0060-path-utils.sh
> @@ -216,7 +216,7 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
>         mkdir second &&
>         ln -s ../first second/other &&
>         mkdir third &&
> -       dir="$(cd .git; pwd -P)" &&
> +       dir="$(cd .git && pwd -P)" &&
>         dir2=third/../second/other/.git &&
>         test "$dir" = "$(test-tool path-utils real_path $dir2)" &&
>         file="$dir"/index &&
> @@ -224,7 +224,7 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
>         basename=blub &&
>         test "$dir/$basename" = "$(cd .git && test-tool path-utils real_path "$basename")" &&
>         ln -s ../first/file .git/syml &&
> -       sym="$(cd first; pwd -P)"/file &&
> +       sym="$(cd first && pwd -P)"/file &&
>         test "$sym" = "$(test-tool path-utils real_path "$dir2/syml")"
>  '
>
> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
> index 658628375c..67a3f64c2d 100755
> --- a/t/t1006-cat-file.sh
> +++ b/t/t1006-cat-file.sh
> @@ -211,14 +211,14 @@ done
>  test_expect_success "--batch-check for a non-existent named object" '
>      test "foobar42 missing
>  foobar84 missing" = \
> -    "$( ( echo foobar42; echo_without_newline foobar84; ) | git cat-file --batch-check)"
> +    "$( ( echo foobar42 && echo_without_newline foobar84 ) | git cat-file --batch-check)"
>  '
>
>  test_expect_success "--batch-check for a non-existent hash" '
>      test "0000000000000000000000000000000000000042 missing
>  0000000000000000000000000000000000000084 missing" = \
> -    "$( ( echo 0000000000000000000000000000000000000042;
> -        echo_without_newline 0000000000000000000000000000000000000084; ) |
> +    "$( ( echo 0000000000000000000000000000000000000042 &&
> +        echo_without_newline 0000000000000000000000000000000000000084 ) |
>         git cat-file --batch-check)"
>  '
>
> @@ -226,8 +226,8 @@ test_expect_success "--batch for an existent and a non-existent hash" '
>      test "$tag_sha1 tag $tag_size
>  $tag_content
>  0000000000000000000000000000000000000000 missing" = \
> -    "$( ( echo $tag_sha1;
> -        echo_without_newline 0000000000000000000000000000000000000000; ) |
> +    "$( ( echo $tag_sha1 &&
> +        echo_without_newline 0000000000000000000000000000000000000000 ) |
>         git cat-file --batch)"
>  '
>
> diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
> index bb9ef35dac..ed3952eb98 100755
> --- a/t/t3600-rm.sh
> +++ b/t/t3600-rm.sh
> @@ -265,7 +265,7 @@ test_expect_success 'choking "git rm" should not let it die with cruft (induce S
>
>  test_expect_success !MINGW 'choking "git rm" should not let it die with cruft (induce and check SIGPIPE)' '
>         choke_git_rm_setup &&
> -       OUT=$( ((trap "" PIPE; git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
> +       OUT=$( ((trap "" PIPE && git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&

Same here; shouldn't the second ';' be replaced with '&&' as well?

>         test_match_signal 13 "$OUT" &&
>         test_path_is_missing .git/index.lock
>  '
> diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh
> index 0335a9a158..520f96d09f 100755
> --- a/t/t7010-setup.sh
> +++ b/t/t7010-setup.sh
> @@ -137,7 +137,7 @@ test_expect_success 'setup deeper work tree' '
>
>  test_expect_success 'add a directory outside the work tree' '(
>         cd tester &&
> -       d1="$(cd .. ; pwd)" &&
> +       d1="$(cd .. && pwd)" &&
>         test_must_fail git add "$d1"
>  )'
>
> --
> 2.34.1.307.g9b7440fafd
>
Eric Sunshine Dec. 9, 2021, 4:53 p.m. UTC | #2
On Thu, Dec 9, 2021 at 11:44 AM Elijah Newren <newren@gmail.com> wrote:
> On Wed, Dec 8, 2021 at 11:39 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> >  test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' '
> > -       OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) &&
> > +       OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) &&
>
> Shouldn't the second ';' be replaced with '&&' as well?

Thanks for reading so carefully. In this case, the answer is "no", the
semicolon is correct. This code legitimately wants to capture in the
OUT variable the numeric exit status of the command preceding `echo
$?`. If the semicolon is replaced with `&&`, then the echo won't be
executed if the exit status is non-zero, but we want `echo` to be
executed regardless of the exit status. So, the code is correct with
the semicolon, and would be incorrect with `&&`. (I hope I'm
explaining this well enough to make sense.)

It's this sort of special case which accounts for why the new linter
(as mentioned in the cover letter) has special understanding that a
broken &&-chain can be legitimate in certain circumstances, such as
explicit handling of `$?`.

> > -       OUT=$( ((trap "" PIPE; git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
> > +       OUT=$( ((trap "" PIPE && git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
>
> Same here; shouldn't the second ';' be replaced with '&&' as well?

Same answer as above.
Elijah Newren Dec. 9, 2021, 4:57 p.m. UTC | #3
On Thu, Dec 9, 2021 at 8:53 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
>
> On Thu, Dec 9, 2021 at 11:44 AM Elijah Newren <newren@gmail.com> wrote:
> > On Wed, Dec 8, 2021 at 11:39 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > >  test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' '
> > > -       OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) &&
> > > +       OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) &&
> >
> > Shouldn't the second ';' be replaced with '&&' as well?
>
> Thanks for reading so carefully. In this case, the answer is "no", the
> semicolon is correct. This code legitimately wants to capture in the
> OUT variable the numeric exit status of the command preceding `echo
> $?`. If the semicolon is replaced with `&&`, then the echo won't be
> executed if the exit status is non-zero, but we want `echo` to be
> executed regardless of the exit status. So, the code is correct with
> the semicolon, and would be incorrect with `&&`. (I hope I'm
> explaining this well enough to make sense.)
>
> It's this sort of special case which accounts for why the new linter
> (as mentioned in the cover letter) has special understanding that a
> broken &&-chain can be legitimate in certain circumstances, such as
> explicit handling of `$?`.

Ah, right, you had mentioned this in the cover letter.  Short term
memory on my part, I guess.  Thanks for explaining...again.  :-)

> > > -       OUT=$( ((trap "" PIPE; git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
> > > +       OUT=$( ((trap "" PIPE && git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
> >
> > Same here; shouldn't the second ';' be replaced with '&&' as well?
>
> Same answer as above.
Jeff King Dec. 10, 2021, 9:27 a.m. UTC | #4
On Thu, Dec 09, 2021 at 11:53:47AM -0500, Eric Sunshine wrote:

> On Thu, Dec 9, 2021 at 11:44 AM Elijah Newren <newren@gmail.com> wrote:
> > On Wed, Dec 8, 2021 at 11:39 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > >  test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' '
> > > -       OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) &&
> > > +       OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) &&
> >
> > Shouldn't the second ';' be replaced with '&&' as well?
> 
> Thanks for reading so carefully. In this case, the answer is "no", the
> semicolon is correct. This code legitimately wants to capture in the
> OUT variable the numeric exit status of the command preceding `echo
> $?`. If the semicolon is replaced with `&&`, then the echo won't be
> executed if the exit status is non-zero, but we want `echo` to be
> executed regardless of the exit status. So, the code is correct with
> the semicolon, and would be incorrect with `&&`. (I hope I'm
> explaining this well enough to make sense.)

That makes sense to me. I wondered why it was even worth changing the
earlier semi-colon in that case, then, but...

> It's this sort of special case which accounts for why the new linter
> (as mentioned in the cover letter) has special understanding that a
> broken &&-chain can be legitimate in certain circumstances, such as
> explicit handling of `$?`.

...your unseen magic script explains it. :)

All of the changes here look reasonable. We'd either want to know about
failure (e.g., "cd") or don't expect it to fail (e.g., "echo").

These "trap" calls are probably fine. I can't imagine why they'd fail,
but being a weird shell builtin I wonder if it's possible for them to
fail in odd circumstances. I'm happy to leave that as a hypothetical
until we see it in practice.

-Peff
diff mbox series

Patch

diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 4153b65321..1c1f76f04a 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -1445,7 +1445,7 @@  test_expect_success 'subtree descendant check' '
 	) &&
 	test_create_commit "$test_count" folder_subtree/0 &&
 	test_create_commit "$test_count" folder_subtree/b &&
-	cherry=$(cd "$test_count"; git rev-parse HEAD) &&
+	cherry=$(cd "$test_count" && git rev-parse HEAD) &&
 	(
 		cd "$test_count" &&
 		git checkout branch
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
index a5ec6a0315..eba75a2490 100755
--- a/t/t0005-signals.sh
+++ b/t/t0005-signals.sh
@@ -48,7 +48,7 @@  test_expect_success !MINGW 'a constipated git dies with SIGPIPE' '
 '
 
 test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' '
-	OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) &&
+	OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) &&
 	test_match_signal 13 "$OUT"
 '
 
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 34d1061f32..71a5d370cc 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -216,7 +216,7 @@  test_expect_success SYMLINKS 'real path works on symlinks' '
 	mkdir second &&
 	ln -s ../first second/other &&
 	mkdir third &&
-	dir="$(cd .git; pwd -P)" &&
+	dir="$(cd .git && pwd -P)" &&
 	dir2=third/../second/other/.git &&
 	test "$dir" = "$(test-tool path-utils real_path $dir2)" &&
 	file="$dir"/index &&
@@ -224,7 +224,7 @@  test_expect_success SYMLINKS 'real path works on symlinks' '
 	basename=blub &&
 	test "$dir/$basename" = "$(cd .git && test-tool path-utils real_path "$basename")" &&
 	ln -s ../first/file .git/syml &&
-	sym="$(cd first; pwd -P)"/file &&
+	sym="$(cd first && pwd -P)"/file &&
 	test "$sym" = "$(test-tool path-utils real_path "$dir2/syml")"
 '
 
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 658628375c..67a3f64c2d 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -211,14 +211,14 @@  done
 test_expect_success "--batch-check for a non-existent named object" '
     test "foobar42 missing
 foobar84 missing" = \
-    "$( ( echo foobar42; echo_without_newline foobar84; ) | git cat-file --batch-check)"
+    "$( ( echo foobar42 && echo_without_newline foobar84 ) | git cat-file --batch-check)"
 '
 
 test_expect_success "--batch-check for a non-existent hash" '
     test "0000000000000000000000000000000000000042 missing
 0000000000000000000000000000000000000084 missing" = \
-    "$( ( echo 0000000000000000000000000000000000000042;
-	 echo_without_newline 0000000000000000000000000000000000000084; ) |
+    "$( ( echo 0000000000000000000000000000000000000042 &&
+	 echo_without_newline 0000000000000000000000000000000000000084 ) |
        git cat-file --batch-check)"
 '
 
@@ -226,8 +226,8 @@  test_expect_success "--batch for an existent and a non-existent hash" '
     test "$tag_sha1 tag $tag_size
 $tag_content
 0000000000000000000000000000000000000000 missing" = \
-    "$( ( echo $tag_sha1;
-	 echo_without_newline 0000000000000000000000000000000000000000; ) |
+    "$( ( echo $tag_sha1 &&
+	 echo_without_newline 0000000000000000000000000000000000000000 ) |
        git cat-file --batch)"
 '
 
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index bb9ef35dac..ed3952eb98 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -265,7 +265,7 @@  test_expect_success 'choking "git rm" should not let it die with cruft (induce S
 
 test_expect_success !MINGW 'choking "git rm" should not let it die with cruft (induce and check SIGPIPE)' '
 	choke_git_rm_setup &&
-	OUT=$( ((trap "" PIPE; git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
+	OUT=$( ((trap "" PIPE && git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) &&
 	test_match_signal 13 "$OUT" &&
 	test_path_is_missing .git/index.lock
 '
diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh
index 0335a9a158..520f96d09f 100755
--- a/t/t7010-setup.sh
+++ b/t/t7010-setup.sh
@@ -137,7 +137,7 @@  test_expect_success 'setup deeper work tree' '
 
 test_expect_success 'add a directory outside the work tree' '(
 	cd tester &&
-	d1="$(cd .. ; pwd)" &&
+	d1="$(cd .. && pwd)" &&
 	test_must_fail git add "$d1"
 )'