The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!./perl
#
#  Copyright (c) 1995-2000, Raphael Manfredi
#  
#  You may redistribute only under the same terms as Perl 5, as specified
#  in the README file that comes with the distribution.
#  

use Config;

BEGIN {
    if (env::var('PERL_CORE')){
	push $^INCLUDE_PATH, '../ext/Storable/t';
    } else {
	unshift $^INCLUDE_PATH, 't';
    }
    require 'st-dump.pl';
}

use Storable < qw(freeze thaw dclone);

package OBJ_REAL;

use Storable < qw(freeze thaw);

my @x = @('a', 1);

sub make { bless \@(), shift }

sub STORABLE_freeze {
	my $self = shift;
	my $cloning = shift;
	die "STORABLE_freeze" unless Storable::is_storing;
	return @(freeze(\@x), $self);
}

sub STORABLE_thaw($self, $cloning, $x, $obj) {
	die "STORABLE_thaw #1" unless $obj \== $self;
	my $len = length $x;
	my $a = thaw $x;
	die "STORABLE_thaw #2" unless ref $a eq 'ARRAY';
	die "STORABLE_thaw #3" unless nelems @$a == 2 && $a->[0] eq 'a' && $a->[1] == 1;
	@$self = @$a;
	die "STORABLE_thaw #4" unless Storable::is_retrieving;
}

package OBJ_SYNC;

@x = @('a', 1);

sub make { bless \%(), shift }

sub STORABLE_freeze($self, $cloning) {
	return if $cloning;
	return @("", \@x, $self);
}

sub STORABLE_thaw($self, $cloning, $undef, $a, $obj) {
	die "STORABLE_thaw #1" unless $obj \== $self;
	die "STORABLE_thaw #2" unless ref $a eq 'ARRAY' || @$a != 2;
	$self->{+ok} = $self;
}

package OBJ_SYNC2;

use Storable < qw(dclone);

sub make($class, $ext) {
	my $self = bless \%(), $class;

	$self->{+sync} = OBJ_SYNC->make;
	$self->{+ext} = $ext;
	return $self;
}

sub STORABLE_freeze {
	my $self = shift;
	my %copy = %$self;
	my $r = \%copy;
	my $t = dclone($r->{?sync});
	return @("", \@($t, $self->{?ext}), $r, $self, $r->{?ext});
}

sub STORABLE_thaw($self, $cloning, $undef, $a, $r, $obj, $ext) {
	die "STORABLE_thaw #1" unless $obj \== $self;
	die "STORABLE_thaw #2" unless ref $a eq 'ARRAY';
	die "STORABLE_thaw #3" unless ref $r eq 'HASH';
	die "STORABLE_thaw #4" unless $a->[1] \== $r->{?ext};
	$self->{+ok} = $self;
	@($self->{+sync}, $self->{+ext}) = @$a;
}

package OBJ_REAL2;

use Storable < qw(freeze thaw);

my $MAX = 20;
my $recursed = 0;
my $hook_called = 0;

sub make { bless \@(), shift }

sub STORABLE_freeze {
	my $self = shift;
	$hook_called++;
	return @(freeze($self), $self) if ++$recursed +< $MAX;
	return @("no", $self);
}

sub STORABLE_thaw($self, $cloning, $x, $obj) {
	die "STORABLE_thaw #1" unless $obj \== $self;
	$self->[+0] = thaw($x) if $x ne "no";
	$recursed--;
}

package main;

use Test::More tests => 32;

my $real = OBJ_REAL->make;
my $x = freeze $real;

my $y = thaw $x;
ok ref $y eq 'OBJ_REAL';
ok $y->[0] eq 'a';
ok $y->[1] == 1;

my $sync = OBJ_SYNC->make;
$x = freeze $sync;
ok 1;

$y = thaw $x;
ok 1;
ok $y->{?ok} \== $y;

my $ext = \@(1, 2);
$sync = OBJ_SYNC2->make($ext);
$x = freeze \@($sync, $ext);
ok 1;

