package TestAPR::perlio; # to see what happens inside the io layer, assuming that you built # mod_perl with MP_TRACE=1, run: # env MOD_PERL_TRACE=o t/TEST -v -trace=debug apr/perlio use strict; use warnings FATAL => 'all'; use Apache::Test; use Apache::TestUtil; use Fcntl (); use File::Spec::Functions qw(catfile); use Apache2::Const -compile => qw(OK CRLF); #XXX: APR::LARGE_FILES_CONFLICT constant? #XXX: you can set to zero if largefile support is not enabled in Perl use constant LARGE_FILES_CONFLICT => 1; # apr_file_dup has a bug on win32, # should be fixed in apr 0.9.4 / httpd-2.0.48 require Apache2::Build; use constant APR_WIN32_FILE_DUP_BUG => Apache2::Build::WIN32() && !have_min_apache_version('2.0.48'); sub handler { my $r = shift; my $tests = 22; $tests += 3 unless LARGE_FILES_CONFLICT; $tests += 1 unless APR_WIN32_FILE_DUP_BUG; require APR::PerlIO; plan $r, tests => $tests, need { "This Perl build doesn't support PerlIO layers" => APR::PerlIO::PERLIO_LAYERS_ARE_ENABLED() }; my $vars = Apache::Test::config()->{vars}; my $dir = catfile $vars->{documentroot}, "perlio"; t_mkdir($dir); my $sep = "-- sep --\n"; my @lines = ("This is a test: $$\n", "test line --sep two\n"); my $expected = $lines[0]; my $expected_all = join $sep, @lines; # write file my $file = catfile $dir, "test"; t_debug "open file $file for writing"; my $foo = "bar"; open my $fh, ">:APR", $file, $r->pool or die "Cannot open $file for writing: $!"; ok ref($fh) eq 'GLOB'; t_debug "write to a file:\n$expected\n"; print $fh $expected_all; close $fh; # open() failure test { # workaround for locale setups where the error message may be # in a different language open my $fh, "perlio_this_file_cannot_exist"; my $errno_string = "$!"; # non-existent file my $file = "/this/file/does/not/exist"; if (open my $fh, "<:APR", $file, $r->pool) { t_debug "must not be able to open $file!"; ok 0; close $fh; } else { ok t_cmp("$!", $errno_string, "expected failure"); } } # seek/tell() tests unless (LARGE_FILES_CONFLICT) { open my $fh, "<:APR", $file, $r->pool or die "Cannot open $file for reading: $!"; # read the whole file so we can test the buffer flushed # correctly on seek. my $dummy = join '', <$fh>; # Fcntl::SEEK_SET() my $pos = 3; # rewinds after reading 6 chars above seek $fh, $pos, Fcntl::SEEK_SET(); my $got = tell($fh); ok t_cmp($got, $pos, "seek/tell the file Fcntl::SEEK_SET"); # Fcntl::SEEK_CUR() my $step = 10; $pos = tell($fh) + $step; seek $fh, $step, Fcntl::SEEK_CUR(); $got = tell($fh); ok t_cmp($got, $pos, "seek/tell the file Fcntl::SEEK_CUR"); # Fcntl::SEEK_END() $pos = -s $file; seek $fh, 0, Fcntl::SEEK_END(); $got = tell($fh); ok t_cmp($got, $pos, "seek/tell the file Fcntl::SEEK_END"); close $fh; } # read() tests { open my $fh, "<:APR", $file, $r->pool or die "Cannot open $file for reading: $!"; # basic open test ok ref($fh) eq 'GLOB'; # basic single line read ok t_cmp(scalar(<$fh>), $expected, "single line read"); # slurp mode seek $fh, 0, Fcntl::SEEK_SET(); # rewind to the start local $/; ok t_cmp(scalar(<$fh>), $expected_all, "slurp file"); # test ungetc (a long sep requires read ahead) seek $fh, 0, Fcntl::SEEK_SET(); # rewind to the start local $/ = $sep; my @got_lines = <$fh>; my @expect = ($lines[0] . $sep, $lines[1]); ok t_cmp(\@got_lines, \@expect, "custom complex input record sep read"); close $fh; } # eof() tests { open my $fh, "<:APR", $file, $r->pool or die "Cannot open $file for reading: $!"; ok t_cmp(0, int eof($fh), # returns false, not 0 "not end of file"); # go to the end and read so eof will return 1 seek $fh, 0, Fcntl::SEEK_END(); my $received = <$fh>; t_debug($received); ok t_cmp(eof($fh), 1, "end of file"); close $fh; } # dup() test { open my $fh, "<:APR", $file, $r->pool or die "Cannot open $file for reading: $!"; open my $dup_fh, "<&:APR", $fh or die "Cannot dup $file for reading: $!"; close $fh; ok ref($dup_fh) eq 'GLOB'; my $received = <$dup_fh>; close $dup_fh; unless (APR_WIN32_FILE_DUP_BUG) { ok t_cmp($received, $expected, "read/write a dupped file"); } } # unbuffered write { open my $wfh, ">:APR", $file, $r->pool or die "Cannot open $file for writing: $!"; open my $rfh, "<:APR", $file, $r->pool or die "Cannot open $file for reading: $!"; my $expected = "This is an un buffering write test"; # unbuffer my $oldfh = select($wfh); $| = 1; select($oldfh); print $wfh $expected; # must be flushed to disk immediately ok t_cmp(scalar(<$rfh>), $expected, "file unbuffered write"); # buffer up $oldfh = select($wfh); $| = 0; select($oldfh); print $wfh $expected; # should be buffered up and not flushed ok t_cmp(scalar(<$rfh>), undef, "file buffered write"); close $wfh; close $rfh; } # tests reading and writing text and binary files { for my $file ('MoonRise.jpeg', 'redrum.txt') { my $in = catfile $dir, $file; my $out = catfile $dir, "$file.out"; my ($apr_content, $perl_content); open my $rfh, "<:APR", $in, $r->pool or die "Cannot open $in for reading: $!"; { local $/; $apr_content = <$rfh>; } close $rfh; open my $pfh, "<", $in or die "Cannot open $in for reading: $!"; binmode($pfh); { local $/; $perl_content = <$pfh>; } close $pfh; ok t_cmp(length $apr_content, length $perl_content, "testing data size of $file"); open my $wfh, ">:APR", $out, $r->pool or die "Cannot open $out for writing: $!"; print $wfh $apr_content; close $wfh; ok t_cmp(-s $out, -s $in, "testing file size of $file"); unlink $out; } } # tests for various CRLF and utf-8 issues { my $scratch = catfile $dir, 'scratch.dat'; my $text; my $count = 2000; open my $wfh, ">:crlf", $scratch or die "Cannot open $scratch for writing: $!"; print $wfh 'a' . ((('a' x 14) . "\n") x $count); close $wfh; open my $rfh, "<:APR", $scratch, $r->pool or die "Cannot open $scratch for reading: $!"; { local $/; $text = <$rfh>; } close $rfh; ok t_cmp(count_chars($text, Apache2::Const::CRLF), $count, 'testing for presence of \015\012'); ok t_cmp(count_chars($text, "\n"), $count, 'testing for presence of \n'); open $wfh, ">:APR", $scratch, $r->pool or die "Cannot open $scratch for writing: $!"; print $wfh 'a' . ((('a' x 14) . Apache2::Const::CRLF) x $count); close $wfh; open $rfh, "<:APR", $scratch, $r->pool or die "Cannot open $scratch for reading: $!"; { local $/; $text = <$rfh>; } close $rfh; ok t_cmp(count_chars($text, Apache2::Const::CRLF), $count, 'testing for presence of \015\012'); ok t_cmp(count_chars($text, "\n"), $count, 'testing for presence of \n'); open $rfh, "<:crlf", $scratch or die "Cannot open $scratch for reading: $!"; { local $/; $text = <$rfh>; } close $rfh; ok t_cmp(count_chars($text, Apache2::Const::CRLF), 0, 'testing for presence of \015\012'); ok t_cmp(count_chars($text, "\n"), $count, 'testing for presence of \n'); my $utf8 = "\x{042F} \x{0432}\x{0430}\x{0441} \x{043B}\x{044E}"; open $wfh, ">:APR", $scratch, $r->pool or die "Cannot open $scratch for writing: $!"; binmode($wfh, ':utf8'); print $wfh $utf8; close $wfh; open $rfh, "<:APR", $scratch, $r->pool or die "Cannot open $scratch for reading: $!"; binmode($rfh, ':utf8'); { local $/; $text = <$rfh>; } close $rfh; ok t_cmp($text, $utf8, 'utf8 binmode test'); unlink $scratch; } # XXX: need tests # - for stdin/out/err as they are handled specially # XXX: tmpfile is missing: # consider to use 5.8's syntax: # open $fh, "+>", undef; # cleanup: t_mkdir will remove the whole tree including the file Apache2::Const::OK; } sub count_chars { my ($text, $chars) = @_; my $seen = 0; $seen++ while $text =~ /$chars/g; return $seen; } 1;