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

O wise and busy monks, I find myself pulling out valuable hair once again, and come seeking respite and enlightenment.

I'm writing something that will bake and eat cookies in an Apache2/mod_perl2 environment (environment2?) using HTML::Mason. After a bit of wrestling I got Apache2::Cookie and the rest of the libapreq2 things installed.

But now I can't figure out how to test this stuff. The documentation features a mysterious $r, who is supposed to be an Apache2::RequestRec. But then:

$ perl -MApache2::RequestRec -e 'Apache2::RequestRec->new()' Can't locate object method "new" via package "Apache2::RequestRec" at +-e line 1.

Well, maybe there's just another constructor. But I can't find it, and the docs for Apache::RequestUtil actually say that's supposed to work:

new Create a new Apache2::RequestRec object. $r = Apache2::RequestRec->new($c); $r = Apache2::RequestRec->new($c, $pool);

I understand that the $r is, in a running server, automagically provided by mod_perl2. But it seems like a Very Bad Idea ™ to have a running apache2/mod_perl2 system be a prerequisite for unit-testing your cookie recipes, so perhaps I'm just unclear on the concept.

The test I want to run, by the way, would look something like this in its most minimal form:

use strict; use warnings; use Test::More tests => 1; use Apache2::Cookie 2.08; use Apache2::RequestRec 2.0; use Apache2::RequestUtil 2.0; # This, of course, doesn't work: my $r = Apache2::RequestRec->new(); # This is straight out of the synopsis, # but where do I get that blasted $r? my $cookie = Apache2::Cookie->new( $r, -name => "mycookie", -value => "whatever" ); isa_ok( $cookie, 'Apache2::Cookie' );

As much as I'd like to sit around and complain about the mystery meat in that documentation, I have to actually get this thing done, and I believe my options are:

  1. Fall back to CGI::Cookie and hope nothing breaks.
  2. Live without tests and hope nothing breaks. **shudder**
  3. Bite the bullet, dig around in the Apache2::* code, and figure out how to make a Test::MockObject that is sufficiently $r.
  4. Have my dumb mistake(s) pointed out to me by a wise monk who has already solved this problem.

What, dear Monks, would you recommend, and have any of you faced similar troubles in the past?

Replies are listed 'Best First'.
Re: Where can I buy an $r with which to test Apache2::Cookie?
by perrin (Chancellor) on Feb 10, 2008 at 16:12 UTC
    You can't run Apache2:: classes outside of the mod_perl environment. They interact directly with a C API to data structures that don't exist when apache is not running. However, there is a high-quality testing framework developed specifically for this purpose called Apache::Test. It handles all the details of starting a test server for you. It's what the mod_perl project uses for testing and you can use it too.

      Thanks. That's what I feared, and I have heard of (and seen in action, with mixed results) Apache::Test, for which there is probably now an Apache2::Test... but I must say I still don't think that's a reasonable prerequisite for testing cookie generation.

      For testing cookie dispatch to the server, maybe... but for testing the generation per se, nope. At this point I'm hoping I can get it working with Test::MockObject.

      So I suppose if your warning applies to all Apache2:: classes then I'm going to waste some time, and if it just applies to the RequestRec then I'm golden.

        It's not going to work. Apache2::Cookie and the rest of libapreq are a perl interface to a C data structure provided by apache. It's like trying to test your database without running a database.

        If you're determined to test apache without apache, the best you can do is make a mock object of Apache2::Cookie and test that you sent it the right data.

