2010/10/31

Perl - Test::Identity

Today I uploaded a new module to CPAN; Test::Identity. It's possibly the quickest module I've ever written, from when I decided to write it, to when it was actually uploaded:
(18:02) sub Test::Identity::identical { is refaddr $_[0], refaddr $_[1], $_[2] } <== I'm about to write such in a module, unless anyone can suggest me a module that already has it
...
(19:21) * GumbyPAN CPAN Upload: Test-Identity-0.01 by PEVANS
I won't spend a long time explaining why, I'll just quote the docs:

This module provides a single testing function, identical. It asserts that a given reference is as expected; that is, it either refers to the same object or is undef. It is similar to Test::More::is except that it uses refaddr, ensuring that it behaves correctly even if the references under test are objects that overload stringification or numification.

It also provides better diagnostics if the test fails:
$ perl -MTest::More=tests,1 -MTest::Identity -e'identical [], {}'
1..1
not ok 1
# Failed test at -e line 1.
# Expected an anonymous HASH ref, got an anonymous ARRAY ref
# Looks like you failed 1 test of 1.

$ perl -MTest::More=tests,1 -MTest::Identity -e'identical [], []'
1..1
not ok 1
# Failed test at -e line 1.
# Expected an anonymous ARRAY ref to the correct object
# Looks like you failed 1 test of 1.

2010/10/18

Perl - IO::Socket::IP

IO::Socket::IP is a subclass of IO::Socket that provides a protocol-independent way to use IPv4 and IPv6 sockets. It provides an API compatible with the IPv4-only IO::Socket::INET, but does so in a way that ensures properly transparent IPv6 support.

The following example shows it has an identical API to the ::INET module:
use IO::Socket::IP;

my $sock = IO::Socket::IP->new(
PeerHost => "www.google.com",
PeerPort => "www",
) or die "Cannot construct socket - $@";
At this point, $sock is just another IO::Socket-derived filehandle, and supports all the usual methods and IO functionality. The only difference here is that, where IO::Socket::INET would have used a legacy gethostbyname call and made a PF_INET socket, IO::Socket::IP will use getaddrinfo (via Socket::GetAddrInfo), and will use either PF_INET or PF_INET6 as appropriate.

It's not yet a complete 100% API clone of ::INET though. While it supports all the methods, there are a few constructor arguments not yet supported, being Blocking and Timeout. The way that getaddrinfo returns a list of candidate addresses, to be tried in order, makes nonblocking support hard to do, and complicates the model for what timeout really means. For nonblocking connect support, better solutions already exist, such as IO::Async's Connector, which has always supported IPv6 via getaddrinfo. As for timeouts, eventually IO::Socket::IP should support it, but for now a local'ised $SIG{ALRM} and alarm call should suffice.

For almost all use cases, switching to using IO::Socket::IP should be a simple matter, because of the API similarities. Just adding an extra dependency (because ::IP isn't core), and substituting the package name in source should be enough.

Finally, in case you don't want to pull in an extra hard dependency, you might consider the following fragment I've used quite successfully:
   my $class = eval { require IO::Socket::IP
and "IO::Socket::IP" } ||
do { require IO::Socket::INET
and "IO::Socket::INET" };

$socket = $class->new(
PeerHost => $host,
PeerPort => $port,
) or die "Cannot connect - $@\n";
There. Now you have no excuse for not being IPv6-ready.