Skip to content

Commit 73c768d

Browse files
sunshinecottaylorr
authored andcommitted
chainlint: annotate original test definition rather than token stream
When chainlint detects problems in a test, such as a broken &&-chain, it prints out the test with "?!FOO?!" annotations inserted at each problem location. However, rather than annotating the original test definition, it instead dumps out a parsed token representation of the test. Since it lacks comments, indentations, here-doc bodies, and so forth, this tokenized representation can be difficult for the test author to digest and relate back to the original test definition. However, now that each parsed token carries positional information, the location of a detected problem can be pinpointed precisely in the original test definition. Therefore, take advantage of this information to annotate the test definition itself rather than annotating the parsed token stream, thus making it easier for a test author to relate a problem back to the source. Maintaining the positional meta-information associated with each detected problem requires a slight change in how the problems are managed internally. In particular, shell syntax such as: msg="total: $(cd data; wc -w *.txt) words" requires the lexical analyzer to recursively invoke the parser in order to detect problems within the $(...) expression inside the double-quoted string. In this case, the recursive parse context will detect the broken &&-chain between the `cd` and `wc` commands, returning the token stream: cd data ; ?!AMP?! wc -w *.txt However, the parent parse context will see everything inside the double-quotes as a single string token: "total: $(cd data ; ?!AMP?! wc -w *.txt) words" losing whatever positional information was attached to the ";" token where the problem was detected. One way to preserve the positional information of a detected problem in a recursive parse context within a string would be to attach the positional information to the annotation textually; for instance: "total: $(cd data ; ?!AMP:21:22?! wc -w *.txt) words" and then extract the positional information when annotating the original test definition. However, a cleaner and much simpler approach is to maintain the list of detected problems separately rather than embedding the problems as annotations directly in the parsed token stream. Not only does this ensure that positional information within recursive parse contexts is not lost, but it keeps the token stream free from non-token pollution, which may simplify implementation of validations added in the future since they won't have to handle non-token "?!FOO!?" items specially. Finally, the chainlint self-test "expect" files need a few mechanical adjustments now that the original test definitions are emitted rather than the parsed token stream. In particular, the following items missing from the historic parsed-token output are now preserved verbatim: * indentation (and whitespace, in general) * comments * here-doc bodies * here-doc tag quoting (i.e. "\EOF") * line-splices (i.e. "\" at the end of a line) Signed-off-by: Eric Sunshine <[email protected]> Signed-off-by: Taylor Blau <[email protected]>
1 parent 5f0321a commit 73c768d

22 files changed

+163
-33
lines changed

t/chainlint.pl

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,13 @@ package TestParser;
459459

460460
use base 'ShellParser';
461461

