tags:

views:

158

answers:

5

The CORE documentation has shown me how to merrily mock various built Perl functions. However, I'm not really sure how to replace '-d' &c. with my methods. So this is really just a question on how do i replace a function with a dash in it in CORE::GLOBAL.

A manual reference would be nice.

package Testing::MockDir;

use strict;
use warnings;
use Exporter();
use Symbol 'qualify_to_ref';

*import = \&Exporter::import;

our @EXPORT_OK = qw(SetMockDir UnsetMockDir);

our %EXPORT_TAGS = (
    'all' => \@EXPORT_OK,
);

my %path2List = ();
my %handle2List = ();

BEGIN {
    *CORE::GLOBAL::opendir = \&Testing::MockDir::opendir;
    *CORE::GLOBAL::readdir = \&Testing::MockDir::readdir;
    *CORE::GLOBAL::closedir = \&Testing::MockDir::closedir;

    ######################### the "-" is really the problem here
    *CORE::GLOBAL::-d = \&Testing::MockDir::mock_d; # This does not work <<<<<
}

sub mock_d ($) {
    die 'It worked';
}

sub SetMockDir {
    my ($path, @files) = @_;
    $path2List{$path} = [@files];
}

sub UnsetMockDir {
    my ($path) = @_;
    delete $path2List{$path};
}

sub opendir (*$) {
    my $handle = qualify_to_ref(shift, caller);
    my ($path) = @_;
    return CORE::opendir($handle, $path) unless defined $path2List{$path};
    $handle2List{$handle} = $path2List{$path};
    return 1;
}

sub readdir (*) {
    my $handle = qualify_to_ref(shift, caller);
    return CORE::readdir($handle) unless defined $handle2List{$handle};
    return shift @{$handle2List{$handle}} unless wantarray;

    my @files = @{$handle2List{$handle}};
    $handle2List{$handle} = [];
    return @files;
}

sub closedir (*) {
    my $handle = qualify_to_ref(shift, caller);
    return CORE::closedir($handle) unless defined $handle2List{$handle};
    delete $handle2List{$handle};
    return 1;
}

1;
+5  A: 

It may not be possible. The perlsub section on Overriding Built-in Functions is vague about which functions can be overridden. "Many" can, "some" can't, but aside from a handful of examples there's no definitive list.

Normally, I'd try this:

{
    no strict 'refs';
    *{'CORE::GLOBAL::-d'} = \&Testing::MockDir::mock_d;
}

which isn't a syntax error, but doesn't have the effect of overriding -d.

Michael Carman
If that's the case then I'll have to make the target code more testable. thanks
telesphore4
Overriding CORE::GLOBAL has to happen at BEGIN time or Perl won't see it. Its a performance hack. Anyhow, CORE::GLOBAL only works with things with prototypes. -d has no prototype.
Schwern
+9  A: 

CORE::GLOBAL doesn't work on things without prototypes. The only way I can think to do it is rewrite the opcode tree... which is not for the faint of heart. You could pull it off with a combination of B::Utils and B::Generate and a lot of experimentation.

Simplest thing to do would be to use File::Temp to make a temporary directory structure to your liking.

Schwern
+1  A: 

You could go the source filter route:

package Testing::MockDir;
use Filter::Simple;
FILTER {   s/\s+\-d (\S+)/ Testing::MockDir::filetest 'd',$1/g };
sub filetest {
  my ($test, $file) = @_;
  print "Mocking  -$test $file\n";
  return 1;
}

(This sample code isn't very robust. For example it won't translate -d$dir or -d "dirname with spaces", but you can beef it up until it meets the needs of your target code).

mobrule
That is an intriguing idea... source code transformations... very Lispish Thanks!
telesphore4
+2  A: 

The problem is that your app is dependent on hard-coded file specifications. You should parameterize the file specifications; then you don't have to mock anymore, you can just use Directory::Scratch or something.

jrockway
Agreed. However, I'm adding test code to a legacy app.
telesphore4
+2  A: 

Thanks all for your answers.

What I wound up doing is, on a per module/test target basis, I factored out the code with the "-d" into it into it's own function. Like so...

# Because I cannot mock -d directly
sub dirExists {
    return -d shift;
}

Then I can replace this function in the test module with like

my $doesDirExist = 1;
*MyModule::dirExists   = \&main::mock_dirExists;

sub mock_dirExists {
    return $doesDirExist;
}

It's pretty ugly but i didn't want to get hung up on this too long and it works well enuf for my purposes

telesphore4