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>
Eric Sunshine committedSep 1, 2022 at 00:29 UTC56066523ed3ebd16b455e99ce954ec19b6ac5ada
34 files changed+336-2
t/chainlint/blank-line-before-esac.expect
+18
new file mode 100644index 0000000000..48ed4eb124--- /dev/null+++ b/t/chainlint/blank-line-before-esac.expect@@ -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+}
t/chainlint/blank-line-before-esac.test
+19
new file mode 100644index 0000000000..cecccad19f--- /dev/null+++ b/t/chainlint/blank-line-before-esac.test@@ -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+}
t/chainlint/block.expect
+12-1
index 37dbf7d95f..a3bcea492a 100644--- a/t/chainlint/block.expect+++ b/t/chainlint/block.expect@@ -9,4 +9,15 @@ echo c } ?!AMP?! baz-)+) &&++{+ echo a ; ?!AMP?! echo b+} &&+{ echo a ; ?!AMP?! echo b ; } &&++{+ echo "${var}9" &&+ echo "done"+} &&+finis
t/chainlint/block.test
+14-1
index 0a82fd579f..4ab69a4afc 100644--- a/t/chainlint/block.test+++ b/t/chainlint/block.test@@ -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
t/chainlint/chained-block.expect
+9
new file mode 100644index 0000000000..574cdceb07--- /dev/null+++ b/t/chainlint/chained-block.expect@@ -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"+}
t/chainlint/chained-block.test
+11
new file mode 100644index 0000000000..86f81ece63--- /dev/null+++ b/t/chainlint/chained-block.test@@ -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"+}
t/chainlint/chained-subshell.expect
+10
new file mode 100644index 0000000000..af0369d328--- /dev/null+++ b/t/chainlint/chained-subshell.expect@@ -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 )
t/chainlint/chained-subshell.test
+13
new file mode 100644index 0000000000..4ff6ddd8cb--- /dev/null+++ b/t/chainlint/chained-subshell.test@@ -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)
new file mode 100644index 0000000000..2d961a58c6--- /dev/null+++ b/t/chainlint/exclamation.expect@@ -0,0 +1,4 @@+if ! condition ; then echo nope ; else yep ; fi &&+test_prerequisite !MINGW &&+mail uucp!address &&+echo !whatever!
t/chainlint/exclamation.test
+8
new file mode 100644index 0000000000..323595b5bd--- /dev/null+++ b/t/chainlint/exclamation.test@@ -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!
new file mode 100644index 0000000000..5ee59562c9--- /dev/null+++ b/t/chainlint/function.test@@ -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
new file mode 100644index 0000000000..c8a6f18eb4--- /dev/null+++ b/t/chainlint/here-doc-indent-operator.test@@ -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
new file mode 100644index 0000000000..240daa9fd5--- /dev/null+++ b/t/chainlint/if-condition-split.test@@ -0,0 +1,8 @@+# LINT: "if" condition split across multiple lines at "&&" or "||"+if bob &&+ marcia ||+ kevin+then+ echo "nomads"+ echo "for sure"+fi
t/chainlint/one-liner-for-loop.expect
+9
new file mode 100644index 0000000000..51a3dc7c54--- /dev/null+++ b/t/chainlint/one-liner-for-loop.expect@@ -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" &&+)
t/chainlint/one-liner-for-loop.test
+10
new file mode 100644index 0000000000..4bd8c066c7--- /dev/null+++ b/t/chainlint/one-liner-for-loop.test@@ -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" &&+)
t/chainlint/sqstring-in-sqstring.expect
+4
new file mode 100644index 0000000000..cf0b591cf7--- /dev/null+++ b/t/chainlint/sqstring-in-sqstring.expect@@ -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
t/chainlint/sqstring-in-sqstring.test
+5
new file mode 100644index 0000000000..77a425e0c7--- /dev/null+++ b/t/chainlint/sqstring-in-sqstring.test@@ -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
t/chainlint/token-pasting.expect
+27
new file mode 100644index 0000000000..342360bcd0--- /dev/null+++ b/t/chainlint/token-pasting.expect@@ -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'+)
t/chainlint/token-pasting.test
+32
new file mode 100644index 0000000000..b4610ce815--- /dev/null+++ b/t/chainlint/token-pasting.test@@ -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'\''+)