462+
sub new {
463+
my $class = shift @_;
464+
my $self = $class->SUPER::new(@_);
465+
$self->{problems} = [];
466+
return $self;
467+
}
468+
462469
sub find_non_nl {
463470
my $tokens = shift @_;
464471
my $n = shift @_;
@@ -498,7 +505,7 @@ sub parse_loop_body {
498505
return @tokens if ends_with(\@tokens, [qr/^\|\|$/, "\n", qr/^echo$/, qr/^.+$/]);
499506
# flag missing "return/exit" handling explicit failure in loop body
500507
my $n = find_non_nl(\@tokens);
501-
splice(@tokens, $n + 1, 0, ['?!LOOP?!', $tokens[$n]->[1], $tokens[$n]->[2]]);
508+
push(@{$self->{problems}}, ['LOOP', $tokens[$n]]);
502509
return @tokens;
503510
}
504511

@@ -511,6 +518,7 @@ sub parse_loop_body {
511518

512519
sub accumulate {
513520
my ($self, $tokens, $cmd) = @_;
521+
my $problems = $self->{problems};
514522

515523
# no previous command to check for missing "&&"
516524
goto DONE unless @$tokens;
@@ -531,13 +539,13 @@ sub accumulate {
531539
# failure explicitly), then okay for all preceding commands to be
532540
# missing "&&"
533541
if ($$cmd[0]->[0] =~ /^(?:false|return|exit)$/) {
534-
@$tokens = grep {$_->[0] !~ /^\?!AMP\?!$/} @$tokens;
542+
@$problems = grep {$_->[0] ne 'AMP'} @$problems;
535543
goto DONE;
536544
}
537545

538546
# flag missing "&&" at end of previous command
539547
my $n = find_non_nl($tokens);
540-
splice(@$tokens, $n + 1, 0, ['?!AMP?!', $$tokens[$n]->[1], $$tokens[$n]->[2]]) unless $n < 0;
548+
push(@$problems, ['AMP', $tokens->[$n]]) unless $n < 0;
541549

542550
DONE:
543551
$self->SUPER::accumulate($tokens, $cmd);
@@ -594,12 +602,21 @@ sub check_test {
594602
$self->{ntests}++;
595603
my $parser = TestParser->new(\$body);
596604
my @tokens = $parser->parse();
597-
return unless $emit_all || grep {$_->[0] =~ /\?![^?]+\?!/} @tokens;
605+
my $problems = $parser->{problems};
606+
return unless $emit_all || @$problems;
598607
my $c = main::fd_colors(1);
599-
my $checked = join(' ', map {$_->[0]} @tokens);
608+
my $start = 0;
609+
my $checked = '';
610+
for (sort {$a->[1]->[2] <=> $b->[1]->[2]} @$problems) {
611+
my ($label, $token) = @$_;
612+
my $pos = $token->[2];
613+
$checked .= substr($body, $start, $pos - $start) . " ?!$label?! ";
614+
$start = $pos;
615+
}
616+
$checked .= substr($body, $start);
600617
$checked =~ s/^\n//;
601-
$checked =~ s/^ //mg;
602-
$checked =~ s/ $//mg;
618+
$checked =~ s/(\s) \?!/$1?!/mg;
619+
$checked =~ s/\?! (\s)/?!$1/mg;
603620
$checked =~ s/(\?![^?]+\?!)/$c->{rev}$c->{red}$1$c->{reset}/mg;
604621
$checked .= "\n" unless $checked =~ /\n$/;
605622
push(@{$self->{output}}, "$c->{blue}# chainlint: $title$c->{reset}\n$checked");

t/chainlint/block-comment.expect

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
(
22
{
3+
# show a
34
echo a &&
5+
# show b
46
echo b
57
}
68
)

t/chainlint/case-comment.expect

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
(
22
case "$x" in
3+
# found foo
34
x) foo ;;
5+
# found other
46
*)
7+
# treat it as bar
58
bar
69
;;
710
esac

t/chainlint/close-subshell.expect

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
) | wuzzle &&
1616
(
1717
bop
18-
) | fazz fozz &&
18+
) | fazz \
19+
fozz &&
1920
(
2021
bup
2122
) |

t/chainlint/comment.expect

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
(
2+
# comment 1
23
nothing &&
4+
# comment 2
35
something
6+
# comment 3
7+
# comment 4
48
)

t/chainlint/double-here-doc.expect

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
1-
run_sub_test_lib_test_err run-inv-range-start "--run invalid range start" --run="a-5" <<-EOF &&
2-
check_sub_test_lib_test_err run-inv-range-start <<-EOF_OUT 3 <<-EOF_ERR
1+
run_sub_test_lib_test_err run-inv-range-start \
2+
"--run invalid range start" \
3+
--run="a-5" <<-\EOF &&
4+
test_expect_success "passing test #1" "true"
5+
test_done
6+
EOF
7+
check_sub_test_lib_test_err run-inv-range-start \
8+
<<-\EOF_OUT 3<<-EOF_ERR
9+
> FATAL: Unexpected exit with code 1
10+
EOF_OUT
11+
> error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ}
12+
EOF_ERR

t/chainlint/empty-here-doc.expect

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
git ls-tree $tree path > current &&
2-
cat > expected <<EOF &&
2+
cat > expected <<\EOF &&
3+
EOF
34
test_output

t/chainlint/for-loop.expect

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
for i in a b c
33
do
44
echo $i ?!AMP?!
5-
cat <<-EOF ?!LOOP?!
5+
cat <<-\EOF ?!LOOP?!
6+
bar
7+
EOF
68
done ?!AMP?!
79
for i in a b c; do
810
echo $i &&
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
(
2-
cat <<-INPUT)
2+
cat <<-\INPUT)
3+
fizz
4+
INPUT
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
cat > expect <<-EOF &&
1+
cat >expect <<- EOF &&
2+
header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
3+
num_commits: $1
4+
chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
5+
EOF
26

3-
cat > expect <<-EOF ?!AMP?!
7+
cat >expect << -EOF ?!AMP?!
8+
this is not indented
9+
-EOF
410

511
cleanup

0 commit comments

Comments
 (0)