Suppose you are a leading political figure and want certain persons to disappear. You also have members of your organisation that make this happen. But you do not trust them, in fact, you do not trust anyone - that is how you got to your position in the first place. So you want to make sure that the members of your cleanup service do their work, but if they do their work right, they will not be able to show evidence. So how can you make sure that the target is actually eliminated and not only vanished from your grasp?

If your organisation has a "mark of the beast" for every subject, then asserting the demise of unwanted individuals becomes easy as your minions only have to bring a vital and uniquely identifiable part of the target, for example, the heart, against which a DNA test can be made, or the implanted RF chip, if the removal of that chip is lethal.

A similar situation arises when you want to test whether the destructors of your class work: You can't tell whether an object really has gone away as long as you hold a reference to it, and if you release your last reference to it, you can't inspect whether it has gone away. In most cases, this is not of much interest, but if you have circular references, for example through XML tree structures where each node knows its children and the parent, cleanup becomes something that should be tested.

Luckily, Perl has all the mechanisms in place to supervise the extermination of the unwanted subjects in our influence :

The "mark of the beast" of every object is the stringified object, or in presence of overload.pm, it is overload::AddrRef. We have a central morgue in the form of the DESTROY method for every class. So if we want to check whether an object goes away, we assume we hold the last reference to it, install a temporary DESTROY subroutine that counts how often it sees the "mark of the beast" of the object, and we then release our stranglehold on that object.

If we see the mark of the beast exactly once, nobody else had a reference to the object anymore, and all was well. If we never see it, somebody else still has a live reference to the object. If we see the reference more than once, something is really wrong.

So now, here's the code, plus some questions :

  1. This module could be implemented by simply looking at the reference count of the object in question, but there is no pure Perl method to my knowledge to accomplish this.
  2. The module name is still in flux, but as it is for testing the destruction of objects, I found Test::DESTROY appealing - any better idea?
  3. The best method I came up to implement the inverse of destroyed_ok was to fork() the test a second time and use not destroyed_ok() there, observing the output, because most likely, the main program still holds a reference to the object if it assumes to be able to use the object afterwards. A simple look at the reference count would help of course ...

First the example how the module would be used in your tests :

#!/usr/bin/perl -w use strict; use Test::More; use Test::DESTROY tests => 6; my $foo = bless {},'My::Test::Class'; destroyed_ok($foo,"Simple class"); my $foo1 = bless {},'My::Test::Class'; my $bar = $foo1; destroyed_ok($foo1,"Simple class with two references (expected fail)") +; my $foo2 = bless {},'My::Test::Class'; $foo2->{child1} = bless {},'My::Test::Class'; $foo2->{child2} = bless {},'My::Test::Class'; destroyed_ok($foo2,"Simple class with two references"); my $foo3 = bless {},'My::Test::Class'; $bar = $foo3; $foo3->{child1} = bless {},'My::Test::Class'; $foo3->{child2} = bless {},'My::Test::Class'; destroyed_ok($foo3,"Simple class with two references (expected fail)") +; my $foo4 = bless {},'My::Test::Class::Destructor'; my $called = 0; sub My::Test::Class::Destructor::DESTROY { $called++; }; destroyed_ok($foo4,"Simple class with two references"); is($called,1,"Previous destructor gets called");
And here is the module itself (in its current incarnation, without POD):
package Test::DESTROY; use strict; use Test::Builder; require Exporter; use vars qw( @ISA @EXPORT ); @ISA = qw(Exporter); @EXPORT = qw(destroyed_ok); my $Test = Test::Builder->new; sub import { my($self) = shift; my $pack = caller; $Test->exported_to($pack); $Test->plan(@_); $self->export_to_level(1, $self, 'destroyed_ok'); }; sub object_id { my $id; eval { require overload; $id = overload::AddrRef( $_[0] ); }; # If we couldn't load overload, assume nobody else could either, # so simple stringification should work $id = "$_[0]" if $@; $id; }; sub destroyed_ok { my $name = $_[1]; $name ||= "The object"; $name = "$name was destroyed"; my $fail_reason; if (ref $_[0]) { if (UNIVERSAL::isa($_[0],'UNIVERSAL')) { my $class = ref $_[0]; my $id = object_id( $_[0] ); my $count = 0; no strict 'refs'; my $prev_destructor = *{$class . "::DESTROY"}{CODE}; local *{$class . "::DESTROY"} = sub { $count++ if object_id($_[0]) eq $id; goto $prev_destructor if $prev_destructor }; undef $_[0]; if ($count == 1) { $Test->ok(1,$name); } elsif ($count > 1) { $fail_reason = "${class}::DESTROY was called $count times"; } else { $fail_reason = "${class}::DESTROY was never called for $id"; }; } else { $fail_reason = "$_[0] is not a blessed reference."; }; } else { $fail_reason = "$_[0] is not a reference."; }; if ($fail_reason) { $Test->ok(0,$name); $Test->diag($fail_reason); }; };

In reply to RFC: Test::Destroy ('Chasing shadows') by Corion

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.