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:
parent
ae0c55abf8
commit
56066523ed
18
t/chainlint/blank-line-before-esac.expect
Normal file
18
t/chainlint/blank-line-before-esac.expect
Normal 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
|
||||
}
|
19
t/chainlint/blank-line-before-esac.test
Normal file
19
t/chainlint/blank-line-before-esac.test
Normal 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
|
||||
}
|
@ -9,4 +9,15 @@
|
||||
echo c
|
||||
} ?!AMP?!
|
||||
baz
|
||||
)
|
||||
) &&
|
||||
|
||||
{
|
||||
echo a ; ?!AMP?! echo b
|
||||
} &&
|
||||
{ echo a ; ?!AMP?! echo b ; } &&
|
||||
|
||||
{
|
||||
echo "${var}9" &&
|
||||
echo "done"
|
||||
} &&
|
||||
finis
|
||||
|
@ -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
|
||||
|
9
t/chainlint/chained-block.expect
Normal file
9
t/chainlint/chained-block.expect
Normal 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"
|
||||
}
|
11
t/chainlint/chained-block.test
Normal file
11
t/chainlint/chained-block.test
Normal 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"
|
||||
}
|
10
t/chainlint/chained-subshell.expect
Normal file
10
t/chainlint/chained-subshell.expect
Normal 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 )
|
13
t/chainlint/chained-subshell.test
Normal file
13
t/chainlint/chained-subshell.test
Normal 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)
|
2
t/chainlint/command-substitution-subsubshell.expect
Normal file
2
t/chainlint/command-substitution-subsubshell.expect
Normal file
@ -0,0 +1,2 @@
|
||||
OUT=$(( ( large_git 1 >& 3 ) | : ) 3 >& 1) &&
|
||||
test_match_signal 13 "$OUT"
|
3
t/chainlint/command-substitution-subsubshell.test
Normal file
3
t/chainlint/command-substitution-subsubshell.test
Normal 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"
|
2
t/chainlint/double-here-doc.expect
Normal file
2
t/chainlint/double-here-doc.expect
Normal 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
|
12
t/chainlint/double-here-doc.test
Normal file
12
t/chainlint/double-here-doc.test
Normal 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
|
3
t/chainlint/dqstring-line-splice.expect
Normal file
3
t/chainlint/dqstring-line-splice.expect
Normal 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
|
7
t/chainlint/dqstring-line-splice.test
Normal file
7
t/chainlint/dqstring-line-splice.test
Normal 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
|
||||
"'
|
11
t/chainlint/dqstring-no-interpolate.expect
Normal file
11
t/chainlint/dqstring-no-interpolate.expect
Normal 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
|
15
t/chainlint/dqstring-no-interpolate.test
Normal file
15
t/chainlint/dqstring-no-interpolate.test
Normal 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
|
||||
"'
|
3
t/chainlint/empty-here-doc.expect
Normal file
3
t/chainlint/empty-here-doc.expect
Normal file
@ -0,0 +1,3 @@
|
||||
git ls-tree $tree path > current &&
|
||||
cat > expected <<EOF &&
|
||||
test_output
|
5
t/chainlint/empty-here-doc.test
Normal file
5
t/chainlint/empty-here-doc.test
Normal file
@ -0,0 +1,5 @@
|
||||
git ls-tree $tree path >current &&
|
||||
# LINT: empty here-doc
|
||||
cat >expected <<\EOF &&
|
||||
EOF
|
||||
test_output
|
4
t/chainlint/exclamation.expect
Normal file
4
t/chainlint/exclamation.expect
Normal file
@ -0,0 +1,4 @@
|
||||
if ! condition ; then echo nope ; else yep ; fi &&
|
||||
test_prerequisite !MINGW &&
|
||||
mail uucp!address &&
|
||||
echo !whatever!
|
8
t/chainlint/exclamation.test
Normal file
8
t/chainlint/exclamation.test
Normal 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!
|
5
t/chainlint/for-loop-abbreviated.expect
Normal file
5
t/chainlint/for-loop-abbreviated.expect
Normal file
@ -0,0 +1,5 @@
|
||||
for it
|
||||
do
|
||||
path=$(expr "$it" : ( [^:]*) ) &&
|
||||
git update-index --add "$path" || exit
|
||||
done
|
6
t/chainlint/for-loop-abbreviated.test
Normal file
6
t/chainlint/for-loop-abbreviated.test
Normal 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
|
11
t/chainlint/function.expect
Normal file
11
t/chainlint/function.expect
Normal 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
13
t/chainlint/function.test
Normal 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
|
5
t/chainlint/here-doc-indent-operator.expect
Normal file
5
t/chainlint/here-doc-indent-operator.expect
Normal file
@ -0,0 +1,5 @@
|
||||
cat > expect <<-EOF &&
|
||||
|
||||
cat > expect <<-EOF ?!AMP?!
|
||||
|
||||
cleanup
|
13
t/chainlint/here-doc-indent-operator.test
Normal file
13
t/chainlint/here-doc-indent-operator.test
Normal 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
|
7
t/chainlint/if-condition-split.expect
Normal file
7
t/chainlint/if-condition-split.expect
Normal file
@ -0,0 +1,7 @@
|
||||
if bob &&
|
||||
marcia ||
|
||||
kevin
|
||||
then
|
||||
echo "nomads" ?!AMP?!
|
||||
echo "for sure"
|
||||
fi
|
8
t/chainlint/if-condition-split.test
Normal file
8
t/chainlint/if-condition-split.test
Normal 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
|
9
t/chainlint/one-liner-for-loop.expect
Normal file
9
t/chainlint/one-liner-for-loop.expect
Normal 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" &&
|
||||
)
|
10
t/chainlint/one-liner-for-loop.test
Normal file
10
t/chainlint/one-liner-for-loop.test
Normal 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" &&
|
||||
)
|
4
t/chainlint/sqstring-in-sqstring.expect
Normal file
4
t/chainlint/sqstring-in-sqstring.expect
Normal 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
|
5
t/chainlint/sqstring-in-sqstring.test
Normal file
5
t/chainlint/sqstring-in-sqstring.test
Normal 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
|
27
t/chainlint/token-pasting.expect
Normal file
27
t/chainlint/token-pasting.expect
Normal 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'
|
||||
)
|
32
t/chainlint/token-pasting.test
Normal file
32
t/chainlint/token-pasting.test
Normal 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'\''
|
||||
)
|
Loading…
Reference in New Issue
Block a user