use
constant
SUPPORTED_SYSOPEN_MODES
=> O_RDONLY | O_WRONLY | O_RDWR | O_APPEND | O_TRUNC | O_EXCL | O_CREAT | O_NOFOLLOW;
use
constant
BROKEN_SYMLINK
=>
bless
{},
"A::BROKEN::SYMLINK"
;
use
constant
CIRCULAR_SYMLINK
=>
bless
{},
"A::CIRCULAR::SYMLINK"
;
use
Carp
qw(carp confess croak)
;
BEGIN {
$Carp::Internal
{ (__PACKAGE__) }++;
$Carp::Internal
{
'Overload::FileCheck'
}++;
}
use
Errno
qw/EPERM ENOENT ELOOP EEXIST EISDIR ENOTDIR EINVAL/
;
use
constant
FOLLOW_LINK_MAX_DEPTH
=> 10;
Hide Show 11 lines of Pod
our
$VERSION
=
'0.029'
;
our
%files_being_mocked
;
Hide Show 99 lines of Pod
use
constant
STRICT_MODE_DEFAULT
=> STRICT_MODE_ENABLED | STRICT_MODE_UNSET;
our
%authorized_strict_mode_packages
;
our
$STRICT_MODE_STATUS
;
BEGIN {
%authorized_strict_mode_packages
= (
'DynaLoader'
=> 1,
'lib'
=> 1,
);
$STRICT_MODE_STATUS
= STRICT_MODE_DEFAULT;
}
sub
_upgrade_barewords {
my
@args
=
@_
;
my
$caller
=
caller
(1);
unshift
@args
, 0;
Internals::SvREADONLY(
$_
[0] )
or
return
@args
;
my
$handle
;
{
no
strict
'refs'
;
my
$caller_pkg
=
caller
(1);
$handle
= *{
"$caller_pkg\::$args[1]"
};
}
ref
\
$handle
eq
'GLOB'
or
return
@args
;
$args
[0] = 1;
$args
[1] =
$handle
;
return
@args
;
}
Hide Show 13 lines of Pod
my
$_file_arg_post
;
sub
file_arg_position_for_command {
my
(
$command
) =
@_
;
$_file_arg_post
//= {
'chmod'
=> 2,
'chown'
=> 2,
'lstat'
=> 0,
'mkdir'
=> 0,
'open'
=> 2,
'opendir'
=> 1,
'readlink'
=> 0,
'rmdir'
=> 0,
'stat'
=> 0,
'sysopen'
=> 1,
'unlink'
=> 0,
};
croak(
"Unknown strict mode violation for $command"
)
unless
defined
$command
&&
defined
$_file_arg_post
->{
$command
};
return
$_file_arg_post
->{
$command
};
}
sub
_strict_mode_violation {
my
(
$command
,
$at_under_ref
) =
@_
;
return
unless
$STRICT_MODE_STATUS
& STRICT_MODE_ENABLED;
my
@stack
;
foreach
my
$stack_level
( 1 .. 100 ) {
@stack
=
caller
(
$stack_level
);
last
if
!
scalar
@stack
;
last
if
!
defined
$stack
[0];
next
if
(
$stack
[0] eq __PACKAGE__ );
next
if
(
$stack
[0] eq
'Overload::FileCheck'
);
return
if
$authorized_strict_mode_packages
{
$stack
[0] };
last
;
}
my
$file_arg
= file_arg_position_for_command(
$command
);
if
(
$command
eq
'open'
and
scalar
@$at_under_ref
!= 3 ) {
$file_arg
= 1
if
scalar
@$at_under_ref
== 2;
}
my
$filename
=
scalar
@$at_under_ref
<=
$file_arg
?
'<not specified>'
:
$at_under_ref
->[
$file_arg
];
return
if
$filename
=~ m/^\*?(?:main::)?[<*&+>]
*STD
(?:OUT|IN|ERR)$/;
confess(
"Use of $command to access unmocked file or directory '$filename' in strict mode at $stack[1] line $stack[2]"
);
}
sub
import
{
my
(
$class
,
@args
) =
@_
;
my
$strict_mode
= (
grep
{
$_
eq
'nostrict'
}
@args
) ? STRICT_MODE_DISABLED : STRICT_MODE_ENABLED;
if
(
defined
$STRICT_MODE_STATUS
&& !(
$STRICT_MODE_STATUS
& STRICT_MODE_UNSET )
&&
$STRICT_MODE_STATUS
!=
$strict_mode
) {
die
q[Test::MockFile is imported multiple times with different strict modes (not currently supported) ]
.
$class
;
}
$STRICT_MODE_STATUS
=
$strict_mode
;
return
;
}
Hide Show 24 lines of Pod
sub
file {
my
(
$class
,
$file
,
$contents
,
@stats
) =
@_
;
(
defined
$file
&&
length
$file
) or confess(
"No file provided to instantiate $class"
);
_get_file_object(
$file
) and confess(
"It looks like $file is already being mocked. We don't support double mocking yet."
);
my
$path
= _abs_path_to_file(
$file
);
_validate_path(
$_
)
for
$file
,
$path
;
if
(
@stats
> 1 ) {
confess(
sprintf
'Unkownn arguments (%s) passed to file() as stats'
,
join
', '
,
@stats
);
}
!
defined
$contents
&&
@stats
and confess(
"You cannot set stats for non-existent file '$path'"
);
my
%stats
;
if
(
@stats
) {
ref
$stats
[0] eq
'HASH'
or confess(
'->file( FILE_NAME, FILE_CONTENT, { STAT_INFORMATION } )'
);
%stats
= %{
$stats
[0] };
}
my
$perms
= S_IFPERMS & (
defined
$stats
{
'mode'
} ?
int
(
$stats
{
'mode'
} ) : 0666 );
$stats
{
'mode'
} = (
$perms
^
umask
) | S_IFREG;
(
my
$dirname
=
$path
) =~ s{ / [^/]+ $ }{}xms;
if
(
defined
$contents
&&
$files_being_mocked
{
$dirname
} ) {
$files_being_mocked
{
$dirname
}{
'has_content'
} = 1;
}
return
$class
->new(
{
'path'
=>
$path
,
'contents'
=>
$contents
,
%stats
}
);
}
Hide Show 13 lines of Pod
sub
file_from_disk {
my
(
$class
,
$file
,
$file_on_disk
,
@stats
) =
@_
;
my
$fh
;
local
$!;
if
( !CORE::
open
(
$fh
,
'<'
,
$file_on_disk
) ) {
$file_on_disk
//=
'<no file specified>'
;
confess(
"Sorry, I cannot read from $file_on_disk to mock $file. It doesn't appear to be present ($!)"
);
}
local
$/;
my
$contents
= <
$fh
>;
close
$fh
;
return
__PACKAGE__->file(
$file
,
$contents
,
@stats
);
}
Hide Show 19 lines of Pod
sub
symlink
{
my
(
$class
,
$readlink
,
$file
) =
@_
;
(
defined
$file
&&
length
$file
) or confess(
"No file provided to instantiate $class"
);
( !
defined
$readlink
||
length
$readlink
) or confess(
"No file provided for $file to point to in $class"
);
_get_file_object(
$file
) and confess(
"It looks like $file is already being mocked. We don't support double mocking yet."
);
(
my
$dirname
=
$file
) =~ s{ / [^/]+ $ }{}xms;
if
(
$files_being_mocked
{
$dirname
} ) {
$files_being_mocked
{
$dirname
}{
'has_content'
} = 1;
}
return
$class
->new(
{
'path'
=>
$file
,
'contents'
=>
undef
,
'readlink'
=>
$readlink
,
'mode'
=> 07777 | S_IFLNK,
}
);
}
sub
_validate_path {
my
$path
=
shift
;
if
(
$path
=~ m{ ( ^ | / ) \.{2} ( / | $ ) }xms ) {
confess(
'Relative paths are not supported'
);
}
return
;
}
Hide Show 57 lines of Pod
sub
dir {
my
(
$class
,
$dirname
) =
@_
;
(
defined
$dirname
&&
length
$dirname
) or confess(
"No directory name provided to instantiate $class"
);
_get_file_object(
$dirname
)
and confess(
"It looks like $dirname is already being mocked. We don't support double mocking yet."
);
my
$path
= _abs_path_to_file(
$dirname
);
_validate_path(
$_
)
for
$dirname
,
$path
;
$path
ne
'/'
and
$path
=~ s{[/\\]$}{}xmsg;
@_
> 2
and confess(
"You cannot set stats for nonexistent dir '$path'"
);
my
$perms
= S_IFPERMS & 0777;
my
%stats
= (
'mode'
=> (
$perms
^
umask
) | S_IFDIR );
my
$has_content
=
grep
m{^\Q
$path
/\E}xms,
%files_being_mocked
;
return
$class
->new(
{
'path'
=>
$path
,
'has_content'
=>
$has_content
,
%stats
}
);
}
Hide Show 39 lines of Pod
sub
new {
my
$class
=
shift
@_
;
my
%opts
;
if
(
scalar
@_
== 1 &&
ref
$_
[0] ) {
%opts
= %{
$_
[0] };
}
elsif
(
scalar
@_
% 2 ) {
confess(
sprintf
(
"Unknown args (%d) passed to new"
,
scalar
@_
) );
}
else
{
%opts
=
@_
;
}
my
$path
=
$opts
{
'path'
} or confess(
"Mock file created without a path (filename or dirname)!"
);
if
(
$path
!~ m{^/} ) {
$path
=
$opts
{
'path'
} = _abs_path_to_file(
$path
);
}
my
$now
=
time
;
my
$self
=
bless
{
'dev'
=> 0,
'inode'
=> 0,
'mode'
=> 0,
'nlink'
=> 0,
'uid'
=>
int
$>,
'gid'
=>
int
$),
'rdev'
=> 0,
'atime'
=>
$now
,
'mtime'
=>
$now
,
'ctime'
=>
$now
,
'blksize'
=> 4096,
'fileno'
=>
undef
,
'tty'
=> 0,
'readlink'
=>
''
,
'path'
=>
undef
,
'contents'
=>
undef
,
'has_content'
=>
undef
,
},
$class
;
foreach
my
$key
(
keys
%opts
) {
next
unless
exists
$self
->{
$key
};
$self
->{
$key
} =
$opts
{
$key
};
}
$self
->{
'fileno'
} //= _unused_fileno();
$files_being_mocked
{
$path
} =
$self
;
Scalar::Util::weaken(
$files_being_mocked
{
$path
} );
return
$self
;
}
sub
_mock_stat {
my
(
$type
,
$file_or_fh
) =
@_
;
$type
or confess(
"_mock_stat called without a stat type"
);
my
$follow_link
=
$type
eq
'stat'
? 1
:
$type
eq
'lstat'
? 0
: confess(
"Unexpected stat type '$type'"
);
if
(
scalar
@_
!= 2 ) {
_real_file_access_hook(
$type
, [
$file_or_fh
] );
return
FALLBACK_TO_REAL_OP();
}
if
( !
defined
$file_or_fh
|| !
length
$file_or_fh
) {
_real_file_access_hook(
$type
, [
$file_or_fh
] );
return
FALLBACK_TO_REAL_OP();
}
my
$file
= _find_file_or_fh(
$file_or_fh
,
$follow_link
);
return
[]
if
defined
$file
&&
defined
BROKEN_SYMLINK &&
$file
eq BROKEN_SYMLINK;
return
[]
if
defined
$file
&&
defined
CIRCULAR_SYMLINK &&
$file
eq CIRCULAR_SYMLINK;
if
( !
defined
$file
or !
length
$file
) {
_real_file_access_hook(
$type
, [
$file_or_fh
] );
return
FALLBACK_TO_REAL_OP();
}
my
$file_data
= _get_file_object(
$file
);
if
( !
$file_data
) {
_real_file_access_hook(
$type
, [
$file_or_fh
] )
unless
ref
$file_or_fh
;
return
FALLBACK_TO_REAL_OP();
}
return
[]
if
!
$file_data
->is_link && !
defined
$file_data
->contents();
return
[
$file_data
->
stat
];
}
sub
_get_file_object {
my
(
$file_path
) =
@_
;
my
$file
= _find_file_or_fh(
$file_path
) or
return
;
return
$files_being_mocked
{
$file
};
}
sub
_find_file_or_fh {
my
(
$file_or_fh
,
$follow_link
,
$depth
) =
@_
;
my
$absolute_path_to_file
= _fh_to_file(
$file_or_fh
) // _abs_path_to_file(
$file_or_fh
) //
''
;
$absolute_path_to_file
ne
'/'
and
$absolute_path_to_file
=~ s{[/\\]$}{}xmsg;
my
$mock_object
=
$files_being_mocked
{
$absolute_path_to_file
};
return
BROKEN_SYMLINK
if
$depth
and !
$mock_object
;
return
$absolute_path_to_file
unless
$mock_object
&&
$mock_object
->is_link;
return
$absolute_path_to_file
unless
$follow_link
;
$depth
++;
if
(
$depth
> FOLLOW_LINK_MAX_DEPTH ) {
$! = ELOOP;
return
CIRCULAR_SYMLINK;
}
return
_find_file_or_fh(
$mock_object
->
readlink
, 1,
$depth
);
}
sub
_fh_to_file {
my
(
$fh
) =
@_
;
return
unless
defined
$fh
&&
length
$fh
;
foreach
my
$path
(
sort
keys
%files_being_mocked
) {
my
$mock_fh
=
$files_being_mocked
{
$path
}->{
'fh'
};
next
unless
$mock_fh
;
next
unless
"$mock_fh"
eq
"$fh"
;
return
$path
;
}
return
;
}
sub
_files_in_dir {
my
$dirname
=
shift
;
my
@files_in_dir
=
@files_being_mocked
{
grep
m{^\Q
$dirname
/\E},
keys
%files_being_mocked
};
return
@files_in_dir
;
}
sub
_abs_path_to_file {
my
(
$path
) =
shift
;
defined
$path
or
return
;
my
$match
= 1;
while
(
$match
) {
$match
= 0;
$match
= 1
if
$path
=~ s{//+}{/}xmsg;
$match
= 1
if
$path
=~ s{/\.$}{/};
$match
= 1
if
$path
=~ s{(?:[^/]+)/\.\.(/|$)}{$1};
$match
= 1
if
$path
=~ s{/$}{};
}
return
q[/]
if
$path
eq
q[/..]
;
return
$path
if
$path
=~ m{^/};
my
$cwd
= Cwd::getcwd();
return
$cwd
if
$path
eq
'.'
;
return
Cwd::getcwd() .
"/$path"
;
}
sub
DESTROY {
my
(
$self
) =
@_
;
ref
$self
or
return
;
my
$path
=
$self
->{
'path'
};
defined
$path
or
return
;
if
(
defined
$files_being_mocked
{
$path
} ) {
$self
==
$files_being_mocked
{
$path
} or confess(
"Tried to destroy object for $path ($self) but something else is mocking it?"
);
}
delete
$files_being_mocked
{
$path
};
}
Hide Show 14 lines of Pod
sub
contents {
my
(
$self
,
$new_contents
) =
@_
;
$self
or confess;
$self
->is_link
and confess(
"checking or setting contents on a symlink is not supported"
);
if
(
$self
->is_dir() ) {
$new_contents
and confess(
'To change the contents of the dir, you must work on its files'
);
$self
->{
'has_content'
}
or
return
;
my
$dirname
=
$self
->path();
my
@existing_files
=
sort
map
{
(
my
$basename
=
$_
->path() ) =~ s{^\Q
$dirname
/\E}{}xms;
$basename
=~ s{^( [^/]+ ) / .*}{$1}xms;
defined
$_
->{
'contents'
} ||
$_
->is_link() ||
$_
->is_dir() ? (
$basename
) : ();
} _files_in_dir(
$dirname
);
my
%uniq
;
$uniq
{
$_
}++
for
@existing_files
;
return
[
'.'
,
'..'
,
sort
keys
%uniq
];
}
if
(
$self
->is_file() ) {
if
(
defined
$new_contents
) {
ref
$new_contents
and confess(
'File contents must be a simple string'
);
$self
->{
'contents'
} =
$_
[1];
}
return
$self
->{
'contents'
};
}
confess(
'This seems to be neither a file nor a dir - what is it?'
);
}
Hide Show 6 lines of Pod
sub
filename {
carp(
'filename() is deprecated, use path() instead'
);
goto
&path
;
}
Hide Show 7 lines of Pod
sub
path {
my
(
$self
) =
@_
;
$self
or confess(
"path is a method"
);
return
$self
->{
'path'
};
}
Hide Show 6 lines of Pod
sub
unlink
{
my
(
$self
) =
@_
;
$self
or confess(
"unlink is a method"
);
if
( !
$self
->
exists
) {
$! = ENOENT;
return
0;
}
if
(
$self
->is_dir ) {
if
( $] < 5.019 && ( $^O eq
'darwin'
or $^O =~ m/bsd/i ) ) {
$! = EPERM;
}
else
{
$! = EISDIR;
}
return
0;
}
if
(
$self
->is_link ) {
$self
->{
'readlink'
} =
undef
;
}
else
{
$self
->{
'has_content'
} =
undef
;
$self
->{
'contents'
} =
undef
;
}
return
1;
}
Hide Show 12 lines of Pod
sub
touch {
my
(
$self
,
$now
) =
@_
;
$self
or confess(
"touch is a method"
);
$now
//=
time
;
$self
->is_file or confess(
"touch only supports files"
);
my
$pre_size
=
$self
->size();
if
( !
defined
$pre_size
) {
$self
->contents(
''
);
}
$self
->mtime(
$now
);
$self
->ctime(
$now
);
$self
->atime(
$now
);
return
1;
}
Hide Show 6 lines of Pod
sub
stat
{
my
$self
=
shift
;
return
(
$self
->{
'dev'
},
$self
->{
'inode'
},
$self
->{
'mode'
},
$self
->{
'nlink'
},
$self
->{
'uid'
},
$self
->{
'gid'
},
$self
->{
'rdev'
},
$self
->size,
$self
->{
'atime'
},
$self
->{
'mtime'
},
$self
->{
'ctime'
},
$self
->{
'blksize'
},
$self
->blocks,
);
}
sub
_unused_fileno {
return
900;
}
Hide Show 9 lines of Pod
sub
readlink
{
my
(
$self
,
$readlink
) =
@_
;
$self
->is_link or confess(
"readlink is only supported for symlinks"
);
if
(
scalar
@_
== 2 ) {
if
(
defined
$readlink
&&
ref
$readlink
) {
confess(
"readlink can only be set to simple strings."
);
}
$self
->{
'readlink'
} =
$readlink
;
}
return
$self
->{
'readlink'
};
}
Hide Show 6 lines of Pod
sub
is_link {
my
(
$self
) =
@_
;
return
(
defined
$self
->{
'readlink'
} &&
length
$self
->{
'readlink'
} &&
$self
->{
'mode'
} & S_IFLNK ) ? 1 : 0;
}
Hide Show 6 lines of Pod
sub
is_dir {
my
(
$self
) =
@_
;
return
( (
$self
->{
'mode'
} & S_IFMT ) == S_IFDIR ) ? 1 : 0;
}
Hide Show 6 lines of Pod
sub
is_file {
my
(
$self
) =
@_
;
return
( (
$self
->{
'mode'
} & S_IFMT ) == S_IFREG ) ? 1 : 0;
}
Hide Show 6 lines of Pod
sub
size {
my
(
$self
) =
@_
;
return
1
if
$self
->is_link;
if
( $] < 5.012 ) {
return
undef
unless
$self
->
exists
;
}
return
length
$self
->contents;
}
Hide Show 6 lines of Pod
sub
exists
{
my
(
$self
) =
@_
;
$self
->is_link()
and
return
defined
$self
->{
'readlink'
} ? 1 : 0;
$self
->is_file()
and
return
defined
$self
->{
'contents'
} ? 1 : 0;
$self
->is_dir()
and
return
$self
->{
'has_content'
} ? 1 : 0;
return
0;
}
Hide Show 6 lines of Pod
sub
blocks {
my
(
$self
) =
@_
;
my
$blocks
=
int
(
$self
->size /
abs
(
$self
->{
'blksize'
} ) + 1 );
if
(
int
(
$blocks
) >
$blocks
) {
$blocks
=
int
(
$blocks
) + 1;
}
return
$blocks
;
}
Hide Show 10 lines of Pod
sub
chmod
{
my
(
$self
,
$mode
) =
@_
;
$mode
= (
int
(
$mode
) & S_IFPERMS ) ^
umask
;
$self
->{
'mode'
} = (
$self
->{
'mode'
} & S_IFMT ) +
$mode
;
return
$mode
;
}
Hide Show 6 lines of Pod
sub
permissions {
my
(
$self
) =
@_
;
return
int
(
$self
->{
'mode'
} ) & S_IFPERMS;
}
Hide Show 9 lines of Pod
sub
mtime {
my
(
$self
,
$time
) =
@_
;
if
(
scalar
@_
== 2 &&
defined
$time
&&
$time
=~ m/^[0-9]+$/ ) {
$self
->{
'mtime'
} =
$time
;
}
return
$self
->{
'mtime'
};
}
Hide Show 9 lines of Pod
sub
ctime {
my
(
$self
,
$time
) =
@_
;
if
(
@_
== 2 &&
defined
$time
&&
$time
=~ m/^[0-9]+$/ ) {
$self
->{
'ctime'
} =
$time
;
}
return
$self
->{
'ctime'
};
}
Hide Show 9 lines of Pod
sub
atime {
my
(
$self
,
$time
) =
@_
;
if
(
@_
== 2 &&
defined
$time
&&
$time
=~ m/^[0-9]+$/ ) {
$self
->{
'atime'
} =
$time
;
}
return
$self
->{
'atime'
};
}
Hide Show 20 lines of Pod
my
@_public_access_hooks
;
my
@_internal_access_hooks
= ( \
&_strict_mode_violation
);
sub
add_file_access_hook {
my
(
$code_ref
) =
@_
;
(
$code_ref
&&
ref
$code_ref
eq
'CODE'
) or confess(
"add_file_access_hook needs to be passed a code reference."
);
push
@_public_access_hooks
,
$code_ref
;
return
1;
}
Hide Show 7 lines of Pod
sub
clear_file_access_hooks {
@_public_access_hooks
= ();
return
1;
}
sub
_real_file_access_hook {
my
(
$access_type
,
$at_under_ref
) =
@_
;
foreach
my
$code
(
@_internal_access_hooks
,
@_public_access_hooks
) {
$code
->(
$access_type
,
$at_under_ref
);
}
return
1;
}
Hide Show 60 lines of Pod
sub
_goto_is_available {
return
0
if
$] < 5.015;
return
1
if
$] < 5.021;
return
1
if
$] > 5.027;
return
0;
}
BEGIN {
my
$_handle_glob
=
sub
{
my
$spec
=
shift
;
my
@patterns
=
split
/\s+/xms,
$spec
;
my
@mocked_files
=
grep
$files_being_mocked
{
$_
}->
exists
(),
keys
%files_being_mocked
;
@mocked_files
=
map
/^(.+)\/[^\/]+$/xms ? (
$_
, $1 ) : (
$_
),
@mocked_files
;
@mocked_files
=
sort
@mocked_files
;
my
@results
=
map
Text::Glob::match_glob(
$_
,
@mocked_files
),
@patterns
;
return
@results
;
};
*CORE::GLOBAL::glob
= !$^V || $^V lt 5.18.0
?
sub
{
pop
;
goto
&$_handle_glob
;
}
:
sub
(_;) {
goto
&$_handle_glob
; };
*CORE::GLOBAL::open
=
sub
(*;$@) {
my
$likely_bareword
;
my
$arg0
;
if
(
defined
$_
[0] && !
ref
$_
[0] ) {
$arg0
=
$_
[0];
(
$likely_bareword
,
@_
) = _upgrade_barewords(
@_
);
}
my
(
undef
,
$mode
,
$file
) =
@_
;
my
$arg_count
=
@_
;
if
(
$arg_count
== 2 ) {
if
(
$_
[1] =~ /^ ( >> | [+]?> | [+]?< ) (.+) $/xms ) {
$mode
= $1;
$file
= $2;
}
elsif
(
$_
[1] =~ /^[\.\/\\\w\d\-]+$/xms ) {
$mode
=
'<'
;
$file
=
$_
[1];
}
elsif
(
$_
[1] =~ /^\|/xms ) {
$mode
=
'|-'
;
$file
=
$_
[1];
}
elsif
(
$_
[1] =~ /\|$/xms ) {
$mode
=
'-|'
;
$file
=
$_
[1];
}
else
{
die
"Unsupported two-way open: $_[1]\n"
;
}
$arg_count
++;
}
if
(
$arg_count
!= 3 ) {
_real_file_access_hook(
"open"
, \
@_
);
goto
\
&CORE::open
if
_goto_is_available();
if
(
@_
== 1 ) {
return
CORE::
open
(
$_
[0] );
}
elsif
(
@_
== 2 ) {
return
CORE::
open
(
$_
[0],
$_
[1] );
}
elsif
(
@_
>= 3 ) {
return
CORE::
open
(
$_
[0],
$_
[1],
@_
[ 2 ..
$#_
] );
}
}
if
(
ref
$file
&&
ref
$file
eq
'SCALAR'
) {
goto
\
&CORE::open
if
_goto_is_available();
return
CORE::
open
(
$_
[0],
$mode
,
$file
);
}
my
$abs_path
= _find_file_or_fh(
$file
, 1 );
confess()
if
!
$abs_path
&&
$mode
ne
'|-'
&&
$mode
ne
'-|'
;
confess()
if
$abs_path
eq BROKEN_SYMLINK;
my
$mock_file
= _get_file_object(
$abs_path
);
$mode
=~ s/(:.+$)//;
my
$encoding_mode
= $1;
if
( (
$mode
eq
'|-'
||
$mode
eq
'-|'
)
or !
grep
{
$_
eq
$mode
}
qw/> < >> +< +> +>>/
or !
defined
$mock_file
) {
_real_file_access_hook(
"open"
, \
@_
);
goto
\
&CORE::open
if
_goto_is_available();
if
(
@_
== 1 ) {
return
CORE::
open
(
$_
[0] );
}
elsif
(
@_
== 2 ) {
return
CORE::
open
(
$_
[0],
$_
[1] );
}
elsif
(
@_
>= 3 ) {
return
CORE::
open
(
$_
[0],
$_
[1],
@_
[ 2 ..
$#_
] );
}
}
if
( !
defined
$mock_file
->contents() &&
grep
{
$mode
eq
$_
}
qw/< +</
) {
$! = ENOENT;
return
;
}
my
$rw
=
''
;
$rw
.=
'r'
if
grep
{
$_
eq
$mode
}
qw/+< +> +>> </
;
$rw
.=
'w'
if
grep
{
$_
eq
$mode
}
qw/+< +> +>> > >>/
;
my
$filefh
= IO::File->new;
tie
*{
$filefh
},
'Test::MockFile::FileHandle'
,
$abs_path
,
$rw
;
if
(
$likely_bareword
) {
my
$caller
=
caller
();
no
strict;
*{
"${caller}::$arg0"
} =
$filefh
;
@_
= (
$filefh
,
$_
[1] ?
@_
[ 1 ..
$#_
] : () );
}
else
{
$_
[0] =
$filefh
;
}
$mock_file
->{
'fh'
} =
$_
[0];
Scalar::Util::weaken(
$mock_file
->{
'fh'
} )
if
ref
$_
[0];
if
(
$mode
eq
'>>'
or
$mode
eq
'+>>'
) {
$mock_file
->{
'contents'
} //=
''
;
seek
$_
[0],
length
(
$mock_file
->{
'contents'
} ), 0;
}
elsif
(
$mode
eq
'>'
or
$mode
eq
'+>'
) {
$mock_file
->{
'contents'
} =
''
;
}
return
1;
};
*CORE::GLOBAL::sysopen
=
sub
(*$$;$) {
my
$mock_file
= _get_file_object(
$_
[1] );
if
( !
$mock_file
) {
_real_file_access_hook(
"sysopen"
, \
@_
);
goto
\
&CORE::sysopen
if
_goto_is_available();
return
CORE::
sysopen
(
$_
[0],
$_
[1],
@_
[ 2 ..
$#_
] );
}
my
$sysopen_mode
=
$_
[2];
if
( (
$sysopen_mode
& SUPPORTED_SYSOPEN_MODES ) !=
$sysopen_mode
) {
confess(
sprintf
(
"Sorry, can't open %s with 0x%x permissions. Some of your permissions are not yet supported by %s"
,
$_
[1],
$sysopen_mode
, __PACKAGE__ ) );
}
if
( (
$sysopen_mode
& O_NOFOLLOW ) == O_NOFOLLOW &&
$mock_file
->is_link ) {
$! = 40;
return
undef
;
}
if
(
$sysopen_mode
& O_EXCL &&
$sysopen_mode
& O_CREAT &&
defined
$mock_file
->{
'contents'
} ) {
$! = EEXIST;
return
;
}
if
(
$sysopen_mode
& O_CREAT && !
defined
$mock_file
->{
'contents'
} ) {
$mock_file
->{
'contents'
} =
''
;
}
if
(
$sysopen_mode
& O_TRUNC &&
defined
$mock_file
->{
'contents'
} ) {
$mock_file
->{
'contents'
} =
''
;
}
my
$rd_wr_mode
=
$sysopen_mode
& 3;
my
$rw
=
$rd_wr_mode
== O_RDONLY ?
'r'
:
$rd_wr_mode
== O_WRONLY ?
'w'
:
$rd_wr_mode
== O_RDWR ?
'rw'
: confess(
"Unexpected sysopen read/write mode ($rd_wr_mode)"
);
if
( !
defined
$mock_file
->{
'contents'
} &&
$rd_wr_mode
== O_RDONLY ) {
$! = ENOENT;
return
;
}
my
$abs_path
=
$mock_file
->{
'path'
};
$_
[0] = IO::File->new;
tie
*{
$_
[0] },
'Test::MockFile::FileHandle'
,
$abs_path
,
$rw
;
$files_being_mocked
{
$abs_path
}->{
'fh'
} =
$_
[0];
Scalar::Util::weaken(
$files_being_mocked
{
$abs_path
}->{
'fh'
} )
if
ref
$_
[0];
if
(
$sysopen_mode
& O_TRUNC ) {
$mock_file
->{
'contents'
} =
''
;
}
if
(
$sysopen_mode
& O_APPEND ) {
seek
$_
[0],
length
$mock_file
->{
'contents'
}, 0;
}
return
1;
};
*CORE::GLOBAL::opendir
=
sub
(*$) {
(
undef
,
@_
) = _upgrade_barewords(
@_
)
if
defined
$_
[0] && !
ref
$_
[9];
my
$mock_dir
= _get_file_object(
$_
[1] );
if
(
scalar
@_
!= 2 or !
defined
$_
[1] ) {
_real_file_access_hook(
"opendir"
, \
@_
);
goto
\
&CORE::opendir
if
_goto_is_available();
return
CORE::
opendir
(
$_
[0],
@_
[ 1 ..
$#_
] );
}
if
( !
$mock_dir
) {
_real_file_access_hook(
"opendir"
, \
@_
);
goto
\
&CORE::opendir
if
_goto_is_available();
return
CORE::
opendir
(
$_
[0],
$_
[1] );
}
if
( !
defined
$mock_dir
->contents ) {
$! = ENOENT;
return
undef
;
}
if
( !(
$mock_dir
->{
'mode'
} & S_IFDIR ) ) {
$! = ENOTDIR;
return
undef
;
}
if
( !
defined
$_
[0] ) {
$_
[0] = Symbol::gensym;
}
elsif
(
ref
$_
[0] ) {
no
strict
'refs'
;
*{
$_
[0] } = Symbol::geniosym;
}
my
$abs_path
=
$mock_dir
->{
'path'
};
$mock_dir
->{
'obj'
} = Test::MockFile::DirHandle->new(
$abs_path
,
$mock_dir
->contents() );
$mock_dir
->{
'fh'
} =
"$_[0]"
;
return
1;
};
*CORE::GLOBAL::readdir
=
sub
(*) {
(
undef
,
@_
) = _upgrade_barewords(
@_
)
if
defined
$_
[0] && !
ref
$_
[9];
my
$mocked_dir
= _get_file_object(
$_
[0] );
if
( !
$mocked_dir
) {
_real_file_access_hook(
'readdir'
, \
@_
);
goto
\
&CORE::readdir
if
_goto_is_available();
return
CORE::
readdir
(
$_
[0] );
}
my
$obj
=
$mocked_dir
->{
'obj'
};
if
( !
$obj
) {
confess(
"Read on a closed handle"
);
}
if
( !
defined
$obj
->{
'files_in_readdir'
} ) {
confess(
"Did a readdir on an empty dir. This shouldn't have been able to have been opened!"
);
}
if
( !
defined
$obj
->{
'tell'
} ) {
confess(
"readdir called on a closed dirhandle"
);
}
return
undef
if
$obj
->{
'tell'
} > $
if
(
wantarray
) {
my
@return
;
foreach
my
$pos
(
$obj
->{
'tell'
} .. $
push
@return
,
$obj
->{
'files_in_readdir'
}->[
$pos
];
}
$obj
->{
'tell'
} = $
return
@return
;
}
return
$obj
->{
'files_in_readdir'
}->[
$obj
->{
'tell'
}++ ];
};
*CORE::GLOBAL::telldir
=
sub
(*) {
(
undef
,
@_
) = _upgrade_barewords(
@_
)
if
defined
$_
[0] && !
ref
$_
[9];
my
(
$fh
) =
@_
;
my
$mocked_dir
= _get_file_object(
$fh
);
if
( !
$mocked_dir
|| !
$mocked_dir
->{
'obj'
} ) {
_real_file_access_hook(
'telldir'
, \
@_
);
goto
\
&CORE::telldir
if
_goto_is_available();
return
CORE::
telldir
(
$fh
);
}
my
$obj
=
$mocked_dir
->{
'obj'
};
if
( !
defined
$obj
->{
'files_in_readdir'
} ) {
confess(
"Did a telldir on an empty dir. This shouldn't have been able to have been opened!"
);
}
if
( !
defined
$obj
->{
'tell'
} ) {
confess(
"telldir called on a closed dirhandle"
);
}
return
$obj
->{
'tell'
};
};
*CORE::GLOBAL::rewinddir
=
sub
(*) {
(
undef
,
@_
) = _upgrade_barewords(
@_
)
if
defined
$_
[0] && !
ref
$_
[9];
my
(
$fh
) =
@_
;
my
$mocked_dir
= _get_file_object(
$fh
);
if
( !
$mocked_dir
|| !
$mocked_dir
->{
'obj'
} ) {
_real_file_access_hook(
'rewinddir'
, \
@_
);
goto
\
&CORE::rewinddir
if
_goto_is_available();
return
CORE::
rewinddir
(
$_
[0] );
}
my
$obj
=
$mocked_dir
->{
'obj'
};
if
( !
defined
$obj
->{
'files_in_readdir'
} ) {
confess(
"Did a rewinddir on an empty dir. This shouldn't have been able to have been opened!"
);
}
if
( !
defined
$obj
->{
'tell'
} ) {
confess(
"rewinddir called on a closed dirhandle"
);
}
$obj
->{
'tell'
} = 0;
return
1;
};
*CORE::GLOBAL::seekdir
=
sub
(*$) {
(
undef
,
@_
) = _upgrade_barewords(
@_
)
if
defined
$_
[0] && !
ref
$_
[9];
my
(
$fh
,
$goto
) =
@_
;
my
$mocked_dir
= _get_file_object(
$fh
);
if
( !
$mocked_dir
|| !
$mocked_dir
->{
'obj'
} ) {
_real_file_access_hook(
'seekdir'
, \
@_
);
goto
\
&CORE::seekdir
if
_goto_is_available();
return
CORE::
seekdir
(
$fh
,
$goto
);
}
my
$obj
=
$mocked_dir
->{
'obj'
};
if
( !
defined
$obj
->{
'files_in_readdir'
} ) {
confess(
"Did a seekdir on an empty dir. This shouldn't have been able to have been opened!"
);
}
if
( !
defined
$obj
->{
'tell'
} ) {
confess(
"seekdir called on a closed dirhandle"
);
}
return
$obj
->{
'tell'
} =
$goto
;
};
*CORE::GLOBAL::closedir
=
sub
(*) {
(
undef
,
@_
) = _upgrade_barewords(
@_
)
if
defined
$_
[0] && !
ref
$_
[9];
my
(
$fh
) =
@_
;
my
$mocked_dir
= _get_file_object(
$fh
);
if
( !
$mocked_dir
|| !
$mocked_dir
->{
'obj'
} ) {
_real_file_access_hook(
'closedir'
, \
@_
);
goto
\
&CORE::closedir
if
_goto_is_available();
return
CORE::
closedir
(
$fh
);
}
delete
$mocked_dir
->{
'obj'
};
delete
$mocked_dir
->{
'fh'
};
return
1;
};
*CORE::GLOBAL::unlink
=
sub
(@) {
my
@files_to_unlink
=
@_
;
my
$files_deleted
= 0;
foreach
my
$file
(
@files_to_unlink
) {
my
$mock
= _get_file_object(
$file
);
if
( !
$mock
) {
_real_file_access_hook(
"unlink"
, [
$file
] );
$files_deleted
+= CORE::
unlink
(
$file
);
}
else
{
$files_deleted
+=
$mock
->
unlink
;
}
}
return
$files_deleted
;
};
*CORE::GLOBAL::readlink
=
sub
(_) {
my
(
$file
) =
@_
;
if
( !
defined
$file
) {
carp(
'Use of uninitialized value in readlink'
);
if
( $^O eq
'freebsd'
) {
$! = EINVAL;
}
else
{
$! = ENOENT;
}
return
;
}
my
$mock_object
= _get_file_object(
$file
);
if
( !
$mock_object
) {
_real_file_access_hook(
'readlink'
, \
@_
);
goto
\
&CORE::readlink
if
_goto_is_available();
return
CORE::
readlink
(
$file
);
}
if
( !
$mock_object
->is_link ) {
$! = EINVAL;
return
;
}
return
$mock_object
->
readlink
;
};
*CORE::GLOBAL::mkdir
=
sub
(_;$) {
my
(
$file
,
$perms
) =
@_
;
$perms
= (
$perms
// 0777 ) & S_IFPERMS;
if
( !
defined
$file
) {
carp(
"Use of uninitialized value in mkdir"
);
$! = ENOENT;
return
0;
}
my
$mock
= _get_file_object(
$file
);
if
( !
$mock
) {
_real_file_access_hook(
'mkdir'
, \
@_
);
goto
\
&CORE::mkdir
if
_goto_is_available();
return
CORE::
mkdir
(
@_
);
}
if
(
$mock
->
exists
) {
$! = EEXIST;
return
0;
}
$mock
->{
'mode'
} = (
$perms
^
umask
) | S_IFDIR;
delete
$mock
->{
'readlink'
};
$mock
->{
'has_content'
} = 1;
return
1;
};
*CORE::GLOBAL::rmdir
=
sub
(_) {
my
(
$file
) =
@_
;
if
( !
defined
$file
) {
carp(
'Use of uninitialized value in rmdir'
);
return
0;
}
my
$mock
= _get_file_object(
$file
);
if
( !
$mock
) {
_real_file_access_hook(
'rmdir'
, \
@_
);
goto
\
&CORE::rmdir
if
_goto_is_available();
return
CORE::
rmdir
(
$file
);
}
if
(
$mock
->
exists
) {
if
(
$mock
->is_file ) {
$! = ENOTDIR;
return
0;
}
if
(
$mock
->is_link ) {
$! = ENOTDIR;
return
0;
}
}
if
( !
$mock
->
exists
) {
$! = ENOENT;
return
0;
}
if
( _files_in_dir(
$file
) ) {
$! = 39;
return
0;
}
$mock
->{
'has_content'
} =
undef
;
return
1;
};
*CORE::GLOBAL::chown
=
sub
(@) {
my
(
$uid
,
$gid
,
@files
) =
@_
;
$^O eq
'MSWin32'
and
return
0;
@files
or
return
0;
my
%mocked_files
=
map
+(
$_
=> _get_file_object(
$_
) ),
@files
;
my
@unmocked_files
=
grep
!
$mocked_files
{
$_
},
@files
;
my
@mocked_files
=
map
ref
$_
?
$_
->{
'path'
} : (),
values
%mocked_files
;
if
(
@mocked_files
&&
@mocked_files
!=
@files
) {
confess(
sprintf
'You called chown() on a mix of mocked (%s) and unmocked files (%s) '
.
' - this is very likely a bug on your side'
,
(
join
', '
,
@mocked_files
),
(
join
', '
,
@unmocked_files
),
);
}
$uid
== -1 and
$uid
= $>;
$gid
== -1 and
$gid
= $);
my
$is_root
= $> == 0 || $) =~ /( ^ | \s ) 0 ( \s | $)/xms;
my
$is_in_group
=
grep
/(^ | \s ) \Q
$gid
\E ( \s | $ )/xms, $);
my
$set_error
;
my
$num_changed
= 0;
foreach
my
$file
(
@files
) {
my
$mock
=
$mocked_files
{
$file
};
if
( !
$mock
) {
_real_file_access_hook(
'chown'
, \
@_
);
goto
\
&CORE::chown
if
_goto_is_available();
return
CORE::
chown
(
@files
);
}
if
( !
$mock
->
exists
() ) {
$set_error
or $! = ENOENT;
next
;
}
if
( !
$is_root
) {
if
( $> !=
$uid
|| !
$is_in_group
) {
$set_error
or $! = EPERM;
last
;
}
}
$mock
->{
'uid'
} =
$uid
;
$mock
->{
'gid'
} =
$gid
;
$num_changed
++;
}
return
$num_changed
;
};
*CORE::GLOBAL::chmod
=
sub
(@) {
my
(
$mode
,
@files
) =
@_
;
@files
or
return
0;
{
no
warnings;
$mode
=~ /^[0-9]+/xms
or
warn
"Argument \"$mode\" isn't numeric in chmod"
;
$mode
=
int
$mode
;
}
my
%mocked_files
=
map
+(
$_
=> _get_file_object(
$_
) ),
@files
;
my
@unmocked_files
=
grep
!
$mocked_files
{
$_
},
@files
;
my
@mocked_files
=
map
ref
$_
?
$_
->{
'path'
} : (),
values
%mocked_files
;
if
(
@mocked_files
&&
@mocked_files
!=
@files
) {
confess(
sprintf
'You called chmod() on a mix of mocked (%s) and unmocked files (%s) '
.
' - this is very likely a bug on your side'
,
(
join
', '
,
@mocked_files
),
(
join
', '
,
@unmocked_files
),
);
}
my
$num_changed
= 0;
foreach
my
$file
(
@files
) {
my
$mock
=
$mocked_files
{
$file
};
if
( !
$mock
) {
_real_file_access_hook(
'chmod'
, \
@_
);
goto
\
&CORE::chmod
if
_goto_is_available();
return
CORE::
chmod
(
@files
);
}
if
( !
$mock
->
exists
() ) {
$! = ENOENT;
next
;
}
$mock
->{
'mode'
} = (
$mock
->{
'mode'
} & S_IFMT ) +
$mode
;
$num_changed
++;
}
return
$num_changed
;
};
}
Hide Show 108 lines of Pod
1;