Selkie.git | t/fixtures/ | error-log-roundtrip.raku
use NativeCall;
use Selkie::App::Internal::ErrorLog;
use Selkie::App::Internal::ErrorLogPlatform;
class ErrorLogProbe does Selkie::App::Internal::ErrorLog {
method install(Str:D $path --> Nil) { self!install-error-log($path) }
method uninstall(--> Nil) { self!uninstall-error-log }
method replace-process-stderr(IO::Handle:D $handle --> Nil) {
self!replace-process-stderr($handle)
}
method restore-process-stderr(--> Nil) { self!restore-process-stderr }
}
sub posix-write(int32, Str, size_t --> ssize_t)
is native is symbol("write") { * }
sub windows-write-file(Pointer, Str, uint32, uint32 is rw, Pointer --> int32)
is native("kernel32") is symbol("WriteFile") { * }
sub native-stderr(Str:D $message --> Nil) {
my $written = $message.encode.bytes;
if $*DISTRO.is-win {
my uint32 $count = 0;
my $ok = windows-write-file(
windows-get-std-handle(WINDOWS-STD-ERROR-HANDLE),
$message,
$written,
$count,
Pointer,
);
die "native stderr write failed" unless $ok && $count == $written;
return;
}
$written = posix-write(2, $message, $written);
die "native stderr write failed" unless $written == $message.encode.bytes;
}
my $scratch = @*ARGS[0].IO;
my Str $stage = "setup";
my $stage-path = $scratch.add("fixture-stage.txt");
sub enter-stage(Str:D $next --> Nil) {
$stage = $next;
try $stage-path.spurt("$stage\n");
}
enter-stage($stage);
CATCH {
default {
my $message = "stage=$stage\n{.message}\n{.?backtrace.?full // ''}";
try $scratch.add("fixture-failure.txt").spurt($message);
exit 1;
}
}
my $log-a = $scratch.add("nested path").add("first error.log");
my $log-b = $scratch.add("second error.log");
my $blocked = $scratch.add("not-a-file");
$blocked.mkdir;
my $probe = ErrorLogProbe.new;
my $original-windows-handle = IS-WINDOWS
?? windows-get-std-handle(WINDOWS-STD-ERROR-HANDLE)
!! Pointer;
# Exercise the PROCESS::<$ERR> swap on every host. The production error-log
# path uses it only on Windows, so a POSIX-only suite would otherwise miss
# container-aliasing regressions in restoration.
my $process-log = $scratch.add("process error.log");
my $process-handle = open $process-log, :w;
enter-stage("process stderr replacement");
$probe.replace-process-stderr($process-handle);
enter-stage("process stderr redirected say");
$*ERR.say("process-redirected-marker");
enter-stage("process stderr redirected flush");
$*ERR.flush;
enter-stage("process stderr restore");
$probe.restore-process-stderr;
enter-stage("process stderr replacement handle close");
$process-handle.close;
enter-stage("process stderr restored say");
$*ERR.say("process-restored-marker");
enter-stage("process stderr restored flush");
$*ERR.flush;
sub same-handle($left, $right --> Bool:D) {
$left.defined && $right.defined && $left.Int == $right.Int;
}
enter-stage("first install");
$probe.install($log-a.Str);
if IS-WINDOWS {
my $installed = windows-get-std-handle(WINDOWS-STD-ERROR-HANDLE);
die "Win32 stderr handle was not replaced"
if same-handle($installed, $original-windows-handle);
}
enter-stage("first redirected writes");
$*ERR.say("raku-marker-a");
$*ERR.flush;
native-stderr("native-marker-a\n");
enter-stage("first uninstall");
$probe.uninstall;
if IS-WINDOWS {
die "Win32 stderr handle was not restored"
unless same-handle(
windows-get-std-handle(WINDOWS-STD-ERROR-HANDLE),
$original-windows-handle,
);
}
enter-stage("restored stderr write");
$*ERR.say("restored-marker");
$*ERR.flush;
native-stderr("native-restored-marker\n");
enter-stage("blank-path install");
$probe.install(" ");
$*ERR.say("blank-path-marker");
$*ERR.flush;
$probe.uninstall;
enter-stage("unopenable-path install");
$probe.install($blocked.Str);
$*ERR.say("unopenable-path-marker");
$*ERR.flush;
$probe.uninstall;
enter-stage("second install");
$probe.install($log-b.Str);
$*ERR.say("raku-marker-b");
$*ERR.flush;
native-stderr("native-marker-b\n");
enter-stage("second uninstall");
$probe.uninstall;
$probe.uninstall;
enter-stage("complete");
say "child-ok";