t/chainlint: add more chainlint.pl self-tests

During the development of chainlint.pl, numerous new self-tests were
created to verify correct functioning beyond the checks already
represented by the existing self-tests. The new checks fall into several
categories:

* behavior of the lexical analyzer for complex cases, such as line
  splicing, token pasting, entering and exiting string contexts inside
  and outside of test script bodies; for instance:

    test_expect_success 'title' '
      x=$(echo "something" |
        sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\''
    '

* behavior of the parser for all compound grammatical constructs, such
  as `if...fi`, `case...esac`, `while...done`, `{...}`, etc., and for
  other legal shell grammatical constructs not covered by existing
  chainlint.sed self-tests, as well as complex cases, such as:

    OUT=$( ((large_git 1>&3) | :) 3>&1 ) &&

* detection of problems, such as &&-chain breakage, from top-level to
  any depth since the existing self-tests do not cover any top-level
  context and only cover subshells one level deep due to limitations of
  chainlint.sed

* address blind spots in chainlint.sed (such as not detecting a broken
  &&-chain on a one-line for-loop in a subshell[1]) which chainlint.pl
  correctly detects

* real-world cases which tripped up chainlint.pl during its development

[1]: https://lore.kernel.org/git/dce35a47012fecc6edc11c68e91dbb485c5bc36f.1661663880.git.gitgitgadget@gmail.com/

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Eric Sunshine 2022-09-01 00:29:52 +00:00 committed by Junio C Hamano
parent ae0c55abf8
commit 56066523ed
34 changed files with 336 additions and 2 deletions

View File

@ -0,0 +1,18 @@
test_done ( ) {
case "$test_failure" in
0 )
test_at_end_hook_
exit 0 ;;
* )
if test $test_external_has_tap -eq 0
then
say_color error "# failed $test_failure among $msg"
say "1..$test_count"
fi
exit 1 ;;
esac
}

View File

@ -0,0 +1,19 @@
# LINT: blank line before "esac"
test_done () {
case "$test_failure" in
0)
test_at_end_hook_
exit 0 ;;
*)
if test $test_external_has_tap -eq 0
then
say_color error "# failed $test_failure among $msg"
say "1..$test_count"
fi
exit 1 ;;
esac
}

View File

@ -9,4 +9,15 @@
echo c
} ?!AMP?!
baz
)
) &&
{
echo a ; ?!AMP?! echo b
} &&
{ echo a ; ?!AMP?! echo b ; } &&
{
echo "${var}9" &&
echo "done"
} &&
finis

View File

@ -11,4 +11,17 @@
echo c
}
baz
)
) &&
# LINT: ";" not allowed in place of "&&"
{
echo a; echo b
} &&
{ echo a; echo b; } &&
# LINT: "}" inside string not mistaken as end of block
{
echo "${var}9" &&
echo "done"
} &&
finis

View File

@ -0,0 +1,9 @@
echo nobody home && {
test the doohicky ?!AMP?!
right now
} &&
GIT_EXTERNAL_DIFF=echo git diff | {
read path oldfile oldhex oldmode newfile newhex newmode &&
test "z$oh" = "z$oldhex"
}

View File

@ -0,0 +1,11 @@
# LINT: start of block chained to preceding command
echo nobody home && {
test the doohicky
right now
} &&
# LINT: preceding command pipes to block on same line
GIT_EXTERNAL_DIFF=echo git diff | {
read path oldfile oldhex oldmode newfile newhex newmode &&
test "z$oh" = "z$oldhex"
}

View File

@ -0,0 +1,10 @@
mkdir sub && (
cd sub &&
foo the bar ?!AMP?!
nuff said
) &&
cut "-d " -f actual | ( read s1 s2 s3 &&
test -f $s1 ?!AMP?!
test $(cat $s2) = tree2path1 &&
test $(cat $s3) = tree3path1 )

View File

@ -0,0 +1,13 @@
# LINT: start of subshell chained to preceding command
mkdir sub && (
cd sub &&
foo the bar
nuff said
) &&
# LINT: preceding command pipes to subshell on same line
cut "-d " -f actual | (read s1 s2 s3 &&
test -f $s1
test $(cat $s2) = tree2path1 &&
# LINT: closing subshell ")" correctly detected on same line as "$(...)"
test $(cat $s3) = tree3path1)

View File

