From 8f731bc643847f9efe127778c686b9f9543ec88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Tue, 3 Mar 2026 13:40:17 -0700 Subject: [PATCH] fix: PRINT/PRINTF return false when weakened data ref is gone PRINT and PRINTF always returned 1 (success) even when _write_bytes() failed because the mock was destroyed and the weakened data ref became undef. WRITE already handled this correctly by checking the return value. Apply the same pattern to PRINT and PRINTF for consistency. Also skips _update_write_times() when no bytes were written, matching WRITE's existing behavior. Co-Authored-By: Claude Opus 4.6 --- lib/Test/MockFile/FileHandle.pm | 12 ++++++------ t/filehandle_weakref.t | 29 +++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/Test/MockFile/FileHandle.pm b/lib/Test/MockFile/FileHandle.pm index 62f5a57..3e130a0 100644 --- a/lib/Test/MockFile/FileHandle.pm +++ b/lib/Test/MockFile/FileHandle.pm @@ -156,10 +156,10 @@ sub PRINT { # at the C level after PRINT returns), so this only covers explicit usage. $output .= $\ if defined $\; - $self->_write_bytes($output); - $self->_update_write_times(); + my $bytes = $self->_write_bytes($output); + $self->_update_write_times() if $bytes; - return 1; + return $bytes ? 1 : 0; } =head2 PRINTF @@ -183,10 +183,10 @@ sub PRINTF { return; } - $self->_write_bytes( sprintf( $format, @_ ) ); - $self->_update_write_times(); + my $bytes = $self->_write_bytes( sprintf( $format, @_ ) ); + $self->_update_write_times() if $bytes; - return 1; + return $bytes ? 1 : 0; } =head2 WRITE diff --git a/t/filehandle_weakref.t b/t/filehandle_weakref.t index 6e62c91..04da368 100644 --- a/t/filehandle_weakref.t +++ b/t/filehandle_weakref.t @@ -66,15 +66,32 @@ subtest 'sysread after mock destruction returns 0' => sub { close $fh; }; -subtest 'print after mock destruction fails gracefully' => sub { +subtest 'print after mock destruction returns false' => sub { my $fh = _open_then_destroy_mock('/fake/print', '', '>'); - my $ret; - my $warn_msg; - local $SIG{__WARN__} = sub { $warn_msg = shift }; - - my $ok = lives { $ret = print {$fh} "hello" }; + my ($ret, $errno); + my $ok = lives { + $ret = print {$fh} "hello"; + $errno = $! + 0; + }; ok($ok, "print does not crash after mock destruction"); + ok(!$ret, "print returns false when mock is destroyed"); + is($errno, EBADF, "errno is EBADF after print on destroyed mock"); + + close $fh; +}; + +subtest 'printf after mock destruction returns false' => sub { + my $fh = _open_then_destroy_mock('/fake/printf', '', '>'); + + my ($ret, $errno); + my $ok = lives { + $ret = printf {$fh} "%s", "hello"; + $errno = $! + 0; + }; + ok($ok, "printf does not crash after mock destruction"); + ok(!$ret, "printf returns false when mock is destroyed"); + is($errno, EBADF, "errno is EBADF after printf on destroyed mock"); close $fh; };