chainlint.pl: recognize test bodies defined via heredoc

In order to check tests for semantic problems, chainlint.pl scans test scripts, looking for tests defined as: test_expect_success [prereq] title ' body ' where `body` is a single string which is then treated as a standalone chunk of code and "linted" to detect semantic issues. (The same happens for `test_expect_failure` definitions.) The introduction of test definitions in which the test body is instead presented via a heredoc rather than as a single string creates a blind spot in the linting process since such invocations are not recognized by chainlint.pl. Prepare for this new style by also recognizing tests defined as: test_expect_success [prereq] title - <<\EOT body EOT A minor complication is that chainlint.pl has never considered heredoc bodies significant since it doesn't scan them for semantic problems, thus it has always simply thrown them away. However, with the new `test_expect_success` calling sequence, heredoc bodies become meaningful, thus need to be captured. Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Sunshine committed Jul 10, 2024 at 04:38 UTC a4a5f282f58f16a472ec2b08f5dc06ee149a9700
1 file changed +22 -5
t/chainlint.pl
+22 -5
@@ -174,6 +174,10 @@ sub swallow_heredocs {
174 $$b =~ /(?:\G|\n)$indent\Q$$tag[0]\E(?:\n|\z)/gc;
175 if (pos($$b) > $start) {
176 my $body = substr($$b, $start, pos($$b) - $start);
177 + $self->{parser}->{heredocs}->{$$tag[0]} = {
178 + content => substr($body, 0, length($body) - length($&)),
179 + start_line => $self->{lineno},
180 + };
181 $self->{lineno} += () = $body =~ /\n/sg;
182 next;
183 }
@@ -232,7 +236,8 @@ sub new {
236 my $self = bless {
237 buff => [],
238 stop => [],
235 - output => []
239 + output => [],
240 + heredocs => {},
241 } => $class;
242 $self->{lexer} = Lexer->new($self, $s);
243 return $self;
@@ -616,14 +621,21 @@ sub unwrap {
621
622 sub check_test {
623 my $self = shift @_;
619 - my ($title, $body) = map(unwrap, @_);
624 + my $title = unwrap(shift @_);
625 + my $body = shift @_;
626 + my $lineno = $body->[3];
627 + $body = unwrap($body);
628 + if ($body eq '-') {
629 + my $herebody = shift @_;
630 + $body = $herebody->{content};
631 + $lineno = $herebody->{start_line};
632 + }
633 $self->{ntests}++;
634 my $parser = TestParser->new(\$body);
635 my @tokens = $parser->parse();
636 my $problems = $parser->{problems};
637 return unless $emit_all || @$problems;
638 my $c = main::fd_colors(1);
626 - my $lineno = $_[1]->[3];
639 my $start = 0;
640 my $checked = '';
641 for (sort {$a->[1]->[2] <=> $b->[1]->[2]} @$problems) {
@@ -649,8 +661,13 @@ sub parse_cmd {
661 return @tokens unless @tokens && $tokens[0]->[0] =~ /^test_expect_(?:success|failure)$/;
662 my $n = $#tokens;
663 $n-- while $n >= 0 && $tokens[$n]->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
652 - $self->check_test($tokens[1], $tokens[2]) if $n == 2; # title body
653 - $self->check_test($tokens[2], $tokens[3]) if $n > 2; # prereq title body
664 + my $herebody;
665 + if ($n >= 2 && $tokens[$n-1]->[0] eq '-' && $tokens[$n]->[0] =~ /^<<-?(.+)$/) {
666 + $herebody = $self->{heredocs}->{$1};
667 + $n--;
668 + }
669 + $self->check_test($tokens[1], $tokens[2], $herebody) if $n == 2; # title body
670 + $self->check_test($tokens[2], $tokens[3], $herebody) if $n > 2; # prereq title body
671 return @tokens;
672 }
673