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

I am puzzled as to how to test for a read() failure across two platform we deploy to here.

Background:
I am writing coverage tests, using Devel::Cover. The coverage tests are for a module that reads encoded files and turns their contents into an object representation.

Problem:
I have tests that check the modules behaviour when there are various problems with the file e.g. file doesn't exist, is zero length, is actually a directory, we dont have adequate permissions etc.

But the problem I am having is testing read failures on a file I have successfully opened. And to have those tests work on both Linux and Solaris (others would be nice - but those at a minimum).

Partial Solution:
I have tests that work correctly (that is, cause a read() to fail on a successfully opened file) on Linux, but they dont work on Solaris.

To get the Linux test working, I did this

<File.t> ... my $obj = Widget::File->new(filename => $testfile, # a real file otherstuff => ...); my $dirh = IO::File->new('/', O_RDONLY); # reading a dirhandle forces +a read() failure on Linux $obj->{fh} = $dirh; # invoke a method that calls read() internally eval { $obj->header(); # read()ing from a dirhandle forces an EISDIR erro +r }; like($@, qr(Error in reading file header - Is a directory at )); </File.t>

But this doesn't work on Solaris - instead I get this

./t/File..........NOK 71# Failed test (./t/File.t at line 191) # '' # doesn't match '(?-xism:Error in reading file header - Is a direc +tory at )' ./t/File..........ok 73/0# Looks like you failed 1 tests of 73.

Now I expect that Solaris is actually returning EISDIR in errno at the C library level, but this isn't propagating up to Perl in Solaris the same way it does in Linux - and hence the regex error.

BTW
Solaris perl == 5.8.1
Linux perl == 5.8.3

So, please, can anyone advise me on a better way for testing cross platform read failures?

+++++++++++++++++
#!/usr/bin/perl
use warnings;use strict;use brain;

Replies are listed 'Best First'.
Re: Testing for read() failures on different platforms
by dragonchild (Archbishop) on Jul 27, 2004 at 02:32 UTC
    What on earth would generate a read failure? You mention reading a directory. Yet, directories are just regular files that have special magic associated with them at the OS level. Linux does a little more work for you than Solaris does. This fact doesn't surprise me at all. Personally, I think Solaris is an OS that hasn't been intelligently supported in years and am actively trying to convert my company from Solaris 2.9 to RH ES 3.

    That said, I'm still wondering what other situations would generate a read failure. If you can successfully open a file, you should always be able to read from it. Of course, I suppose you could create the file, open it successfully, then delete it and try to read from it. That might generate a read error. Then again, a simplistic test on Cygwin(WinXP) didn't. I suffered from input buffering. *shrugs*

    Good luck?

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      I can think of two ways a read from a successfully opened file could fail:

      Opening a file for write access and then trying to read from it. Perl catches this.

      Opening a file on removeable media, and then the media is removed before the read attempt.

      Under win32 (native), this results in an "Abort, retry, continue" popup. Retry is loop unless it is reinserted. The other two result in the read completing as if the file where empty. Ie. a successful read of zero bytes. I'm not sure what *nix would do under these circumstances.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
      "Memory, processor, disk in that order on the hardware side. Algorithm, algoritm, algorithm on the code side." - tachyon
        You forgot about corrupted media. The cheapest media to corrupt is CD-Roms. Take 1, burn a giant file to take up all the space, then scratch it, then try reading from it.

        MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
        I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
        ** The third rule of perl club is a statement of fact: pod is sexy.

        I'm thinking more along the lines of an NFS or Samba mount that goes bye-bye

        +++++++++++++++++
        #!/usr/bin/perl
        use warnings;use strict;use brain;

      That said, I'm still wondering what other situations would generate a read failure.

      Very true, we've been discussing that here - more like, well, if it 'very rarely' happens, should we try to catch a failure.

      But seeing as we have code that tries to deal intelligently with this scenario, we really should try and test it, just to confirm the code really does what we think it does.

      So yes one solution is to not cater for read failures on successfully opened files - then you dont need a test case !-)

      But we are (currently) catering for it, and we want to test that accomodation on different platforms - but with the above mentioned difficulty.

      FWIW, we are getting a consistent (as in, the same error message) on Linux and Solaris if we replace the dummy directory handle (made with IO::File) with one created by IO::Dir - we get

      'usage: $dh->seek(POS) at ...'
      on both platforms - so that would work for now, but come the day we go to another platform I dont hold much hope that will be robust enough

      +++++++++++++++++
      #!/usr/bin/perl
      use warnings;use strict;use brain;

      I suppose you could create the file, open it successfully, then delete it and try to read from it. That might generate a read error.

      Not on UNIX. It is perfectly possible to open a file, unlink it from the file system, and still be able to read from or write to it. The file is actually deleted when the last open filehandle to it is closed. This is a common way to create a temporary file that no other process can open.

      #!/usr/bin/perl my $tf = 'tmpfile'; open FH, ">$tf" or die $!; print FH "Test data\n"; close FH; open FH, "<$tf" or die $!; unlink $tf; print "File deleted!\n" unless -e $tf; print 'File contents: ', <FH>;
        In a similar way, removing read permissions on a file won't prevent reading of the contents through an already open file handle. That may seem insecure, but the property can be used to enhance security. If a process needs to read from or write to a protected resource, it can start life as root, open the resource appropriately and then drop privileges. The now unprivileged process still has privileged access through the open file handle.

        "Even if you are on the right track, you'll get run over if you just sit there." - Will Rogers
      What on earth would generate a read failure?

      Networked file systems, corrupt media, broken pipe, etc. Not checking for read errors can lead to evil bugs :-)

      As to the OPs question:

      ... can anyone advise me on a better way for testing cross platform read failures?

      I find redefining read is the simplest solution. At its most basic just adding

      BEGIN { *CORE::GLOBAL::read = sub (*\$$;$) { return undef }; };

      to your test file will always cause read to fail.

        Yep, of course.
        And nicely extensible to other functions in the CORE package I imagine, like testing failing write()'s, etc.

        That is the solution I will use.

        Thank you

        +++++++++++++++++
        #!/usr/bin/perl
        use warnings;use strict;use brain;

        Bugger, that doesn't work, and it behaves funny too.

        Here's the code in the File.t file

        ... $file = File->new('t/file7.test'); eval { # declare as local so default is restored on block exit local *CORE::GLOBAL::read = sub (*\$$;$) { return undef }; $file->header(); }; ...

        And File::header() looks like this

        ... sub header { my ( $self ) = @_; my $seek_rc = $self->{FH}->seek($self->{Header}->{OFFSET}, SEEK_SE +T ); if ($seek_rc == 0) { croak( 'Cannot seek to absolute file position on opened file handl +e - ', $! ); } my $length = $self->{FH}->read(my $header, $self->{Header}->{Lengt +h}); # check if read() had an error croak( "Error in reading file header - $!") # pathological test unless defined $length; croak( 'Error in reading file header - Mismatch between expected l +ength (', $self->{Header}->{Length}, ") and returned length ($length +) byte count") unless $length == $self->{Header}->{_Length}; $header =~ s/(?:\015+)?\012.*$/\n/; # remove any garbage at end +of header and replace with \n return $header; } ...

        So if the read() returns undef, I expect the croak() message to appear with the $! reason for failure.

        Instead, I don't get my croak() message , all I get is $! populated with 'Bad file descriptor'. $@ is empty (that us , $@ eq '').

        Whatsmore, this happens on Linux. Both $@ and $! are still an empty string on Solaris.

        So why aren't I croak()'ing ?

        use brain;

Re: Testing for read() failures on different platforms
by Fletch (Bishop) on Jul 27, 2004 at 03:39 UTC

    You could use the EFOO constants from the POSIX module and check against $! in a numeric context.

    use POSIX qw( :errno_h ); ## Something that fails here if( $! == EISDIR ) { warn "error was directory\n" }
Re: Testing for read() failures on different platforms
by hbo (Monk) on Jul 27, 2004 at 05:00 UTC
    What is your LANG environment variable set to on each platform? RHEL3 sets it to e.g. en_US.utf8 by default. The unicode locale causes lots of new, different, and possibly buggy behavior when reading data from a file.
    "Even if you are on the right track, you'll get run over if you just sit there." - Will Rogers
      [le6303@itdevtst src]$ env | grep LANG LANG=en_US
      I set it to this cause the utf8 stuff was causing Tk to not compile. See the bug report

      +++++++++++++++++
      #!/usr/bin/perl
      use warnings;use strict;use brain;

        I assume it's the same on both platforms, then? What happens if you set it to "C"?

        How about the modules involved? All the same versions? Can you use the same version of Perl, preferably from the same source tree and with build options as similar as possible? This is just the standard troubleshooting checklist that I never resort to unless I can't solve a problem through dumb luck and/or inspiration. 8)

        "Even if you are on the right track, you'll get run over if you just sit there." - Will Rogers