Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Making open fail

by SilasTheMonk (Chaplain)
on Jul 02, 2010 at 23:35 UTC ( [id://847831]=perlquestion: print w/replies, xml ) Need Help??

SilasTheMonk has asked for the wisdom of the Perl Monks concerning the following question:

I have a module with some code a bit like this:
sub { .... my $string = get_string(); local *STDIN; open(STDIN, '<', \$string) or die "could not open $string"; binmode STDIN; .... }
I cannot find a way in a test script of making the open call fail. I have tried Test::MockObject and subs and liberal use of "BEGIN". The method appears to be loaded from IO::File.

Replies are listed 'Best First'.
Re: Making open fail
by ikegami (Patriarch) on Jul 03, 2010 at 00:33 UTC

    IO::File is only involved if you use HANDLE->print(...), not for print(HANDLE ...), much less before the handle is even created by open. It's not relevant here.

    Contrary to what you said, subs works fine.

    use strict; use warnings; use subs qw( open ); use Errno qw( EPIPE ); sub open { $! = EPIPE; return 0; } open(my $fh, '<', \my $buf) or die("open: $!\n");
    open: Broken pipe

    Update: You probably want to test a module without changing it, so your code would look more like the following:

    package ModuleToTest; sub f { open(my $fh, '<', \my $buf) or die("open: $!\n"); } 1;
    use Test::More tests => 1; { package ModuleToTest; use subs qw( open ); use Errno qw( EPIPE ); sub open { $! = EPIPE; return 0; } } use Module; ok( !eval { Module->f(); 1 } );
    1..1 ok 1
Re: Making open fail
by bluescreen (Friar) on Jul 03, 2010 at 00:15 UTC

    If I understand your program correctly somehow you're trying to get a file name from get_string() method and then open it, so far so good, but in the open call you are passing a reference to an scalar that is an in-memory file ( as mentioned here ) that means that if you read STDIN you will get $string's contents. In this scenario open will never fail as there no way you can have a problem opening an in-memory file.

    So if you meant to get a filename from an get_string() and open it you have to take out the "\" and if file cannot be opened will fail for sure

    my $file = '/tmp/inexistent'; open( my $fh, "<", \$file) or print "open in-memory file error"; open( my $fh1, "<", $file) or print "open real file error ";
Re: Making open fail
by SilasTheMonk (Chaplain) on Jul 03, 2010 at 12:31 UTC

    Ikegami, I tried subs again. It's pretty close to what I had tried before, but I was trying explicitly to be as close as possible to what you suggest. The script hangs on the print/read statement without the custom "open" function running.

    bluescreen I arrived at this situation by trying to follow best practices. 1.) opening a file should be error checked. 2.) I should use Devel::Cover to make sure that my tests provide 100% test coverage. It does seem likely that this particular call can never fail, but I still thinking the problem would be hard in some other circumstances. Also may be there could be a security hole in the perl core, which would allow hackers to generate such a situation and I want to defend my code against such known unknowns.

    Actually I have had two more ideas on how to approach this. One is experimenting with

    local *IO::File::open = sub {.....}
    If ikegami is right that't won't work but I mean it only as indication of what I might try. I assume that this is how subs works anyway. The second idea is to use autodie. I have tried this and it works in as far as it allows me to get my test coverage up. Arguably however it is just sidestepping the problem.

    Edit: Looking at the code for subs I can see why it won't work for me. It only deals with the calling package not where as I want to change how "open" works inside a package from outside.

    Edit 2: My experimentation with typeglobs is failing. It does no better than the subs code above.

      Although I agree that you should try to get a good coverage I wouldn't pursuit the 100% itself. Let's consider the following subroutine

      sub divide { my ($a, $b) = @_; return $a / $b; }

      You can easily create a test case and get a 100% coverage, and as you might realized the code can still fail ( if $b is 0 you'll get a "Illegal division..." ), here the 100% gives you a false sense of security. On the other hand the more tests your application has the harder it is to change it or refactor it

      My point is you shouldn't blindly follow best practices, they are just a guide not a law (use your own criteria). Don't use test coverage as a goal itself, otherwise you would end up writing code to get 100% coverage instead of writing code to perform a task

        On the other hand the more tests your application has the harder it is to ... refactor it
        On the contrary, the more tests you have (or rather, the better your test coverage), the *easier* it is to refactor. Refactoring should involve no changes to the code's function, and the better your test coverage, the easier it is to rearrange the code without unwittingly breaking it.
        bluescreen, I absolutely understand where you are coming from on 100% test coverage. It may be fairer to say I am experimenting with it. I have all sorts of thoughts on the subject. For a start with a new module it is straightforward, to keep the test coverage up to 100%. For an older module you can only hope to approach it aggressively. It is interesting to take a module that you regard as critical, and run Devel::Cover on it - and to compare the results with the bug list - and to ask how many of those bugs could have been avoided with better testing. When I have done that and tried to discuss the results, the responses such as they were struck me as complacent. Also I am working on restructuring and improving CGI::Application::Plugin::Authentication. As a I did not write that module originally, getting the test coverage up seems a constructive way of knowing and controlling the impact of my changes.
      You declare main::open (use subs qw(open);), and you define main::open, neither of which has any effect on calls to open in Test::. See the example I added to my post 8 hours before you posted for how to replace builtin open in a module without changing he module.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://847831]
Approved by ikegami
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (5)
As of 2024-04-19 10:19 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found