#!perl
use strict;
use warnings;
use Data::Dumper;
use File::Spec;
use Scalar::Util qw(blessed);
# These tests use an installed Decoder to do testing on horrific
# Perl data structures such as overloaded and tied structures.
use lib File::Spec->catdir(qw(t lib));
BEGIN {
lib->import('lib')
if !-d 't';
}
use Sereal::TestSet qw(:all);
use Test::More;
if (not have_encoder_and_decoder()) {
plan skip_all => 'Did not find right version of decoder';
exit 0;
}
Sereal::Encoder->import(":all");
Sereal::Decoder->import(":all");
# First, test tied hashes. Expected behaviour: We don't segfault, we don't
# throw exceptions (unless the tied hash is not iterable repeatedly),
# we serialize the tied hash as if it was a normal hash - so no trace of
# tiedness in the output.
{
SCOPE: {
package TiedHash;
require Tie::Hash;
our @ISA = qw(Tie::StdHash);
}
my %testhash = (
foo => [qw(a b c)],
baz => 123,
dfvgbnhmjk => "345ty6ujh",
a => undef,
);
my %tied_hash;
tie %tied_hash => 'TiedHash';
%{tied(%tied_hash)} = %testhash;
is_deeply(\%tied_hash, \%testhash);
my ($out, $ok, $err, $data);
$ok = eval {$out = encode_sereal(\%tied_hash); 1};
$err = $@ || 'Zombie error';
ok($ok, "serializing tied hash did not die")
or note("Error was '$err'");
ok(defined $out, "serializing tied hash returns string");
$ok = eval {$data = decode_sereal($out); 1;};
$err = $@ || 'Zombie error';
ok($ok, "deserializing tied hash did not die")
or note("Error was '$err', data was:\n"), hobodecode($out);
ok(defined $data, "deserializing tied hash yields defined output");
is_deeply($data, \%testhash, "deserializing tied hash yields expected output");
}
# Now tied arrays.
{
SCOPE: {
package TiedArray;
require Tie::Array;
our @ISA = qw(Tie::StdArray);
}
my @testarray = (1, 2, "foo", "bar", []);
my @tied_array;
tie @tied_array => 'TiedArray';
@{tied(@tied_array)} = @testarray;
is_deeply(\@tied_array, \@testarray);
my ($out, $ok, $err, $data);
$ok = eval {$out = encode_sereal(\@tied_array); 1};
$err = $@ || 'Zombie error';
ok($ok, "serializing tied array did not die")
or note("Error was '$err'");
ok(defined $out, "serializing tied array returns string");
$ok = eval {$data = decode_sereal($out); 1;};
$err = $@ || 'Zombie error';
ok($ok, "deserializing tied array did not die")
or note("Error was '$err', data was:\n"), hobodecode($out);
ok(defined $data, "deserializing tied array yields defined output");
is_deeply($data, \@testarray, "deserializing tied array yields expected output");
}
# Now tied scalars.
{
SCOPE: {
package TiedScalar;
require Tie::Scalar;
our @ISA = qw(Tie::StdScalar);
}
my $testscalar = [qw(foo bar baz)];
my $tied_scalar;
tie $tied_scalar => 'TiedScalar';
${tied($tied_scalar)} = $testscalar;
is_deeply($tied_scalar, $testscalar);
my ($out, $ok, $err, $data);
$ok = eval {$out = encode_sereal(\$tied_scalar); 1};
$err = $@ || 'Zombie error';
ok($ok, "serializing tied scalar did not die")
or note("Error was '$err'");
ok(defined $out, "serializing tied scalar returns string");
$ok = eval {$data = decode_sereal($out); 1;};
$err = $@ || 'Zombie error';
ok($ok, "deserializing tied scalar did not die")
or note("Error was '$err', data was:\n"), hobodecode($out);
ok(defined $data, "deserializing tied scalar yields defined output");
is_deeply($data, \$testscalar, "deserializing tied scalar yields expected output");
}
# Now test re-entrancy. DO NOT DO THIS AT HOME!
SCOPE: {
my $enc = Sereal::Encoder->new;
my $die_run = 0;
eval {
local $SIG{__DIE__} = sub {
$die_run++;
ok(defined($enc->encode("foo")), "encode does not segfault");
$die_run++;
};
$enc->encode(["foo", sub{}]);
};
ok($die_run == 2, "__DIE__ called, encode 2 did not die ($die_run)");
}
# github Sereal/Sereal issue 7 regression test:
SCOPE: {
{
package # hide from PAUSE
Blessed::Sub::With::Overload;
use overload '""' => sub { shift->() };
sub new { bless $_[1] => $_[0] }
}
{
package # hide from PAUSE
Blessed::Sub::With::Lazy::Overload;
use overload '""' => sub {
my ($self) = @_;
return $self->[1] if defined $self->[1];
return "OH NOES WE DON'T HAVE A SUB" unless ref $self->[0] eq 'CODE';
return ($self->[1] = $self->[0]->());
};
sub new {
bless [
# The callback
$_[1],
# Cached value
undef
] => $_[0]
}
}
my $data;
$data->[0] = sub {};
$data->[1] = $data->[0];
$data->[2] = Blessed::Sub::With::Overload->new(sub { "hello there" });
$data->[3] = $data->[2];
$data->[4] = Blessed::Sub::With::Overload->new(sub { \"hello there" });
$data->[5] = $data->[4];
my $called;
$data->[6] = Blessed::Sub::With::Overload->new(sub { $called++; "hello there" });
$data->[7] = $data->[6];
$data->[8] = $data->[6];
$data->[9] = $data->[6];
$data->[10] = Blessed::Sub::With::Lazy::Overload->new(sub { "hello there" });
$data->[11] = $data->[10];
my $encode = encode_sereal($data, {stringify_unknown => 1});
# Before 48d5cdc3dc07fd29ac7be05678a0b614244fec4f, we'd
# die here because $data->[1] is a ref to something that doesn't exist anymore
my $decode = decode_sereal($encode);
is($decode->[0], $decode->[1]);
is($decode->[2], $decode->[3]);
is($decode->[4], $decode->[5]);
is($decode->[6], $decode->[$_]) for 7..9;
is($called, 4, "We'll call the sub every time, and won't re-use the initial return value");
ok(blessed($decode->[10]), "We won't be stringifying objects");
like($decode->[10]->[0], qr/^CODE\(.*?\)$/, "And the subroutine we have will just be stringified as usual in Perl");
is("$decode->[10]", "OH NOES WE DON'T HAVE A SUB", "So our subroutine won't survive the roundtrip, our object is broken");
is_deeply($decode->[10], $decode->[11], "Both the original and the reference to it are equally screwed");
}
pass("Alive at end");
done_testing();