Skip to content
Draft
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
55 changes: 35 additions & 20 deletions lib/Test/MockFile.pm
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ our $VERSION = '0.038';

our %files_being_mocked;

# Reverse lookup: stringified filehandle => absolute path.
# Turns _fh_to_file from O(n) iteration to O(1) hash lookup.
our %_fh_to_path;

# Original Cwd functions saved before override
my $_original_cwd_abs_path;

Expand Down Expand Up @@ -1729,26 +1733,8 @@ sub _fh_to_file {

return unless defined $fh && length $fh;

# See if $fh is a file handle. It might be a path.
foreach my $path ( sort keys %files_being_mocked ) {
my $mock = $files_being_mocked{$path};

# Check file handles (multiple handles per file)
my $fhs = $mock->{'fhs'};
if ( $fhs && @{$fhs} ) {
@{$fhs} = grep { defined $_ } @{$fhs};
foreach my $mock_fh ( @{$fhs} ) {
return $path if "$mock_fh" eq "$fh";
}
}

# Check dir handle (stored as stringified handle)
if ( $mock->{'fh'} && $mock->{'fh'} eq "$fh" ) {
return $path;
}
}

return;
# O(1) reverse lookup instead of iterating all mocked files.
return $_fh_to_path{"$fh"};
}

sub _files_in_dir {
Expand Down Expand Up @@ -1951,6 +1937,14 @@ sub DESTROY {
delete $self->{'_autovivified_children'};
}

# Clean up reverse fh-to-path lookup entries for this mock's handles.
if ( my $fhs = $self->{'fhs'} ) {
for my $fh ( @{$fhs} ) {
delete $_fh_to_path{"$fh"} if defined $fh;
}
}
delete $_fh_to_path{ $self->{'fh'} } if $self->{'fh'};

# If the object survives into global destruction, the object which is
# the value of $files_being_mocked{$path} might destroy early.
# As a result, don't worry about the self == check just delete the key.
Expand Down Expand Up @@ -2733,6 +2727,12 @@ sub _io_file_mock_open {
$mock_file->{'fh'} = $fh;
Scalar::Util::weaken( $mock_file->{'fh'} ) if ref $fh;

# Register in reverse lookup for O(1) _fh_to_file resolution.
$_fh_to_path{"$fh"} = $abs_path;
if ( ref $fh && tied( *{$fh} ) ) {
tied( *{$fh} )->{'_fh_string'} = "$fh";
}

# Handle append/truncate modes
if ( $mode eq '>>' or $mode eq '+>>' ) {
$mock_file->{'contents'} //= '';
Expand Down Expand Up @@ -3079,6 +3079,12 @@ sub __open (*;$@) {
push @{ $mock_file->{'fhs'} }, $_[0];
Scalar::Util::weaken( $mock_file->{'fhs'}[-1] ) if ref $_[0];

# Register in reverse lookup for O(1) _fh_to_file resolution.
$_fh_to_path{"$_[0]"} = $mock_file->{'path'};
if ( ref $_[0] && tied( *{ $_[0] } ) ) {
tied( *{ $_[0] } )->{'_fh_string'} = "$_[0]";
}

# Fix tell based on open options.
# Track whether this open creates the file (transitions from non-existent).
my $was_new = !defined $mock_file->{'contents'};
Expand Down Expand Up @@ -3275,6 +3281,12 @@ sub __sysopen (*$$;$) {
push @{ $files_being_mocked{$abs_path}->{'fhs'} }, $_[0];
Scalar::Util::weaken( $files_being_mocked{$abs_path}->{'fhs'}[-1] ) if ref $_[0];

# Register in reverse lookup for O(1) _fh_to_file resolution.
$_fh_to_path{"$_[0]"} = $abs_path;
if ( ref $_[0] && tied( *{ $_[0] } ) ) {
tied( *{ $_[0] } )->{'_fh_string'} = "$_[0]";
}

# O_APPEND
if ( $sysopen_mode & O_APPEND ) {
seek $_[0], length $mock_file->{'contents'}, 0;
Expand Down Expand Up @@ -3352,6 +3364,9 @@ sub __opendir (*$) {
$mock_dir->{'obj'} = Test::MockFile::DirHandle->new( $abs_path, $mock_dir->contents() );
$mock_dir->{'fh'} = "$_[0]";

# Register in reverse lookup for O(1) _fh_to_file resolution.
$_fh_to_path{"$_[0]"} = $abs_path;

return 1;

}
Expand Down
7 changes: 7 additions & 0 deletions lib/Test/MockFile/FileHandle.pm
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ use Scalar::Util ();
our $VERSION = '0.038';

my $files_being_mocked;
my $fh_to_path;
{
no warnings 'once';
$files_being_mocked = \%Test::MockFile::files_being_mocked;
$fh_to_path = \%Test::MockFile::_fh_to_path;
}

=head1 NAME
Expand Down Expand Up @@ -481,6 +483,11 @@ sub CLOSE {
} @{ $mock->{'fhs'} };
}

# Remove from reverse fh-to-path lookup.
if ( my $fh_str = $self->{'_fh_string'} ) {
delete $fh_to_path->{$fh_str};
}

return 1;
}

Expand Down
Loading