Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions lib/App/Yath/Command/abort.pm
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use Test2::Harness::Runner::State;
use Test2::Harness::Util::File::JSON();
use Test2::Harness::Util::Queue();

use Test2::Harness::Util qw/open_file/;
use Test2::Harness::Util qw/open_file sanitize_filename/;

use parent 'App::Yath::Command::status';
use Test2::Harness::Util::HashBase;
Expand Down Expand Up @@ -52,7 +52,7 @@ sub run {
my $running = $state->running_tasks;
for my $task (values %$running) {
my $pid = $self->get_job_pid($task->{run_id}, $task->{job_id}) // next;;
my $file = $task->{rel_file};
my $file = sanitize_filename($task->{rel_file});
print "Killing test $pid - $file...\n";
kill('INT', $pid);
}
Expand Down
6 changes: 4 additions & 2 deletions lib/App/Yath/Command/failed.pm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ our $VERSION = '1.000164';
use Test2::Util::Table qw/table/;
use Test2::Harness::Util::File::JSONL;

use Test2::Harness::Util qw/sanitize_filename/;

use parent 'App::Yath::Command';
use Test2::Harness::Util::HashBase qw{<log_file};

Expand Down Expand Up @@ -78,10 +80,10 @@ sub run {
my $subtests = join "\n" => grep { !$seen{$_}++ } sort @{$data->{subtests} // []};

if ($settings->display->brief) {
print $ends->[-1]->{rel_file}, "\n" if $ends->[-1]->{fail};
print sanitize_filename($ends->[-1]->{rel_file}), "\n" if $ends->[-1]->{fail};
}
else {
push @$rows => [$job_id, scalar(@$ends), $ends->[-1]->{rel_file}, $subtests, $ends->[-1]->{fail} ? "NO" : "YES"];
push @$rows => [$job_id, scalar(@$ends), sanitize_filename($ends->[-1]->{rel_file}), $subtests, $ends->[-1]->{fail} ? "NO" : "YES"];
}
}

Expand Down
7 changes: 4 additions & 3 deletions lib/App/Yath/Command/status.pm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ our $VERSION = '1.000164';

use Term::Table();
use File::Spec();
use Test2::Harness::Util qw/sanitize_filename/;

use Test2::Harness::Runner::State;
use Test2::Harness::Util::File::JSON();
Expand Down Expand Up @@ -73,7 +74,7 @@ sub run {
next;
}

my @rows = map {[$_->{job_id}, $_->{is_try} // $_->{job_try} // 0, $_->{rel_file}, join(', ' => @{$_->{conflicts} // []})]} @tasks;
my @rows = map {[$_->{job_id}, $_->{is_try} // $_->{job_try} // 0, sanitize_filename($_->{rel_file}), join(', ' => @{$_->{conflicts} // []})]} @tasks;
my $run_table = Term::Table->new(
collapse => 1,
header => [qw/uuid try test conflicts/],
Expand Down Expand Up @@ -117,7 +118,7 @@ sub run {
for my $file (keys %{$reload_status->{$stage}}) {
next if $seen{$file}++;
my $data = $reload_status->{$stage}->{$file} or next;
print "\n==== SOURCE FILE: $file ====\n";
print "\n==== SOURCE FILE: " . sanitize_filename($file) . " ====\n";
print $data->{error} if $data->{error};
print $_ for @{$data->{warnings} // []};
}
Expand All @@ -128,7 +129,7 @@ sub run {
print "\n**** Running tests: ****\n";
my $running = $state->running_tasks;
my $running_tasks = [values %$running];
my @rows = map {[$self->get_job_pid($_->{run_id}, $_->{job_id}) // 'N/A', $_->{job_id}, $_->{is_try} // $_->{job_try} // 0, $_->{rel_file}, join(', ' => @{$_->{conflicts} // []})]} @$running_tasks;
my @rows = map {[$self->get_job_pid($_->{run_id}, $_->{job_id}) // 'N/A', $_->{job_id}, $_->{is_try} // $_->{job_try} // 0, sanitize_filename($_->{rel_file}), join(', ' => @{$_->{conflicts} // []})]} @$running_tasks;
if (@rows) {
my $run_table = Term::Table->new(
collapse => 1,
Expand Down
4 changes: 2 additions & 2 deletions lib/App/Yath/Command/test.pm
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use Test2::Harness::IPC;
use Test2::Harness::Runner::State;

use Test2::Harness::Util::JSON qw/encode_json decode_json JSON/;
use Test2::Harness::Util qw/mod2file open_file chmod_tmp/;
use Test2::Harness::Util qw/mod2file open_file chmod_tmp sanitize_filename/;
use Test2::Util::Table qw/table/;

use Test2::Harness::Util::Term qw/USE_ANSI_COLOR/;
Expand Down Expand Up @@ -450,7 +450,7 @@ sub stop {
for my $task (values %$running) {
next unless $task->{run_id} && $task->{run_id} eq $self->{+RUN_ID};
my $pid = $self->get_job_pid($task->{run_id}, $task->{job_id}) // next;
my $file = $task->{rel_file};
my $file = sanitize_filename($task->{rel_file});
print "Killing test $pid - $file...\n";
kill('INT', $pid);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/Test2/Formatter/Test2.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use warnings;
our $VERSION = '1.000164';

use Test2::Util::Term qw/term_size/;
use Test2::Harness::Util qw/hub_truth apply_encoding/;
use Test2::Harness::Util qw/hub_truth apply_encoding sanitize_filename/;
use Test2::Harness::Util::Term qw/USE_ANSI_COLOR/;
use Test2::Util qw/IS_WIN32 clone_io/;
use Time::HiRes qw/time/;
Expand Down Expand Up @@ -369,7 +369,7 @@ sub update_active_disp {

if ($f->{harness_job_launch}) {
my $job = $f->{harness_job};
$self->{+ACTIVE_FILES}->{File::Spec->abs2rel($job->{file})} = $job->{job_name} || $job->{job_id};
$self->{+ACTIVE_FILES}->{sanitize_filename(File::Spec->abs2rel($job->{file}))} = $job->{job_name} || $job->{job_id};
$should_show = 1;
$stats->{running}++;
$stats->{todo}--;
Expand All @@ -378,7 +378,7 @@ sub update_active_disp {

if ($f->{harness_job_end}) {
my $file = $f->{harness_job_end}->{file};
delete $self->{+ACTIVE_FILES}->{File::Spec->abs2rel($file)};
delete $self->{+ACTIVE_FILES}->{sanitize_filename(File::Spec->abs2rel($file))};
$should_show = 1;
$stats->{running}--;

Expand Down
3 changes: 2 additions & 1 deletion lib/Test2/Harness/Auditor.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use Time::HiRes qw/time/;

use Test2::Harness::Util::UUID qw/gen_uuid/;
use Test2::Harness::Util::JSON qw/decode_json/;
use Test2::Harness::Util qw/sanitize_filename/;

use Test2::Harness::Event;
use Test2::Harness::Auditor::Watcher;
Expand Down Expand Up @@ -94,7 +95,7 @@ sub finish {
my $final_data = {pass => 1};

while (my ($job_id, $watchers) = each %{$self->{+WATCHERS}}) {
my $file = File::Spec->abs2rel($self->{+QUEUED}->{$job_id}->{file});
my $file = sanitize_filename(File::Spec->abs2rel($self->{+QUEUED}->{$job_id}->{file}));

if (@$watchers) {
push @{$final_data->{failed}} => [$job_id, $file, $watchers->[-1]->failed_subtest_tree] if $watchers->[-1]->fail;
Expand Down
6 changes: 3 additions & 3 deletions lib/Test2/Harness/Renderer/Formatter.pm
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use File::Spec;

use Storable qw/dclone/;

use Test2::Harness::Util qw/fqmod mod2file/;
use Test2::Harness::Util qw/fqmod mod2file sanitize_filename/;
use Test2::Harness::Util::JSON qw/encode_pretty_json/;

BEGIN { require Test2::Harness::Renderer; our @ISA = ('Test2::Harness::Renderer') }
Expand Down Expand Up @@ -106,7 +106,7 @@ sub render_event {
tag => $f->{harness_job_launch}->{retry} ? 'RETRY' : 'LAUNCH',
debug => 0,
important => 1,
details => File::Spec->abs2rel($job->{file}),
details => sanitize_filename(File::Spec->abs2rel($job->{file})),
};
}

Expand Down Expand Up @@ -136,7 +136,7 @@ sub render_event {
}

if ($self->{+SHOW_JOB_END}) {
my $name = File::Spec->abs2rel($file);
my $name = sanitize_filename(File::Spec->abs2rel($file));
$name .= " - $skip" if $skip;

my $tag = 'PASSED';
Expand Down
17 changes: 17 additions & 0 deletions lib/Test2/Harness/Util.pm
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,25 @@ our @EXPORT_OK = qw{

looks_like_uuid
is_same_file

sanitize_filename
};

sub sanitize_filename {
my ($name) = @_;
return $name unless defined $name;

# Replace ANSI escape sequences (CSI and OSC) with empty string
$name =~ s/\e\[[0-9;]*[A-Za-z]//g; # CSI sequences: ESC [ ... letter
$name =~ s/\e\][^\a\e]*(?:\a|\e\\)//g; # OSC sequences: ESC ] ... BEL/ST

# Replace remaining control characters (0x00-0x1F, 0x7F) with their
# caret notation, e.g. \x01 => ^A, \x1B => ^[, \x7F => ^?
$name =~ s/([\x00-\x1f\x7f])/'^' . chr(ord($1) ^ 0x40)/ge;

return $name;
}

sub is_same_file {
my ($file1, $file2) = @_;

Expand Down
25 changes: 25 additions & 0 deletions t/unit/Test2/Harness/Util.t
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,29 @@ ok(is_same_file("$tmp/foo", "$tmp/foo2"), "hard link");
ok(is_same_file("$tmp/foo", "$tmp/foo3"), "soft link");
ok(!is_same_file("$tmp/foo", "$tmp/bar"), "Different files");

subtest sanitize_filename => sub {
# Normal filename unchanged
is(sanitize_filename('t/foo/bar.t'), 't/foo/bar.t', "Normal filename unchanged");

# undef passes through
is(sanitize_filename(undef), undef, "undef passes through");

# ANSI CSI sequences stripped (e.g. ESC[0m, ESC[1;31m, ESC[H)
is(sanitize_filename("t/\e[1mBoo\e[0m.t"), 't/Boo.t', "CSI bold/reset stripped");
is(sanitize_filename("t/\e[H\e[2J.t"), 't/.t', "CSI cursor home + clear stripped");
is(sanitize_filename("t/\e[1;31mred\e[0m.t"), 't/red.t', "CSI with params stripped");

# OSC sequences stripped (ESC ] ... BEL or ESC ] ... ST)
is(sanitize_filename("t/\e]0;evil title\a.t"), 't/.t', "OSC with BEL stripped");
is(sanitize_filename("t/\e]0;evil title\e\\.t"), 't/.t', "OSC with ST stripped");

# Remaining control characters become caret notation
is(sanitize_filename("t/foo\x01bar.t"), 't/foo^Abar.t', "SOH becomes ^A");
is(sanitize_filename("t/foo\x7fbar.t"), 't/foo^?bar.t', "DEL becomes ^?");
is(sanitize_filename("t/foo\tbar.t"), 't/foo^Ibar.t', "Tab becomes ^I");

# Combined: ANSI stripped first, then control chars escaped
is(sanitize_filename("t/\e[0J\x01.t"), 't/^A.t', "CSI stripped then ctrl escaped");
};

done_testing;
Loading