diff --git a/lib/Test/MockFile.pm b/lib/Test/MockFile.pm index 17aed44..018e4d2 100644 --- a/lib/Test/MockFile.pm +++ b/lib/Test/MockFile.pm @@ -1422,7 +1422,6 @@ When creating mocked files or directories, we default their stats to: 'mtime' => $now, # stat[9] 'ctime' => $now, # stat[10] 'blksize' => 4096, # stat[11] - 'fileno' => undef, # fileno() } ); You'll notice that mode, size, and blocks have been left out of this. @@ -1464,7 +1463,6 @@ sub _default_mock_attrs { 'mtime' => $now, # stat[9] 'ctime' => $now, # stat[10] 'blksize' => 4096, # stat[11] - 'fileno' => undef, # fileno() 'tty' => 0, 'readlink' => '', 'path' => undef, @@ -1535,8 +1533,6 @@ sub new { $self->{'nlink'} = ( $self->{'mode'} & S_IFMT ) == S_IFDIR ? 2 : 1; } - $self->{'fileno'} //= _unused_fileno(); - $files_being_mocked{$path} = $self; Scalar::Util::weaken( $files_being_mocked{$path} ); @@ -2281,10 +2277,6 @@ sub stat { ); } -sub _unused_fileno { - return 900; # TODO -} - =head2 readlink Optional Arg: $readlink diff --git a/lib/Test/MockFile/FileHandle.pm b/lib/Test/MockFile/FileHandle.pm index e5b1f69..23d4dbc 100644 --- a/lib/Test/MockFile/FileHandle.pm +++ b/lib/Test/MockFile/FileHandle.pm @@ -20,6 +20,8 @@ my $files_being_mocked; $files_being_mocked = \%Test::MockFile::files_being_mocked; } +my $_next_fileno = 900; + =head1 NAME Test::MockFile::FileHandle - Provides a class for L to @@ -75,6 +77,7 @@ sub TIEHANDLE { 'read' => $mode =~ m/r/ ? 1 : 0, 'write' => $mode =~ m/w/ ? 1 : 0, 'append' => $mode =~ m/a/ ? 1 : 0, + 'fileno' => $_next_fileno++, }, $class; # This ref count can't hold the object from getting released. @@ -578,19 +581,15 @@ sub OPEN { =head2 FILENO -B: Open a ticket in -L if you need -this feature. - -No L -exists on this method. +Returns the unique file descriptor number assigned to this handle when +it was opened. Each C call gets its own fileno, mirroring real +kernel behavior. =cut sub FILENO { my ($self) = @_; - die 'fileno is purposefully unsupported'; + return $self->{'fileno'}; } =head2 SEEK diff --git a/t/fileno.t b/t/fileno.t index 6f58c2c..3d245fd 100644 --- a/t/fileno.t +++ b/t/fileno.t @@ -14,13 +14,37 @@ my $file = Test::MockFile->file( '/foo', '' ); my $fh; ok( lives( sub { open $fh, '<', '/foo' } ), 'Opened file' ); -like( - dies( sub { fileno $fh } ), - qr/\Qfileno is purposefully unsupported\E/xms, - 'Refuse to support fileno', -); +# fileno should return a value for mocked file handles +ok( defined fileno($fh), 'fileno returns a defined value for a mocked fh' ); -ok( lives( sub { close $fh } ), 'Opened file' ); +ok( lives( sub { close $fh } ), 'Closed file' ); + +# Each open() should get a unique fileno, even on the same mocked file. +# This mirrors real behavior where fileno is a property of open(), not the file. +subtest 'unique fileno per open filehandle' => sub { + my $mock_a = Test::MockFile->file( '/unique_a', 'aaa' ); + my $mock_b = Test::MockFile->file( '/unique_b', 'bbb' ); + + open my $fh_a, '<', '/unique_a' or die; + open my $fh_b, '<', '/unique_b' or die; + open my $fh_a2, '<', '/unique_a' or die; # second open of same file + + my @filenos = map { fileno($_) } ( $fh_a, $fh_b, $fh_a2 ); + + # All three should be defined + ok( defined $filenos[0], 'fileno for fh_a is defined' ); + ok( defined $filenos[1], 'fileno for fh_b is defined' ); + ok( defined $filenos[2], 'fileno for fh_a2 is defined' ); + + # All three should be distinct (even the two opens of /unique_a) + isnt( $filenos[0], $filenos[1], 'fh_a and fh_b have different filenos' ); + isnt( $filenos[0], $filenos[2], 'fh_a and fh_a2 have different filenos' ); + isnt( $filenos[1], $filenos[2], 'fh_b and fh_a2 have different filenos' ); + + close $fh_a; + close $fh_b; + close $fh_a2; +}; done_testing(); exit;