Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?

Simulating the Future

by dynamo (Chaplain)
on Jan 04, 2008 at 18:55 UTC ( [id://660449] : perlquestion . print w/replies, xml ) Need Help??

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

Greetings monkii,

I've written some mod_perl code that will execute conditionally after a certain time(). I'd like to be able to make sure that it works. Is there a well-tested method out there to have my code get a faked time value when it asks? I've looked at Time::Warp, and it seems like overkill to get xs involved. That would give the verification process itself considerably more risk of regression than the very simple change I'm testing for.

Ideally I'd like to do something outside the normal codebase, such as adding in a module on the command line.

btw - Changing the real system time is not an option. Other processes can (and must) handle the truth.

Thanks in advance,
- d

Replies are listed 'Best First'.
Re: Simulating the Future
by blokhead (Monsignor) on Jan 04, 2008 at 19:46 UTC
    Update 2: This module has been cleaned up a little bit and some small features added (not updated in the code in this node), and I've uploaded it to CPAN as Time::Fake. It will appear on CPAN after the normal processing time...

    Here's a little module that I just threw together. It overrides the builtin time sub (Update: and also localtime and gmtime) to adjust the output by some offset:

    package TimeOffset; sub import { my $pkg = shift; my $offset = shift || "+0"; if ($offset !~ /^[+-]/) { $offset = $offset - time; } *CORE::GLOBAL::time = sub { CORE::time() + $offset }; *CORE::GLOBAL::localtime = sub { return @_ ? CORE::localtime(@_) : CORE::localtime(CORE::time() + $offset); }; *CORE::GLOBAL::gmtime = sub { return @_ ? CORE::gmtime(@_) : CORE::gmtime(CORE::time() + $offset); }; } 1;
    You can use this as follows:
    use TimeOffset '+3600'; # pretend it's 1hr in the future
    use TimeOffset '-60'; # pretend it's 1min in the past
    use TimeOffset 12345678; # pretend the program was started # at 12345678 epoch seconds
    This is just a proof of concept but you can probably extend it to however your code figures out the current time. I.e, there may be other ways to obtain the time in perl that I haven't thought of.

    Perhaps I could make this a little nicer, package it up, and send it off to CPAN? That is, if something like this really isn't on CPAN already..

    Update: some caveats that I can think of, off the top of my head:

    • doesn't affect -M, -A, -C filetest operators in the way you'd probably want. I belive these would still report *actual* script start time (not faked script start time) minus file access time.


      This looks more like what I was getting at. Thanks. I'd say package it up, I'd have used it if it were there this morning. The competition requires a compile.

      - d

      Very nice... A couple extra ideas, which may or may not already be in the changes you made before sending it to CPAN:

      - Change $offset from my to our (or add a method for setting it) so that tests can, e.g., do something now (at an offset of +0) which expires in an hour, then set offset to +3600 and check whether it has expired.

      - Add an our $fixed_time which, if defined, returns that time (instead of actual time + offset) for all calls. This would be useful for cases where you're testing that a sub stores the time correctly and don't want to have to worry about whether the system clock rolled over from 12345678 to 12345679 between when the time is stored and when you test the stored value's correctness.

Re: Simulating the Future
by Old_Gray_Bear (Bishop) on Jan 04, 2008 at 19:05 UTC
    Given your constraint (changing the system clock is not an option), I'd set myself up with a subroutine that I called every-time I needed to refer to the clock. A little bit of Getopts::Std to let me pass a parameter in from the command line, and I can set the subroutine to return anytime I like.

    I Go Back to Sleep, Now.


      Well, that's sorta like what I was thinking.. The problem is that I can't make something like mytime() and replace throughout the code because A> waaaay too many to replace, and B> it's an internal codebase change. The testing system has to fake the time externally to validate the code, otherwise it would be modified again when I take out the test..

      My first thought was to try using something like this:

      package FakeTime; sub main::time { 12345 } 1;
      That didn't work. Neither did "sub ::time { 12345 }", I thought that might work as a second resort. Didn't.

      - d