chainlint.pl: add parser to validate tests

Continue fleshing out chainlint.pl by adding TestParser, a parser with special knowledge about how Git tests should be written; for instance, it knows that commands within a test body should be chained together with `&&`. An upcoming parser which plucks test definitions from test scripts will invoke TestParser for each test body it encounters. Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Sunshine committed Sep 1, 2022 at 00:29 UTC 6d932e92fcb49b59b780bc018fe550d867bb3d84
1 file changed +46
t/chainlint.pl
+46
@@ -441,6 +441,52 @@ DONE:
441 return @tokens;
442 }
443
444 +# TestParser is a subclass of ShellParser which, beyond parsing shell script
445 +# code, is also imbued with semantic knowledge of test construction, and checks
446 +# tests for common problems (such as broken &&-chains) which might hide bugs in
447 +# the tests themselves or in behaviors being exercised by the tests. As such,
448 +# TestParser is only called upon to parse test bodies, not the top-level
449 +# scripts in which the tests are defined.
450 +package TestParser;
451 +
452 +use base 'ShellParser';
453 +
454 +sub find_non_nl {
455 + my $tokens = shift @_;
456 + my $n = shift @_;
457 + $n = $#$tokens if !defined($n);
458 + $n-- while $n >= 0 && $$tokens[$n] eq "\n";
459 + return $n;
460 +}
461 +
462 +sub ends_with {
463 + my ($tokens, $needles) = @_;
464 + my $n = find_non_nl($tokens);
465 + for my $needle (reverse(@$needles)) {
466 + return undef if $n < 0;
467 + $n = find_non_nl($tokens, $n), next if $needle eq "\n";
468 + return undef if $$tokens[$n] !~ $needle;
469 + $n--;
470 + }
471 + return 1;
472 +}
473 +
474 +sub accumulate {
475 + my ($self, $tokens, $cmd) = @_;
476 + goto DONE unless @$tokens;
477 + goto DONE if @$cmd == 1 && $$cmd[0] eq "\n";
478 +
479 + # did previous command end with "&&", "||", "|"?
480 + goto DONE if ends_with($tokens, [qr/^(?:&&|\|\||\|)$/]);
481 +
482 + # flag missing "&&" at end of previous command
483 + my $n = find_non_nl($tokens);
484 + splice(@$tokens, $n + 1, 0, '?!AMP?!') unless $n < 0;
485 +
486 +DONE:
487 + $self->SUPER::accumulate($tokens, $cmd);
488 +}
489 +
490 package ScriptParser;
491
492 sub new {