Think McFly, Think!

This week I made a time machine with Perl.

So, writing tests for testing anything that happens with the passage of time is very very hard to do as you don’t know how long any operation might actually take on the computer you’re testing on. Code may take different time to run on different machines. Sleep instructions may take longer on busy machines than on non-busy machines. The user might suspend the computer in the middle of your test suite!

Multiple this across the large number and diverse set of computers that might run your test suite after you upload your module to the CPAN and you’re in a situation where you’re going to get some false negatives, where the test suite will fail even though nothing’s really wrong. Telling your end users to “try it again, it’l probably work this time” isn’t exactly a recipe for instilling confidence in your code.

That’s why I developer my trusty time machine. Test::Time::HiRes is what I call it, and it allows me fine grained control over passage of a certain type of time – the time that Test::HiRes reports back to modules using it.

The easiest way to think of it is an alternative implementation of Time::HiRes where time only progresses time when you (or the code you’re testing) tells it to. This means that all sleeps your code does take an instant of wallclock seconds, but the simulated clock moves automatically. As such this code runs in milliseconds, rather than hours:

use Test::Time::HiRes;  
use Time::HiRes qw(sleep);
sleep(3600 * 10);

We can also jump around in time however we want.

use Test::More tests => 2;
use Test::Time::HiRes;

# Time::Hash::Expire uses Time::HiRes to get it's timing
use Tie::Hash::Expire;
tie my %hash, "Tie::Hash::Expire", { expire_seconds => 10 };
$hash{"foo"} = 1;

ok($hash{"foo"}, "key not expired");
time_travel_by(3600);
ok(!$hash{"foo"}, "key expired");

(or back in time, or to particular points in time.) We have complete control!

Of course, I’m not the first person to come up with this idea. The very fine Test::MockTime does the same thing for Perl’s inbuilt time(),localtime(), and gmtime() functions. I’m just extending the concept for more accurate time.

It’s not quite a DeLorean, but it’ll do me.

The module is on the CPAN and on github. [Update: as of 2009-09-24 20:15:39 UTC, the module still hasn't reached search.cpan.org yet - I'll leave the link in place for when it does, but if you're impatient, head off to github]

Posted in 1

Permalink 1 Comment

Three Blinding Perl Tips, See How They Execute

I’ve got a lot of half written posts that I still need to complete, but I don’t want to let this blog stagnate. Quick! Time for an n things list – Three random Perl things that I’ve been using a lot recently:

Default Prompts

If you install Term::ReadLine::Perl you can provide defaults for interactive prompts in your program

my $term = Term::ReadLine->new('report');
my $filename = $term->readline(
   "file to write output to? ",
   "$ENV{HOME}/report-".DateTime->now->ymd.".csv"
);

When this executes this code creates a line in your terminal that has a default value already filled in for you to edit:

file to write output to? /Users/mark/report-2009-09-16.csv

Controlling Where lwp-request Sends Its Requests

The command line utility lwp-request, which is a handy Perl utility that ships with LWP that allows you to download webpages from the command line, can take proxy settings from the environment variables you pass to it.

bash$ http_proxy=http://127.0.0.1 lwp-request http://www.mywebsite.com

This is really useful for development: You can set your test apache running on your local machine / virtual machine / dev box up to answer on the same virtual host name as your live domain and, via the proxy settings, have lwp-request send requests there rather than to the real live machine whose DNS the domain name points to. This is also really useful for debugging reverse proxies in live – you can choose exactly which machine in your proxy chain to send to, bypassing the nginx / lighttpd / varnish front proxy machine and talking directly to the backend machine if you wish.

The “Just Show Me The Data” Incarnation for DBI

Most of the time I don’t use DBI directly – I use an object relational mapper like DBIx-Class. But sometimes I’ll get handed a chunk of SQL from our DBA and I just want to write a wrapper script to run that SQL and do something simple with it. DBI is very flexible, and the documentation talks at length about things like caching parsed queries, reading data in from the database row by row, efficency of data structures, etc, etc. What it’s not very clear about, however, is how to ignore all of that and just get all your data from executing some SQL in the easiest possible format to manipulate.

The magic incarnation looks like this:

    my $dbh = DBI->connect(
      "DBI:mysql:hostname=127.0.0.1;database=foo",
      $username, 
      $password,
      { RaiseError => 1 },
    );

    my $rows = $dbh->selectall_arrayref( <<'SQL',{ Slice => {} }, "flintstone");
      SELECT *
        FROM characters
       WHERE last_name = ?
    SQL

This gives you a reference to an array, with each element in this array representing one row returned from the database as a hashref keyed by field name. This is really easy to process:

    foreach my $row (@{ $rows }) {
      say " * $row->{first_name} $row->{last_name}";
    }

If there's any problem at all DBI will raise an exception, so you don't need to worry about writing lots of error checking code.

Follow

Get every new post delivered to your Inbox.