my $z = thaw $x;
$y = $z->[0];
ok 1;
ok $y->{?ok} \== $y;
ok ref $y->{?sync} eq 'OBJ_SYNC';
ok $y->{?ext} \== $z->[1];

$real = OBJ_REAL2->make;
$x = freeze $real;
ok 1;
ok $OBJ_REAL2::recursed == $OBJ_REAL2::MAX;
ok $OBJ_REAL2::hook_called == $OBJ_REAL2::MAX;

$y = thaw $x;
ok 1;
ok $OBJ_REAL2::recursed == 0;

$x = dclone $real;
ok 1;
ok ref $x eq 'OBJ_REAL2';
ok $OBJ_REAL2::recursed == 0;
ok $OBJ_REAL2::hook_called == 2 * $OBJ_REAL2::MAX;

ok !Storable::is_storing;
ok !Storable::is_retrieving;

#
# The following was a test-case that Salvador Ortiz Garcia <sog@msg.com.mx>
# sent me, along with a proposed fix.
#

package Foo;

sub new {
	my $class = shift;
	my $dat = shift;
	return bless \%(dat => $dat), $class;
}

package Bar;
sub new {
	my $class = shift;
	return bless \%(
		a => 'dummy',
		b => \@( 
			Foo->new(1),
			Foo->new(2), # Second instance of a Foo 
		)
	), $class;
}

sub STORABLE_freeze($self,$clonning) {
	return @( "$self->{?a}", $self->{?b} );
}

sub STORABLE_thaw($self,$clonning,$dummy,$o) {
	$self->{+a} = $dummy;
	$self->{+b} = $o;
}

package main;

my $bar = Bar->new();
my $bar2 = thaw freeze $bar;

ok ref($bar2) eq 'Bar';
ok ref($bar->{b}->[0]) eq 'Foo';
ok ref($bar->{b}->[1]) eq 'Foo';
ok ref($bar2->{b}->[0]) eq 'Foo';
ok ref($bar2->{b}->[1]) eq 'Foo';

#
# The following attempts to make sure blessed objects are blessed ASAP
# at retrieve time.
#

package CLASS_1;

sub make {
	my $self = bless \%(), shift;
	return $self;
}

package CLASS_2;

sub make($class, $o) {
	my $self = bless \%(), $class;

	$self->{+c1} = CLASS_1->make();
	$self->{+o} = $o;
	$self->{+c3} = bless CLASS_1->make(), "CLASS_3";
	$o->set_c2($self);
	return $self;
}

sub STORABLE_freeze($self, $clonning) {
	return @( "", $self->{?c1}, $self->{?c3}, $self->{?o} );
}

sub STORABLE_thaw($self, $clonning, $frozen, $c1, $c3, $o) {
	main::ok ref $self eq "CLASS_2";
	main::ok ref $c1 eq "CLASS_1";
	main::ok ref $c3 eq "CLASS_3";
	main::ok ref $o eq "CLASS_OTHER";
	$self->{+c1} = $c1;
	$self->{+c3} = $c3;
}

package CLASS_OTHER;

sub make {
	my $self = bless \%(), shift;
	return $self;
}

sub set_c2 { @_[0]->{+c2} = @_[1] }

#
# Is the reference count of the extra references returned from a
# STORABLE_freeze hook correct? [ID 20020601.005]
#
package Foo2;

sub new {
	my $self = bless \%(), @_[0];
	$self->{+freezed} = dump::view($self);
	return $self;
}

sub DESTROY {
	my $self = shift;
	$main::refcount_ok = 1 unless dump::view($self) eq $self->{?freezed};
}

package Foo3;

sub new {
	bless \%(), @_[0];
}

sub STORABLE_freeze {
	my $obj = shift;
	return @("", $obj, Foo2->new);
}

sub STORABLE_thaw { } # Not really used

package main;
our ($refcount_ok);

my $o = CLASS_OTHER->make();
my $c2 = CLASS_2->make($o);
my $so = thaw freeze $o;

$refcount_ok = 0;
thaw freeze(Foo3->new);
ok $refcount_ok == 1;