@ -0,0 +1,2 @@
OUT=$(( ( large_git 1 >& 3 ) | : ) 3 >& 1) &&
test_match_signal 13 "$OUT"

View File

@ -0,0 +1,3 @@
# LINT: subshell nested in subshell nested in command substitution
OUT=$( ((large_git 1>&3) | :) 3>&1 ) &&
test_match_signal 13 "$OUT"

View File

@ -0,0 +1,2 @@
run_sub_test_lib_test_err run-inv-range-start "--run invalid range start" --run="a-5" <<-EOF &&
check_sub_test_lib_test_err run-inv-range-start <<-EOF_OUT 3 <<-EOF_ERR

View File

@ -0,0 +1,12 @@
run_sub_test_lib_test_err run-inv-range-start \
"--run invalid range start" \
--run="a-5" <<-\EOF &&
test_expect_success "passing test #1" "true"
test_done
EOF
check_sub_test_lib_test_err run-inv-range-start \
<<-\EOF_OUT 3<<-EOF_ERR
> FATAL: Unexpected exit with code 1
EOF_OUT
> error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ}
EOF_ERR

View File

@ -0,0 +1,3 @@
echo 'fatal: reword option of --fixup is mutually exclusive with' '--patch/--interactive/--all/--include/--only' > expect &&
test_must_fail git commit --fixup=reword:HEAD~ $1 2 > actual &&
test_cmp expect actual

View File

@ -0,0 +1,7 @@
# LINT: line-splice within DQ-string
'"
echo 'fatal: reword option of --fixup is mutually exclusive with'\
'--patch/--interactive/--all/--include/--only' >expect &&
test_must_fail git commit --fixup=reword:HEAD~ $1 2>actual &&
test_cmp expect actual
"'

View File

@ -0,0 +1,11 @@
grep "^ ! [rejected][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" out &&
grep "^\.git$" output.txt &&
(
cd client$version &&
GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. $(cat ../input)
) > output &&
cut -d ' ' -f 2 < output | sort > actual &&
test_cmp expect actual

View File

@ -0,0 +1,15 @@
# LINT: regex dollar-sign eol anchor in double-quoted string not special
grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" out &&
# LINT: escaped "$" not mistaken for variable expansion
grep "^\\.git\$" output.txt &&
'"
(
cd client$version &&
# LINT: escaped dollar-sign in double-quoted test body
GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. \$(cat ../input)
) >output &&
cut -d ' ' -f 2 <output | sort >actual &&
test_cmp expect actual
"'

View File

@ -0,0 +1,3 @@
git ls-tree $tree path > current &&
cat > expected <<EOF &&
test_output

View File

@ -0,0 +1,5 @@
git ls-tree $tree path >current &&
# LINT: empty here-doc
cat >expected <<\EOF &&
EOF
test_output

View File

@ -0,0 +1,4 @@
if ! condition ; then echo nope ; else yep ; fi &&
test_prerequisite !MINGW &&
mail uucp!address &&
echo !whatever!

View File

@ -0,0 +1,8 @@
# LINT: "! word" is two tokens
if ! condition; then echo nope; else yep; fi &&
# LINT: "!word" is single token, not two tokens "!" and "word"
test_prerequisite !MINGW &&
# LINT: "word!word" is single token, not three tokens "word", "!", and "word"
mail uucp!address &&
# LINT: "!word!" is single token, not three tokens "!", "word", and "!"
echo !whatever!

View File

@ -0,0 +1,5 @@
for it
do
path=$(expr "$it" : ( [^:]*) ) &&
git update-index --add "$path" || exit
done

View File

@ -0,0 +1,6 @@
# LINT: for-loop lacking optional "in [word...]" before "do"
for it
do
path=$(expr "$it" : '\([^:]*\)') &&
git update-index --add "$path" || exit
done

View File

@ -0,0 +1,11 @@
sha1_file ( ) {
echo "$*" | sed "s#..#.git/objects/&/#"
} &&
remove_object ( ) {
file=$(sha1_file "$*") &&
test -e "$file" ?!AMP?!
rm -f "$file"
} ?!AMP?!
sha1_file arg && remove_object arg

13
t/chainlint/function.test Normal file
View File