Re: Where can I buy an $r with which to test Apache2::Cookie?
by TOD (Friar) on Feb 10, 2008 at 03:52 UTC
    what's so dangerous with a running apache? it is very well able to serve on a localhost IP, e.g. 127.0.0.1. you may even tell your system a hostname for this IP - however, in this case you should note it in /etc/hosts.
    --------------------------------
    masses are the opiate for religion.

      It's not that it's dangerous, it's that I don't think it's a sensible requirement for the thing being tested.

      I suppose I'm not willing to fire up an apache2 on some random port every time I want to test something that happens to involve Apache2::RequestRec. If I can't create the $r in a "pure" context then I should be able to mock it, that's exactly what Test::MockObject is for (and very good at).

      So, I dunno, maybe my objection is mostly aesthetic (plus the broken documentation) but I feel strongly enough about it that I refuse to "poison" my test suite for all eternity by constantly firing up web servers in order to verify things like correct unicode object serialization to a cookie.

      (I do of course admit that my definition of "poison" could be very different than anyone else's.)

      Maybe my best answer to your question is that the danger lies in introducing that "poison" into a test infrastructure that I'm hoping to live with for years to come.

      (And I love how ridiculously personal people can take their Perl code. Do Java people do this?)

        If I can't create the $r in a "pure" context then I should be able to mock it, that's exactly what Test::MockObject is for (and very good at).
        Aha, right. Then why don't you just do it? fake the $r with Test::MockObject?

        --shmem

        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Where can I buy an $r with which to test Apache2::Cookie?
by hossman (Prior) on Feb 10, 2008 at 08:59 UTC

    I've never played with mod_perl in my life ... but my reading of the docs you cite is that if you use Apache2::RequestUtil then it adds a a new method for Apache2::RequestRec ... but you still have to specify some args (namely a connection)

    (your first example code doesn't use Apache2::RequestUtil, your second example doesn't pass any args to Apache2::RequestRec->new() and you don't specify what kind of error you get)

    Then again, i could be totally wrong.

    You might also want to google unit test mod_perl code ... that turned up several promising looking links, many right here on perlmonks.

      Thanks for the reply. Are you sure Apache2::RequestUtil adds that method? Is there some secret way in which you need to load it for that to happen?

      I did try that, because I believe that's what the docs say, but as far as I could tell the docs are wrong.

      Sorry for the obscurantist test comment last time, here's a better one that actually passes:

      use strict; use warnings; use Test::More tests => 1; use Apache2::Cookie 2.08; use Apache2::RequestRec 2.0; use Apache2::RequestUtil 2.0; eval { my $r = Apache2::RequestRec->new(); }; like( $@, qr/Can't locate object method "new" via package "Apache2::RequestR +ec"/ );
Re: Where can I buy an $r with which to test Apache2::Cookie?
by Anonymous Monk on Feb 10, 2008 at 10:55 UTC

      Thanks for the links.

      Apache2::Cookie Pod says:

      Just like CGI::Cookie::new, but requires an additional environment argument

      ...which is the thing I need to mock.

      Also, as an aside, it seems like I keep running into example code (e.g. under the "cooking" link above) that isn't happy under use strict - unless, if I understand it correctly, it's running in a full mod_perl environment.

      Maybe I'm asking for too much, but I wish that were explained better. In the age of Google, every page is a landing page.

Re: Where can I buy an $r with which to test Apache2::Cookie?
by frostman (Beadle) on Feb 11, 2008 at 06:47 UTC

    UPDATE: maybe solved. Thanks to everyone for the help.

    Having chosen Door Number 3, it seems at least some of it is easier than I'd feared. This, for example, seems to work, except for the last test:

    use strict; use warnings; use Test::More tests => 5; use CGI; use Test::MockObject; use Apache2::Cookie 2.08; # This gets me started. my $r = Test::MockObject->new(); $r->mock( pool => sub { return APR::Pool->new; } ); # This part looks good. my $c = Apache2::Cookie->new( $r, # mocked -name => 'mycookie', -value => 'whatever' ) or die "no cookie for you"; isa_ok( $c, 'Apache2::Cookie' ); is( $c->value, 'whatever', "value" ); # This works for some values, but doing random # stuff via chr() can break it. I need to write # more tests to figure out whether that's my fault. my $val = "über die Straße"; my $c2 = Apache2::Cookie->new( $r, # mocked -name => 'mycookie', -value => $val ) or die "no cookie for you"; like( $c2->as_string, qr/[a-zA-Z0-9%=]+/, "as_string encoded" ); is( $c2->value, $val, "value not encoded" ); # Uh-oh, this is busted. I think I can live # without it, but it's annoying. eval { $c2->raw_value(); }; is( $@, '', "raw_value() doesn't die" );

    That last test gets me this error:

    Can't locate auto/Apache2/Cookie/raw_value.al in @INC

    Joy. If it's just a case of the docs being wrong, yet the code working, then I'm OK with that. I can file a documentation bug, with a test and a patch, if I can prove it.

    I'm sure I have the right version installed, and as I built it from source I'm also sure all its non-skipped tests.

      FYI, I was trying to use Test::MockObject with mod_perl2, but discovered a bug that causes an apache2 child segmentation fault after several successful HTTP requests when running a shell command from the response handler (such as with backticks or system()). Here is a minimal response handler that reproduces the problem:

      #! /usr/bin/perl -w package MyApache2::Chain; use strict; use Apache2::Const -compile => qw(:common); use Apache2::SubRequest (); # for $r->internal_redirect ### Comment out the following line and the Apache2 child ### segmentation fault no longer occurs: use Test::MockObject; sub handler { my $r = shift || die; my $f = $ENV{DOCUMENT_ROOT} . "/date.txt"; system("date >> $f 2> /dev/null"); $r->internal_redirect("/date.txt"); return Apache2::Const::OK; } ##### DO NOT DELETE THE FOLLOWING LINE! ##### 1;

      See my bug report at https://rt.cpan.org/Public/Bug/Display.html?id=73723