diff --git a/lib/App/Yath2/Command/test.pm b/lib/App/Yath2/Command/test.pm index 14578da91..16078c635 100644 --- a/lib/App/Yath2/Command/test.pm +++ b/lib/App/Yath2/Command/test.pm @@ -12,11 +12,13 @@ use Object::HashBase qw{ tests->includes, already resolved), then the + # default lib/blib directories (on by default, disabled by --no-lib / + # --no-blib). Paths are absolutised relative to the current working + # directory so they remain valid regardless of the RunService's CWD. + # + # We PREPEND to any T2_HARNESS_INCLUDES value already set in the + # environment (e.g. /foo;/bar;/baz from nested_includes.t) so caller- + # injected paths survive but the tester's full @INC dump (added by + # App::Yath2::Tester for its own yath-subprocess setup) ends up after + # our ordered prefix. + { + my $cwd = Cwd::getcwd(); + my @new_inc; + + push @new_inc, App::Yath2->app_path; + + if (eval { $settings->can('check_group') && $settings->check_group('tests') }) { + my $ts = $settings->tests; + for my $path (@{$ts->includes // []}) { + push @new_inc, File::Spec->rel2abs($path, $cwd); + } + unless (defined($ts->lib) && !$ts->lib) { + push @new_inc, File::Spec->catdir($cwd, 'lib'); + } + unless (defined($ts->blib) && !$ts->blib) { + push @new_inc, File::Spec->catdir($cwd, 'blib', 'lib'); + push @new_inc, File::Spec->catdir($cwd, 'blib', 'arch'); + } + } + + my %seen; + my @all_inc; + for my $p (@new_inc) { + push @all_inc, $p unless $seen{$p}++; + } + if (my $existing = $ENV{T2_HARNESS_INCLUDES}) { + for my $p (grep { length && $_ ne '.' } split /;/, $existing) { + push @all_inc, $p unless $seen{$p}++; + } + } + $ENV{T2_HARNESS_INCLUDES} = join ';', @all_inc; + } + my $workdir = $settings->workspace->workdir; my $spawn = Test2::Harness2->spawn( diff --git a/lib/Test2/Harness2/RunService.pm b/lib/Test2/Harness2/RunService.pm index bbe1f15ab..f44c8d0b8 100644 --- a/lib/Test2/Harness2/RunService.pm +++ b/lib/Test2/Harness2/RunService.pm @@ -189,13 +189,23 @@ sub request_handler_launch_job { return {ok => 0, error => "'test_file' must be absolute"} unless $test_file_abs =~ m{^/}; + # Parse the test file to detect non-perl scripts (shell scripts, etc.) + # via their shebang line. We need this before building the launch command + # so we can decide whether to use -I flags or PERL5LIB. + require Test2::Harness2::TestFile; + my $test_file_spec = Test2::Harness2::TestFile->new(file => $test_file_abs); + $test_file_spec->scan; + # The harness's synthetic-skip / synthetic-fail paths hand us an # explicit launch command (perl -e '...'). Default to running the - # real test file when no override is present. When the launch - # payload's env carries T2_HARNESS_INCLUDES (forwarded by - # Test2::Harness2::_launch_job), turn it into -I flags so the - # child's @INC actually picks the paths up -- the env var alone - # is not enough since exec(perl ...) starts a fresh interpreter. + # real test file when no override is present. + # + # T2_HARNESS_INCLUDES (forwarded by Test2::Harness2::_launch_job and + # populated by Command::test from the user's -I/--lib/--blib options) + # carries the correct include paths for test children. We convert these + # to -I flags for perl scripts, or to PERL5LIB for non-perl scripts + # (shell wrappers that re-exec perl). + my %extra_env; if (!defined $launch_cmd) { my @extra_inc; if (my $payload_env = $payload->{env}) { @@ -203,7 +213,31 @@ sub request_handler_launch_job { @extra_inc = grep { length && $_ ne '.' } split /;/, $inc if defined $inc && length $inc; } - $launch_cmd = [$^X, (map { "-I$_" } @extra_inc), '-Ilib', $test_file_abs]; + + if ($test_file_spec->non_perl) { + # Non-perl scripts (e.g. shell wrappers that exec perl): pass + # include paths via PERL5LIB so the re-execed perl sees them. + # Do not use -I flags — they would be forwarded by perl to the + # shebang interpreter (e.g. bash), which does not understand them. + # Always include 'lib' so harness internals (e.g. Stream2 + # formatter) are reachable even when T2_HARNESS_INCLUDES is unset. + my $sep = $^O eq 'MSWin32' ? ';' : ':'; + my $new_perl5lib = join $sep, @extra_inc, 'lib'; + if (my $existing = $ENV{PERL5LIB}) { + $new_perl5lib .= "$sep$existing"; + } + $extra_env{PERL5LIB} = $new_perl5lib; + # Run the script directly; the OS kernel handles the shebang. + $launch_cmd = [$test_file_abs]; + } + else { + # Perl scripts: inject include paths as -I flags on the command + # line. This sets @INC before any module is loaded, which is + # required because exec() starts a fresh interpreter. + # Always include -Ilib so harness internals (e.g. Stream2 + # formatter) are reachable even when T2_HARNESS_INCLUDES is unset. + $launch_cmd = [$^X, (map { "-I$_" } @extra_inc), '-Ilib', $test_file_abs]; + } } # Default the payload-level log_file only if the caller wants one; @@ -218,8 +252,6 @@ sub request_handler_launch_job { # test_file path so the resulting .json includes the relative # and absolute test-file paths. Callers that supply their own # spec are left alone. - require Test2::Harness2::TestFile; - my $test_file_spec = Test2::Harness2::TestFile->new(file => $test_file_abs); my @logger_specs = map { _maybe_inject_test_file_spec($_, $test_file_spec) } @$payload_loggers; @@ -230,7 +262,7 @@ sub request_handler_launch_job { launch => $launch_cmd, new_pgroup => 1, parent_pids => [$$], - env_vars => {T2_FORMATTER => 'Stream2', %$env}, + env_vars => {T2_FORMATTER => 'Stream2', %$env, %extra_env}, logdir => $self->{+LOGDIR}, run_id => $run_id, job_id => $job_id, diff --git a/t/Yath/integration/concurrency.t b/t/Yath/integration/concurrency.t index 28a494e9d..327d02f9a 100644 --- a/t/Yath/integration/concurrency.t +++ b/t/Yath/integration/concurrency.t @@ -1,6 +1,6 @@ # HARNESS-CONFLICTS YATH use Test2::V0; -plan skip_all => "TODO: job concurrency scheduling not validated yet against current scheduler"; +plan skip_all => "TODO: test uses \$log->poll() (JSONL API) but log=>1 now returns a workdir; needs rewrite to use App::Yath2::LogArchive for job event ordering assertions"; __END__ use Test2::V0; diff --git a/t/Yath/integration/includes.t b/t/Yath/integration/includes.t index 9cdba6eb9..d1d17ae8f 100644 --- a/t/Yath/integration/includes.t +++ b/t/Yath/integration/includes.t @@ -1,8 +1,4 @@ # HARNESS-CONFLICTS YATH -use Test2::V0; -plan skip_all => "TODO: -I/--unsafe-inc include handling not aligned with current command::test"; -__END__ - use Test2::V0; use IPC::Cmd qw/can_run/; diff --git a/t/Yath/integration/includes/.yath.rc b/t/Yath/integration/includes/.yath.v2.rc similarity index 100% rename from t/Yath/integration/includes/.yath.rc rename to t/Yath/integration/includes/.yath.v2.rc diff --git a/t/Yath/integration/projects.t b/t/Yath/integration/projects.t index 3d233bbd4..7f227f470 100644 --- a/t/Yath/integration/projects.t +++ b/t/Yath/integration/projects.t @@ -1,6 +1,6 @@ # HARNESS-CONFLICTS YATH use Test2::V0; -plan skip_all => "TODO: projects command output not aligned with current renderer"; +plan skip_all => "TODO: App::Yath2::Command::projects needs run() override — inherits test's guard that requires explicit args, no CWD fallback"; __END__ use Test2::V0; diff --git a/t/Yath/integration/signals.t b/t/Yath/integration/signals.t index b059c5404..c4b0c8413 100644 --- a/t/Yath/integration/signals.t +++ b/t/Yath/integration/signals.t @@ -1,8 +1,4 @@ # HARNESS-CONFLICTS YATH -use Test2::V0; -plan skip_all => "TODO: signal handling under nested yath not aligned with current Streamer (AUTHOR_TESTING bypasses old gate)"; -__END__ - use Test2::V0; use Test2::Require::AuthorTesting; diff --git a/t/Yath/integration/smoke.t b/t/Yath/integration/smoke.t index 3db10035b..be6f77d13 100644 --- a/t/Yath/integration/smoke.t +++ b/t/Yath/integration/smoke.t @@ -1,6 +1,6 @@ # HARNESS-CONFLICTS YATH use Test2::V0; -plan skip_all => "TODO: plugin loading (-p+SmokePlugin) and -D dev-libs not aligned"; +plan skip_all => "TODO: test uses \$log->poll() (JSONL API) but log=>1 now returns a workdir; needs rewrite to use App::Yath2::LogArchive for event iteration"; __END__ use Test2::V0; diff --git a/t/Yath/integration/stamps.t b/t/Yath/integration/stamps.t index 3c6164634..1031f4243 100644 --- a/t/Yath/integration/stamps.t +++ b/t/Yath/integration/stamps.t @@ -1,6 +1,6 @@ # HARNESS-CONFLICTS YATH use Test2::V0; -plan skip_all => "TODO: plugin loading and event timestamp rendering not aligned"; +plan skip_all => "TODO: test uses \$log->poll() (JSONL API) but log=>1 now returns a workdir; needs rewrite to App::Yath2::LogArchive; also --no-plugins flag not yet implemented"; __END__ use Test2::V0;