Clone

 view release on metacpan or  search on metacpan

t/14-weakened-ref.t  view on Meta::CPAN

#!/usr/bin/perl
use strict;
use warnings;

use Test::More;
use Clone qw(clone);

BEGIN {
    eval 'use Scalar::Util qw( weaken isweak );';
    if ($@) {
        plan skip_all => "Scalar::Util::weaken not available";
        exit;
    }
}

plan tests => 16;

# GH #15 - Weakened refs always clone as undef
# When cloning a structure with weakened references, Clone should
# preserve the weakness and keep referents alive when strong
# references to them exist elsewhere in the clone graph.

{
    package Parent;
    sub new { bless { children => [] }, shift }

    package Child;
    sub new {
        my ($class, $parent) = @_;
        my $child = bless { parent => $parent }, $class;
        Scalar::Util::weaken($child->{parent});
        push @{ $parent->{children} }, $child;
        return $child;
    }
}

# Test 1: Clone from parent side (strong ref to child, weak ref back)
# This is the primary fix: when the referent has strong references
# elsewhere in the clone graph, the weakened ref should survive.
{
    my $p = Parent->new();
    my $c = Child->new($p);

    my $p_clone = clone($p);

    ok(defined $p_clone->{children}[0]{parent},
       'weakened ref survives when cloning from parent side');
    isnt($p_clone, $p, 'cloned parent is a different object');
    is($p_clone->{children}[0]{parent}, $p_clone,
       'cloned child points to cloned parent (not original)');
    ok(Scalar::Util::isweak($p_clone->{children}[0]{parent}),
       'weakened ref is still weak after cloning parent');
}

# Test 2: Multiple children with weakened refs to same parent
{
    my $p = Parent->new();
    my $c1 = Child->new($p);
    my $c2 = Child->new($p);

    my $p_clone = clone($p);

    ok(defined $p_clone->{children}[0]{parent},
       'first child weakened ref survives');
    ok(defined $p_clone->{children}[1]{parent},
       'second child weakened ref survives');
    is($p_clone->{children}[0]{parent}, $p_clone->{children}[1]{parent},
       'both children point to same cloned parent');
    ok(Scalar::Util::isweak($p_clone->{children}[0]{parent}),
       'first child ref is weak');
    ok(Scalar::Util::isweak($p_clone->{children}[1]{parent}),
       'second child ref is weak');
}

# Test 3: Hash with both strong and weak ref to same target
{
    my $data = { value => 42 };
    my $holder = { strong => $data, weak => $data };
    weaken($holder->{weak});

    my $cloned = clone($holder);

    ok(defined $cloned->{weak}, 'weakened hash value survives when strong ref exists');
    is($cloned->{weak}{value}, 42, 'weakened ref points to correct data');
    is($cloned->{strong}, $cloned->{weak},
       'strong and weak refs point to same cloned object');
    ok(!Scalar::Util::isweak($cloned->{strong}),
       'strong ref remains strong');
    ok(Scalar::Util::isweak($cloned->{weak}),
       'weak ref remains weak');
}

# Test 4: Standalone weakened ref with no strong ref in clone graph
# When the referent has no strong references in the clone graph,
# the weakened ref correctly becomes undef (same as Storable::dclone).
{
    my $p = Parent->new();
    my $c = Child->new($p);

    my $c_clone = clone($c);

    ok(!defined $c_clone->{parent},
       'weakened ref to object with no strong ref in clone becomes undef');
    # This is correct: the cloned parent has no independent strong reference,
    # so it gets collected when the weak reference is established.
}

# Test 5: Circular weak reference (existing test from t/06-refcnt.t)
{
    my $a = bless {}, 'Test::Circular';
    my $b = { r => $a };
    $a->{r} = $b;
    weaken($b->{'r'});

    my $c = clone($a);
    ok(defined $c->{r}, 'circular weak reference survives cloning');
}



( run in 2.240 seconds using v1.01-cache-2.11-cpan-364913b4093 )