@ -0,0 +1,13 @@
# LINT: "()" in function definition not mistaken for subshell
sha1_file() {
echo "$*" | sed "s#..#.git/objects/&/#"
} &&
# LINT: broken &&-chain in function and after function
remove_object() {
file=$(sha1_file "$*") &&
test -e "$file"
rm -f "$file"
}
sha1_file arg && remove_object arg

View File

@ -0,0 +1,5 @@
cat > expect <<-EOF &&
cat > expect <<-EOF ?!AMP?!
cleanup

View File

@ -0,0 +1,13 @@
# LINT: whitespace between operator "<<-" and tag legal
cat >expect <<- EOF &&
header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
num_commits: $1
chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
EOF
# LINT: not an indented here-doc; just a plain here-doc with tag named "-EOF"
cat >expect << -EOF
this is not indented
-EOF
cleanup

View File

@ -0,0 +1,7 @@
if bob &&
marcia ||
kevin
then
echo "nomads" ?!AMP?!
echo "for sure"
fi

View File

@ -0,0 +1,8 @@
# LINT: "if" condition split across multiple lines at "&&" or "||"
if bob &&
marcia ||
kevin
then
echo "nomads"
echo "for sure"
fi

View File

@ -0,0 +1,9 @@
git init dir-rename-and-content &&
(
cd dir-rename-and-content &&
test_write_lines 1 2 3 4 5 >foo &&
mkdir olddir &&
for i in a b c; do echo $i >olddir/$i; ?!LOOP?! done ?!AMP?!
git add foo olddir &&
git commit -m "original" &&
)

View File

@ -0,0 +1,10 @@
git init dir-rename-and-content &&
(
cd dir-rename-and-content &&
test_write_lines 1 2 3 4 5 >foo &&
mkdir olddir &&
# LINT: one-liner for-loop missing "|| exit"; also broken &&-chain
for i in a b c; do echo $i >olddir/$i; done
git add foo olddir &&
git commit -m "original" &&
)

View File

@ -0,0 +1,4 @@
perl -e '
defined($_ = -s $_) or die for @ARGV;
exit 1 if $ARGV[0] <= $ARGV[1];
' test-2-$packname_2.pack test-3-$packname_3.pack

View File

@ -0,0 +1,5 @@
# LINT: SQ-string Perl code fragment within SQ-string
perl -e '\''
defined($_ = -s $_) or die for @ARGV;
exit 1 if $ARGV[0] <= $ARGV[1];
'\'' test-2-$packname_2.pack test-3-$packname_3.pack

View File

@ -0,0 +1,27 @@
git config filter.rot13.smudge ./rot13.sh &&
git config filter.rot13.clean ./rot13.sh &&
{
echo "*.t filter=rot13" ?!AMP?!
echo "*.i ident"
} > .gitattributes &&
{
echo a b c d e f g h i j k l m ?!AMP?!
echo n o p q r s t u v w x y z ?!AMP?!
echo '$Id$'
} > test &&
cat test > test.t &&
cat test > test.o &&
cat test > test.i &&
git add test test.t test.i &&
rm -f test test.t test.i &&
git checkout -- test test.t test.i &&
echo "content-test2" > test2.o &&
echo "content-test3 - filename with special characters" > "test3 'sq',$x=.o" ?!AMP?!
downstream_url_for_sed=$(
printf "%sn" "$downstream_url" |
sed -e 's/\/\\/g' -e 's/[[/.*^$]/\&/g'
)

View File

@ -0,0 +1,32 @@
# LINT: single token; composite of multiple strings
git config filter.rot13.smudge ./rot13.sh &&
git config filter.rot13.clean ./rot13.sh &&
{
echo "*.t filter=rot13"
echo "*.i ident"
} >.gitattributes &&
{
echo a b c d e f g h i j k l m
echo n o p q r s t u v w x y z
# LINT: exit/enter string context and escaped-quote outside of string
echo '\''$Id$'\''
} >test &&
cat test >test.t &&
cat test >test.o &&
cat test >test.i &&
git add test test.t test.i &&
rm -f test test.t test.i &&
git checkout -- test test.t test.i &&
echo "content-test2" >test2.o &&
# LINT: exit/enter string context and escaped-quote outside of string
echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x=.o"
# LINT: single token; composite of multiple strings
downstream_url_for_sed=$(
printf "%s\n" "$downstream_url" |
# LINT: exit/enter string context; "&" inside string not command terminator
sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\''
)