From 0582c7a81f8e39488ed167af39bc18956a35f2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Sat, 25 Apr 2026 21:39:20 +0200 Subject: [PATCH 1/3] feat(options): add --no-plugins flag to disable auto-plugin discovery Adds a --no-plugins Bool option to App::Yath2::Options::Yath. When set, the auto-loading loop in App::Yath2::process_args skips any module that is an App::Yath2::Plugin subclass discovered through STATE_MODULES (environment scan, option files, etc.). Plugins explicitly requested via -p are still loaded normally since they are processed separately in run(). This is useful in tests like stamps.t where ambient plugin configuration must not interfere with the explicitly specified -pTestPlugin. Note: --log-dir / -L are already implemented in Options::Logging and wired in Command::test; log_dir.t already runs without a skip guard. Co-Authored-By: Claude Sonnet 4.6 --- lib/App/Yath2.pm | 2 ++ lib/App/Yath2/Options/Yath.pm | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/App/Yath2.pm b/lib/App/Yath2.pm index 1f9450bcf..fa94c7dc6 100644 --- a/lib/App/Yath2.pm +++ b/lib/App/Yath2.pm @@ -506,10 +506,12 @@ sub process_args { $self->{+ENV_VARS} = $self->{+STATE_ENV}; $self->{+OPTION_STATE} = $state; + my $no_plugins = eval { $settings->yath->no_plugins } // 0; for my $module (keys %{$self->{+STATE_MODULES}}) { for my $set (['yath', 'plugins', 'App::Yath2::Plugin'], ['renderer', 'classes', 'App::Yath2::Renderer'], ['resource', 'classes', 'App::Yath2::Resource']) { my ($group, $field, $type) = @$set; next unless $module->isa($type); + next if $no_plugins && $type eq 'App::Yath2::Plugin'; $settings->$group->option($field => {}) unless $settings->$group->$field; my $args = $settings->$group->$field->{$module} //= []; next unless $module->can('args_from_settings'); diff --git a/lib/App/Yath2/Options/Yath.pm b/lib/App/Yath2/Options/Yath.pm index 78ba25f63..730a62eb6 100644 --- a/lib/App/Yath2/Options/Yath.pm +++ b/lib/App/Yath2/Options/Yath.pm @@ -144,6 +144,13 @@ option_group {group => 'yath', category => 'Yath Options'} => sub { return $class => $args; }, ); + + option no_plugins => ( + type => 'Bool', + alt => ['no-plugins'], + default => 0, + description => 'Disable auto-loading of configured plugins. Explicitly requested plugins (-p) are still loaded. Useful in tests to prevent ambient plugin configuration from interfering.', + ); }; 1; From aa0ba6eb70ad672ae24d028bb374000c7dae56d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Sun, 26 Apr 2026 03:51:25 -0500 Subject: [PATCH 2/3] Fix --no-plugins option name collision in Yath option group The plugins Map option auto-generates --no-plugins as its negation form. Adding option no_plugins with alt no-plugins created a duplicate --no-plugins form, causing "Option form '--no-plugins' defined twice" at load time. Rename no_plugins to no_plugin_scan (dropping the conflicting alt) and update the corresponding accessor in App::Yath2. Co-Authored-By: Claude Sonnet 4.6 --- lib/App/Yath2.pm | 2 +- lib/App/Yath2/Options/Yath.pm | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/App/Yath2.pm b/lib/App/Yath2.pm index fa94c7dc6..317210505 100644 --- a/lib/App/Yath2.pm +++ b/lib/App/Yath2.pm @@ -506,7 +506,7 @@ sub process_args { $self->{+ENV_VARS} = $self->{+STATE_ENV}; $self->{+OPTION_STATE} = $state; - my $no_plugins = eval { $settings->yath->no_plugins } // 0; + my $no_plugins = eval { $settings->yath->no_plugin_scan } // 0; for my $module (keys %{$self->{+STATE_MODULES}}) { for my $set (['yath', 'plugins', 'App::Yath2::Plugin'], ['renderer', 'classes', 'App::Yath2::Renderer'], ['resource', 'classes', 'App::Yath2::Resource']) { my ($group, $field, $type) = @$set; diff --git a/lib/App/Yath2/Options/Yath.pm b/lib/App/Yath2/Options/Yath.pm index 730a62eb6..0b0cece53 100644 --- a/lib/App/Yath2/Options/Yath.pm +++ b/lib/App/Yath2/Options/Yath.pm @@ -145,9 +145,8 @@ option_group {group => 'yath', category => 'Yath Options'} => sub { }, ); - option no_plugins => ( + option no_plugin_scan => ( type => 'Bool', - alt => ['no-plugins'], default => 0, description => 'Disable auto-loading of configured plugins. Explicitly requested plugins (-p) are still loaded. Useful in tests to prevent ambient plugin configuration from interfering.', ); From adb884826ec0d76c90bc9ec0578f6a53055d9b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Sun, 26 Apr 2026 04:38:35 -0500 Subject: [PATCH 3/3] replay.t: remove skip, align with upstream 2.0, fix interleave flap PR #437 brought back a live replay.t on 2.0. Our branch still had plan skip_all, causing the GitHub merge commit to pick up upstream's unpatched version, which fails on Perl 5.24/5.28 due to event ordering differences between live and replay renderers. Update replay.t to match the upstream 2.0 version exactly, then apply PR #443's fix: sort ALL consecutive `job N`-prefixed lines (not just PASSED/FAILED status lines). Diag/Reason/Fail lines also carry `job N` and can interleave differently between live and replay, so the narrower sort was insufficient. Also adds the missing `Wrote archive:` strip and `job N` normalisation that upstream 2.0 introduced in PR #437. Co-Authored-By: Claude Sonnet 4.6 --- t/Yath/integration/replay.t | 38 ++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/t/Yath/integration/replay.t b/t/Yath/integration/replay.t index e0c96d150..4eb8f93b8 100644 --- a/t/Yath/integration/replay.t +++ b/t/Yath/integration/replay.t @@ -1,8 +1,5 @@ # HARNESS-CONFLICTS YATH -use Test2::V0; -plan skip_all => "TODO: replay command not yet aligned with current log/streamer format"; -__END__ - +# HARNESS-DURATION-SLOW use Test2::V0; use File::Temp qw/tempdir/; @@ -21,6 +18,7 @@ sub clean_output { my $out = shift; $out->{output} =~ s/^.*duration.*$//m; $out->{output} =~ s/^.*Wrote log file:.*$//m; + $out->{output} =~ s/^.*Wrote archive:.*$//m; $out->{output} =~ s/^.*Symlinked to:.*$//m; $out->{output} =~ s/^.*Linked log file:.*$//m; $out->{output} =~ s/^\s*Wall Time:.*seconds//m; @@ -35,6 +33,11 @@ sub clean_output { # Can remove this once the fixme is removed $out->{output} =~ s/^FIXME: publish should send log to server$//gm; + # Normalize display job numbers: parallel jobs complete in non-deterministic + # order so the renderer assigns job 1/2/... differently each run. Replace + # all "job N" sequences with a "job N" sentinel so both sides match. + $out->{output} =~ s/\bjob\s+\d+\b/job N/g; + my @lines; my $start; for my $line (split /\n/, $out->{output}) { @@ -45,7 +48,32 @@ sub clean_output { push @lines => $line; } - $out->{output} = join "\n" => @lines; + # Live (`yath test`) and replay emit the same per-job lines but in + # different orders, depending on how the failed job's diagnostics + # (FAIL / DIAG / REASON) interleave with another job's status line. + # Trying to group diag lines back to "their" status line is fragile + # because diag lines carry only `job N` (already normalised above) -- + # there is no filename to disambiguate. The robust fix is to + # canonicalise the order of every `job N`-prefixed line: collect + # consecutive runs of them, sort the run, flush. Non-job lines (the + # "The following jobs failed:" table, the "Yath Result Summary" block, + # etc.) pass through verbatim so the file's overall narrative stays + # intact. + my @normalized; + my @run; + for my $line (@lines) { + if ($line =~ /\bjob\s+N\b/) { + push @run => $line; + } + else { + push @normalized => sort @run if @run; + @run = (); + push @normalized => $line; + } + } + push @normalized => sort @run if @run; + + $out->{output} = join "\n" => @normalized; } my $out1 = yath(