Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
FROM perl:5.42.2-slim-bookworm@sha256:49f4e5e7e2fc5b12e5fc9b5a0603d96502feb24b97babd1bdf42e3f1fc3ebc43

# expect-dev - provides `unbuffer`
RUN apt-get update && \
apt-get install -y curl npm expect-dev && \
apt-get purge --auto-remove -y && \
apt-get install -y --no-install-recommends expect-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

RUN npm install -g tap-parser

WORKDIR /opt/test-runner
COPY . .
RUN curl -fsSL https://raw.githubusercontent.com/skaji/cpm/main/cpm | perl - install -g --cpanfile /opt/test-runner/cpanfile --snapshot /dev/null

# Build the CPAN deps. A C toolchain is needed to compile the XS modules, but it is
# installed and purged within this single layer so the compiler doesn't bloat the
# final image. The cpm build cache is removed too.
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential curl && \
curl -fsSL https://raw.githubusercontent.com/skaji/cpm/main/cpm | perl - install -g --cpanfile /opt/test-runner/cpanfile --snapshot /dev/null && \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, you can use ADD in place of curl, too.

apt-get purge -y build-essential curl && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf "$HOME/.perl-cpm" /usr/share/doc /var/lib/apt/lists/* /tmp/*

ENTRYPOINT ["/opt/test-runner/bin/run.sh"]
2 changes: 1 addition & 1 deletion bin/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ else
test_file="${input_dir}/${slug}.t"
fi
chmod +x $test_file
TABLE_TERM_SIZE=120 HARNESS_ACTIVE=1 PERL5OPT='-MXXX=-global' unbuffer perl -I"${input_dir}/lib" -I"${input_dir}/local/lib/perl5" $test_file 2>&1 | tap-parser -j 0 > "${output_dir}/tap.json"
TABLE_TERM_SIZE=120 HARNESS_ACTIVE=1 PERL5OPT='-MXXX=-global' unbuffer perl -I"${input_dir}/lib" -I"${input_dir}/local/lib/perl5" $test_file 2>&1 | perl "$(dirname "$0")/tap-to-json.pl" > "${output_dir}/tap.json"
bin/transform-results.pl "${output_dir}/tap.json" "${results_file}" $test_file

echo "${slug}: done"
88 changes: 88 additions & 0 deletions bin/tap-to-json.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env perl

# Drop-in replacement for `tap-parser -j 0` (the npm package), using only core Perl
# (JSON::PP). Reads a TAP stream on STDIN and writes, to STDOUT, the JSON event array
# that bin/transform-results.pl consumes:
#
# ["assert", {"ok": <bool>, "name": <str>}] one per TAP test point
# ["comment", "# ...\n"] diagnostic (# ...) lines
# ["extra", "...\n"] non-TAP output (errors, prints)
# ["bailout", "<reason>"] a `Bail out!` line
# ["complete",{"count":N,"ok":<bool>,
# "plan":{"end":<N|null>,"skipAll":<bool>}}] emitted once at the end
#
# Only the fields transform-results.pl reads are emitted; version/plan lines are folded
# into the trailing `complete` event exactly as the npm tap-parser reports them.

use v5.36;
use JSON::PP ();

binmode STDIN, ':encoding(UTF-8)';
binmode STDOUT, ':encoding(UTF-8)';

sub jbool { $_[0] ? JSON::PP::true : JSON::PP::false }

my @events;
my ($count, $failed, $bailed) = (0, 0, 0);
my $plan_end; # stays undef unless a `1..N` plan line is seen

while (my $line = <STDIN>) {
if ($line =~ /^TAP version \d+/) {
# version is not consumed by transform-results.pl
}
elsif ($line =~ /^(\d+)\.\.(\d+)/) {
$plan_end = 0 + $2; # reported via the `complete` event, not as its own event
}
elsif ($line =~ /^Bail out!\s*(.*?)\s*$/) {
$bailed = 1;
push @events, [ 'bailout', $1 ];
}
elsif ($line =~ /^(not )?ok\b[ \t]*[0-9]*(.*)$/) {
my $is_not = $1;
$failed++ if $is_not;
$count++;
my $name = $2 // '';
$name =~ s/^\s*-\s*//; # drop the "- " separator
$name =~ s/\s+#\s*(?:SKIP|TODO)\b.*$//i; # drop a trailing TAP directive
$name =~ s/\s+$//;
push @events, [ 'assert', { ok => jbool(!$is_not), name => $name } ];
}
elsif ($line =~ /^\s*#/) {
push @events, [ 'comment', $line ];
}
elsif ($line =~ /^\s*$/) {
# blank lines produce no event (matches the npm tap-parser)
}
else {
push @events, [ 'extra', $line ];
}
}

# Reproduce how tap-parser fills in the plan for the `complete` event:
# - real `1..N` plan -> end = N, skipAll = false
# - no plan and no tests run -> end = 0, skipAll = true ("no tests found")
# - tests ran but no plan -> end = null, skipAll = false (e.g. died mid-run)
my ($end, $skip_all);
if (defined $plan_end) { ($end, $skip_all) = ($plan_end, 0); }
elsif ($count == 0) { ($end, $skip_all) = (0, 1); }
else { ($end, $skip_all) = (undef, 0); }

my $ok =
$bailed ? 0
: $skip_all ? 1
: ($failed == 0 && (!defined $plan_end || $count == $plan_end)) ? 1
: 0;

# tap-parser injects this diagnostic when the test count doesn't match the plan
# (e.g. a test died before done_testing()), just before the `complete` event.
if (defined $plan_end ? $count != $plan_end : $count > 0) {
my $plan_str = defined $plan_end ? $plan_end : 'null';
push @events, [ 'comment', "# test count($count) != plan($plan_str)\n" ];
}

push @events, [
'complete',
{ count => $count, ok => jbool($ok), plan => { end => $end, skipAll => jbool($skip_all) } },
];

print JSON::PP->new->canonical->encode(\@events);