package # Hide from PAUSE
Sereal::TestSet;
use strict;
use warnings;
use File::Spec;
use Scalar::Util qw(weaken);
use Test::More;
use Test::LongString;
#use Data::Dumper; # MUST BE LOADED *AFTER* THIS FILE (BUG IN PERL)
use Devel::Peek;
use Encode qw(encode_utf8);
# Dynamically load constants from whatever is being tested
our ($Class, $ConstClass);
BEGIN {
if (defined $INC{"Sereal/Encoder.pm"}
and $INC{"Sereal/Encoder.pm"} =~ /\bblib\b/)
{
$Class = 'Sereal::Encoder';
}
else {
$Class = 'Sereal::Decoder';
}
$ConstClass = $Class . "::Constants";
eval "use $ConstClass ':all'; 1"
or do {
my $err = $@ || 'Zombie Error';
die "Failed to load/import constants from '$ConstClass': $err";
};
}
use Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
@BasicTests $Class $ConstClass
Header
FBIT
hobodecode
integer short_string varint array array_fbit
hash dump_bless
have_encoder_and_decoder
run_roundtrip_tests
write_test_files
$use_objectv
setup_tests
);
our %EXPORT_TAGS = (all => \@EXPORT_OK);
our $use_objectv = 1;
use constant FBIT => 128;
sub hobodecode {
open my $fh, "| $^X -Mblib=../Encoder -Mblib=../Decoder author_tools/hobodecoder.pl -e" or die $!;
print $fh @_;
close $fh;
}
sub array_head {
if ($_[0]>=16) {
return chr(SRL_HDR_REFN) . chr(SRL_HDR_ARRAY) . varint($_[0])
} else {
return chr(SRL_HDR_ARRAYREF + $_[0])
}
}
sub array {
array_head( 0+@_ ) . join("", @_)
}
sub array_fbit {
chr(SRL_HDR_REFN).
chr(SRL_HDR_ARRAY+FBIT) . varint(0+@_) . join("", @_)
}
sub hash_head {
my $ret;
my $len= int $_[0]/2;
if ($len >= 16) {
return chr(SRL_HDR_REFN) . chr(SRL_HDR_HASH) . varint($len)
} else {
return chr(SRL_HDR_HASHREF + $len)
}
}
sub hash {
hash_head(0+@_) . join("", @_)
}
sub dump_bless {
# this hack does not support UTF8 class names, but that's not supported by
# most releases of perl anyway
(
ref($_[1])
? (
$use_objectv
? chr(SRL_HDR_OBJECTV) . varint(${$_[1]})
: chr(SRL_HDR_OBJECT) . chr(SRL_HDR_COPY) . varint(${$_[1]})
)
:
chr(SRL_HDR_OBJECT).
(
(length($_[1]) >= SRL_MASK_SHORT_BINARY_LEN)
? chr(SRL_HDR_BINARY).varint(length($_[1])).$_[1]
: chr(length($_[1]) + SRL_HDR_SHORT_BINARY_LOW).$_[1]
)
)
. $_[0]
}
sub short_string {
die if length($_[0]) > SRL_MASK_SHORT_BINARY_LEN;
return chr(SRL_HDR_SHORT_BINARY_LOW + length($_[0])) . $_[0];
}
sub integer {
if ($_[0] < 0) {
return $_[0] < -16
? die("zigzag not implemented in test suite")
: chr(0b0001_0000 + abs($_[0]));
}
else {
return $_[0] > 15
? varint($_[0])
: chr(0b0000_0000 + $_[0]);
}
}
sub varint {
my $n = shift;
die "varint cannot be negative" if $n < 0;
my $out = '';
while ($n >= 0x80) {
$out .= chr( ($n & 0x7f) | 0x80 );
$n >>= 7;
}
$out .= chr($n);
return $out;
}
our $PROTO_VERSION;
sub Header {
my $proto_version = shift || $PROTO_VERSION;
my $user_data_blob = shift;
my $hdr_base = SRL_MAGIC_STRING . chr($proto_version||SRL_PROTOCOL_VERSION);
if (defined $user_data_blob) {
return $hdr_base . varint(1 + length($user_data_blob)) . chr(1) . $user_data_blob;
}
else {
return $hdr_base . chr(0);
}
}
sub offset {
my ($str)= @_;
Carp::confess("no protoversion") if !defined $PROTO_VERSION;
if ($PROTO_VERSION >= 2) {
return length($str)+1;
} else {
return length($str) + length Header($PROTO_VERSION);
}
}
sub offseti {
my ( $i )= @_;
if ($PROTO_VERSION >= 2) {
return $i + 1;
} else {
return $i + length Header($PROTO_VERSION);
}
}
sub debug_checks {
my ($data_ref, $encoded_ref, $decoded_ref) = @_;
if (defined $ENV{DEBUG_SEREAL}) {
note("Original data was: " . Data::Dumper::Dumper($$data_ref)) if defined $data_ref;
note("Encoded data is: " . (defined($$encoded_ref) ? $$encoded_ref : "<undef>")) if defined $encoded_ref;
note("Decoded data was: " . Data::Dumper::Dumper($$decoded_ref)) if defined $decoded_ref;
}
if (defined $ENV{DEBUG_DUMP}) {
Dump($$encoded_ref) if defined $encoded_ref;
Dump($$decoded_ref) if defined $decoded_ref;
}
if (defined $ENV{DEBUG_HOBO}) {
hobodecode($$encoded_ref) if defined $encoded_ref;
}
exit() if $ENV{DEBUG_FAIL_FATAL};
}
our @BasicTests;
sub setup_tests {
my ($proto_version)=@_;
$PROTO_VERSION= $proto_version if defined $proto_version;
my $ary_ref_for_repeating = [5,6];
my $scalar_ref_for_repeating = \9;
my $weak_thing; $weak_thing = [\$weak_thing, 1]; weaken($weak_thing->[0]);
my $unicode1= "Ba\xDF Ba\xDF"; my $unicode2= "\x{168}nix! \x{263a}"; utf8::upgrade($unicode1); utf8::upgrade($unicode2);
@BasicTests = (
# warning: this hardcodes the POS/NEG headers
[-16, chr(0b0001_0000), "encode -16"],
[-1, chr(0b0001_1111), "encode -1"],
[0, chr(0b0000_0000), "encode 0"],
[1, chr(0b0000_0001), "encode 1"],
[15, chr(0b0000_1111), "encode 15"],
[undef, chr(SRL_HDR_UNDEF), "encode undef"],
["", short_string(""), "encode empty string"],
["1", short_string("1"), "encode string '1'"],
["91a", short_string("91a"), "encode string '91a'"],
["abc" x 1000, chr(SRL_HDR_BINARY).varint(3000).("abc" x 1000), "long ASCII string"],
[\1, chr(SRL_HDR_REFN).chr(0b0000_0001), "scalar ref to int"],
[[], array(), "empty array ref"],
[[1,2,3], array(chr(0b0000_0001), chr(0b0000_0010), chr(0b0000_0011)), "array ref"],
[1000, chr(SRL_HDR_VARINT).varint(1000), "large int"],
[ [1..1000],
array(
(map chr, (1 .. SRL_POS_MAX_SIZE)),
(map chr(SRL_HDR_VARINT) . varint($_), ((SRL_POS_MAX_SIZE+1) .. 1000))
),
"array ref with pos and varints"
],
[{}, hash(), "empty hash ref"],
[{foo => "baaaaar"}, hash(short_string("foo"),short_string("baaaaar")), "simple hash ref"],
[
[qw(foooo foooo foooo)],
sub {
my $opt = shift;
if ($opt->{dedupe_strings} || $opt->{aliased_dedupe_strings}) {
my $d = array_head(3);
my $pos = offset($d);
my $tag = $opt->{aliased_dedupe_strings} ? SRL_HDR_ALIAS : SRL_HDR_COPY;
$d .= short_string("foooo") . chr($tag) . varint($pos)
. chr($tag) . varint($pos);
return $d;
}
else {
return array(short_string("foooo"),short_string("foooo"), short_string("foooo"));
}
},
"ary ref with repeated string"
],
[
[{foooo => "barrr"}, {barrr => "foooo"}],
array(hash(short_string("foooo"), short_string("barrr")),
hash(short_string("barrr"), short_string("foooo"))),
"ary ref of hash refs without repeated strings"
],
[
[{foooo => "foooo"}, {foooo2 => "foooo"}],
sub {
my $opt = shift;
if ($opt->{dedupe_strings} || $opt->{aliased_dedupe_strings}) {
my $tag = $opt->{aliased_dedupe_strings} ? SRL_HDR_ALIAS : SRL_HDR_COPY;
my $d = array_head(2) . hash_head(2) . short_string("foooo");
my $pos = offset($d);
$d .= short_string("foooo") . hash_head(2)
. short_string("foooo2")
. chr($tag) . varint($pos);
return $d;
}
else {
return array(hash(short_string("foooo"), short_string("foooo")),
hash(short_string("foooo2"), short_string("foooo"))),
}
},
"ary ref of hash refs with repeated strings"
],
[$scalar_ref_for_repeating, chr(SRL_HDR_REFN).chr(0b0000_1001), "scalar ref to constant"],
[[$scalar_ref_for_repeating, $scalar_ref_for_repeating],
do {
my $content = array_head(2);
$content .= chr(SRL_HDR_REFN);
my $pos = offset($content);
$content .= chr(0b1000_1001)
.chr(SRL_HDR_REFP)
.varint($pos)
;
$content
}, "repeated substructure (REFP): scalar ref"],
[[$ary_ref_for_repeating, $ary_ref_for_repeating],
do {
my $content = array_head(2);
my $pos = offset($content) + 1;
$content .= array_fbit(chr(0b0000_0101), chr(0b0000_0110))
.chr(SRL_HDR_REFP)
.varint($pos)
;
$content
}, "repeated substructure (REFP): array"],
[[\$ary_ref_for_repeating, [1, $ary_ref_for_repeating]],
do {
my $content = array_head(2) . chr(SRL_HDR_REFN);
my $pos = offset($content) + 1;
$content .= array_fbit(
chr(0b0000_0101),
chr(0b0000_0110)
)
. array(
chr(0b0000_0001),
chr(SRL_HDR_REFP) . varint($pos)
)
;
$content
}, "repeated substructure (REFP): asymmetric"],
[
$weak_thing,
chr(SRL_HDR_REFN)
. chr(SRL_HDR_ARRAY + FBIT) . varint(2)
. chr(SRL_HDR_PAD) . chr(SRL_HDR_REFN)
. chr(SRL_HDR_REFP) . varint(offseti(1))
. chr(0b0000_0001)
,
"weak thing copy (requires PAD)"
],
[
\$weak_thing,
chr(SRL_HDR_REFN)
. chr(SRL_HDR_REFN + FBIT)
. chr(SRL_HDR_ARRAY) . varint(2)
.chr(SRL_HDR_WEAKEN) . chr(SRL_HDR_REFP) . varint(offseti(1))
.chr(0b0000_0001)
,
"weak thing ref"
],
sub { \@_ } ->(
$weak_thing,
chr(SRL_HDR_REFN + FBIT)
.chr(SRL_HDR_ARRAY).varint(2)
.chr(SRL_HDR_WEAKEN).chr(SRL_HDR_REFP).varint(offseti(0))
.chr(0b0000_0001)
,
"weak thing (aliased root)"
),
[
do { my @array; $array[0]=\$array[1]; $array[1]=\$array[0]; \@array },
do {
my $content= array_head(2);
my $pos= offset($content);
$content
. chr(SRL_HDR_REFN + FBIT)
. chr(SRL_HDR_REFP + FBIT)
. varint( $pos )
. chr(SRL_HDR_ALIAS)
. varint($pos + 1)
},
"scalar cross"
],
[
do { my @array; $array[0]=\$array[1]; $array[1]=\$array[0]; weaken($array[1]); weaken($array[0]); \@array },
do {
my $content= array_head(2);
my $pos= offset($content);
$content
. chr(SRL_HDR_WEAKEN + FBIT)
. chr(SRL_HDR_REFN)
. chr(SRL_HDR_WEAKEN + FBIT)
. chr(SRL_HDR_REFP)
. varint($pos)
. chr(SRL_HDR_ALIAS)
. varint($pos+2)
},
"weak scalar cross"
],
[
bless([],"foo"),
dump_bless(array(), "foo"),
"bless [], 'foo' (2)"
],
[
do { my $qr= bless qr/foo/ix,"bar"; [ $qr, $qr ] },
do {
my $content= array_head(2);
my $pos= offset($content);
join("", $content,
chr(SRL_HDR_OBJECT),
short_string("bar"),
chr(SRL_HDR_REFN),
chr(SRL_HDR_REGEXP + FBIT),
short_string("foo"),
short_string("ix"),
chr(SRL_HDR_REFP),
varint($pos + 6 ),
)
},
"blessed regexp with reuse"
],
[
do { my $o1=bless [], "foo"; my $o2=bless [], "foo"; [ $o1, $o2, $o1, $o2 ] },
do {
my $content= array_head(4). chr(SRL_HDR_OBJECT);
my $pos= offset($content);
join("",$content,
short_string("foo"),
chr(SRL_HDR_REFN).chr(SRL_HDR_ARRAY + FBIT),varint(0),
chr( SRL_HDR_OBJECT + $use_objectv),
$use_objectv ? () : chr(SRL_HDR_COPY), varint($pos),
chr(SRL_HDR_REFN).chr(SRL_HDR_ARRAY + FBIT), varint(0),
chr(SRL_HDR_REFP),varint($pos + 5),
chr(SRL_HDR_REFP),varint($pos + 10),
)
},
"blessed arrays with reuse"
],
[
[bless([], "foo"), bless([], "foo")],
do {
my $content = array_head(2) . chr(SRL_HDR_OBJECT);
my $pos = offset($content);
$content .= short_string("foo")
. array()
. dump_bless( array(), \$pos )
;
$content
},
"reused classname empty array"
],
[
bless([bless {}, "foo"], "foo"),
do {
my $content = chr(SRL_HDR_OBJECT);
my $pos = offset($content);
$content .= short_string("foo")
. array_head(1)
. dump_bless(hash(), \$pos);
;
$content
},
"wrapped objects"
],
[
qr/foo/,
dump_bless(
chr(SRL_HDR_REFN)
.chr(SRL_HDR_REGEXP)
.short_string("foo")
.short_string(""),
"Regexp"
),
"qr/foo/"
],
[
qr/(?i-xsm:foo)/,
dump_bless(
chr(SRL_HDR_REFN)
.chr(SRL_HDR_REGEXP)
.short_string("(?i-xsm:foo)")
.short_string(""),
"Regexp"
),
"qr/(?i-xsm:foo)/"
],
[
qr/foo/i,
dump_bless(
chr(SRL_HDR_REFN)
.chr(SRL_HDR_REGEXP)
.short_string("foo")
.short_string("i"),
"Regexp"
),
"qr/foo/i"
],
[
[{foo => 1}, {foo => 2}],
sub {
my $opt = shift;
if ($opt->{no_shared_hashkeys}) {
return array(
hash(
short_string("foo"),
integer(1),
),
hash(
short_string("foo"),
integer(2),
),
);
}
else {
my $content= array_head(2);
return join(
"",
$content,
hash(
short_string("foo"),
integer(1),
),
hash(
chr(SRL_HDR_COPY) . varint(offset($content)+1),
integer(2),
),
)
}
},
"duplicate hash keys"
],
[
{ $unicode1 => $unicode2 },
hash(
chr(SRL_HDR_STR_UTF8) . varint(bytes::length($unicode1)) . encode_utf8($unicode1),
chr(SRL_HDR_STR_UTF8) . varint(bytes::length($unicode2)) . encode_utf8($unicode2),
),
"simple unicode hash key and value"
],
[
sub { \@_ }->(!1,!0),
array(chr(SRL_HDR_FALSE),chr(SRL_HDR_TRUE)),
"true/false"
]
);
}
sub get_git_top_dir {
my @dirs = (0, 1, 2);
for my $d (@dirs) {
my $tdir = File::Spec->catdir(map File::Spec->updir, 1..$d);
my $gdir = File::Spec->catdir($tdir, '.git');
return $tdir
if -d $gdir;
}
return();
}
sub have_encoder_and_decoder {
# $Class is the already-loaded class, so the one we're testing
my $need = $Class =~ /Encoder/ ? "Decoder" : "Encoder";
my $need_class = "Sereal::$need";
my %compat_versions = map {$_ => 1} $Class->_test_compat();
if (defined(my $top_dir = get_git_top_dir())) {
my $blib_dir = File::Spec->catdir($top_dir, 'Perl', $need, "blib");
if (-d $blib_dir) {
require blib;
blib->import($blib_dir);
}
}
eval "use $need_class; 1"
or do {
note("Could not locate $need_class for testing" . ($@ ? " (Exception: $@)" : ""));
return();
};
my $cmp_v = $need_class->VERSION;
$cmp_v =~ s/_//;
$cmp_v = sprintf("%.2f", int($cmp_v*100)/100);
if (not defined $cmp_v or not exists $compat_versions{$cmp_v}) {
note("Could not load correct version of $need_class for testing "
."(got: $cmp_v, needed any of ".join(", ", keys %compat_versions).")");
return();
}
return 1;
}
# max iv/uv logic taken from Storable tests
my $max_uv = ~0;
my $max_uv_m1 = ~0 ^ 1;
# Express it in this way so as not to use any addition, as 5.6 maths would
# do this in NVs on 64 bit machines, and we're overflowing IVs so can't use
# use integer.
my $max_iv_p1 = $max_uv ^ ($max_uv >> 1);
my $lots_of_9C = do {
my $temp = sprintf "%#x", ~0;
$temp =~ s/ff/9c/g;
local $^W;
no warnings;
eval $temp;
};
my $max_iv = ~0 >> 1;
my $min_iv = do {use integer; -$max_iv-1}; # 2s complement assumption
our @ScalarRoundtripTests = (
# name, structure
["undef", undef],
["small int", 3],
["small negative int", -8],
["largeish int", 100000],
["largeish negative int", -302001],
(
map {["integer: $_", $_]} (
# IV bounds of 8 bits
-1, 0, 1, -127, -128, -129, 42, 126, 127, 128, 129, 254, 255, 256, 257,
# IV bounds of 32 bits
-2147483647, -2147483648, -2147483649, 2147483646, 2147483647, 2147483648,
# IV bounds
$min_iv, do {use integer; $min_iv + 1}, do {use integer; $max_iv - 1},
$max_iv,
# UV bounds at 32 bits
0x7FFFFFFF, 0x80000000, 0x80000001, 0xFFFFFFFF, 0xDEADBEEF,
# UV bounds
$max_iv_p1, $max_uv_m1, $max_uv, $lots_of_9C,
)
),
["float", 0.2],
["short ascii string", "fooo"],
["short latin1 string", "Müller"],
["short utf8 string", do {use utf8; " עדיין ח"}],
["long ascii string", do{"abc" x 1000}],
["long latin1 string", "üll" x 1000],
["long utf8 string", do {use utf8; " עדיין חשב" x 1000}],
["long utf8 string with only ascii", do {use utf8; "foo" x 1000}],
["long utf8 string with only latin1 subset", do {use utf8; "üll" x 1000}],
["simple regexp", qr/foo/],
["regexp with inline modifiers", qr/(?i-xsm:foo)/],
["regexp with modifiers", qr/foo/i],
["float", 123013.139],
["negative float",-1234.59],
["small float",0.41],
["negative small float",-0.13],
["small int", 123],
["empty string", ''],
["simple array", []],
["empty hash", {}],
["simple hash", { foo => 'bar' }],
["undef value", { foo => bar => baz => undef }],
["simple array", [ 1 ]],
["nested simple", [ 1, [ 2 ] ] ],
["deep nest", [1,2,[3,4,{5=>6,7=>{8=>[]},9=>{}},{},[]]]],
["complex hash", {
foo => 123,
bar => -159.23 ,
'baz' =>"foo",
'bop \''=> "\10"
,'bop \'\\'=> "\x{100}" ,
'bop \'x\\x' =>"x\x{100}" , 'bing' => "x\x{100}",
x=>'y', z => 'p', i=> '1', l=>" \10", m=>"\10 ", n => " \10 ",
}],
["more complex", {
foo => [123],
"bar" => [-159.23 , { 'baz' => "foo", }, ],
'bop \''=> { "\10" => { 'bop \'\\'=> "\x{100}", h=>{
'bop \'x\\x' =>"x\x{100}" , 'bing' => "x\x{100}",
x=>'y',}, z => 'p' , } ,
i => '1' ,}, l=>" \10", m=>"\10 ", n => " \10 ",
o => undef ,p=>undef,
}],
['var strings', [ "\$", "\@", "\%" ]],
[ "quote keys", { "" => '"', "'" => "" }],
[ "ref to foo", \"foo" ],
[ "double ref to foo", \\"foo"],
[ "refy array", \\["foo"]],
[ "reffy hash", \\\{foo=>\"bar"}],
[ "blessed array", bless(\[],"foo")],
[ "utf8 string", "123\\277ABC\\x{DF}456"],
[ "escaped string", "\\012\345\267\145123\\277ABC\\x{DF}456"],
[ "more escapes", "\\0123\0124"],
[ "ref to undef", \undef],
[ "negative big num", -4123456789],
[ "positive big num", 4123456789],
);
use Storable qw(dclone);
our @RoundtripTests = (
@ScalarRoundtripTests,
["[{foo => 1}, {foo => 2}] - repeated hash keys",
[{foo => 1}, {foo => 2}] ],
(map {["scalar ref to " . $_->[0], (\($_->[1]))]} @ScalarRoundtripTests),
(map {["nested scalar ref to " . $_->[0], (\\($_->[1]))]} @ScalarRoundtripTests),
(map {["array ref to " . $_->[0], ([$_->[1]])]} @ScalarRoundtripTests),
(map {["hash ref to " . $_->[0], ({foo => $_->[1]})]} @ScalarRoundtripTests),
(map {["array ref to duplicate " . $_->[0], ([$_->[1], $_->[1]])]} @ScalarRoundtripTests),
(map {["array ref to aliases " . $_->[0], (sub {\@_}->($_->[1], $_->[1]))]} @ScalarRoundtripTests),
(map {["array ref to scalar refs to same " . $_->[0], ([\($_->[1]), \($_->[1])])]} @ScalarRoundtripTests),
);
if (eval "use Array::RefElem (av_store hv_store); 1") {
my $x= "alias!";
my (@av,%hv);
av_store(@av,0,$x);
av_store(@av,1,$x);
hv_store(%hv,"x", $x);
hv_store(%hv,"y", $x);
push @RoundtripTests,
[\@av,"alias in array"],
[\%hv,"alias in hash"],
[[\@av,\%hv,\$x], "alias hell"];
}
sub run_roundtrip_tests {
my ($proto_version) = @_;
my @proto_versions = ($proto_version ? ($proto_version) : qw(2 1));
for my $proto_version ($proto_version) {
my $suffix = $proto_version == 1 ? "_v1" : "";
for my $opt (
['plain', { } ],
['snappy', { snappy => 1 } ],
['snappy_incr', { snappy_incr => 1 } ],
['sort_keys', { sort_keys => 1 } ],
['dedupe_strings', { dedupe_strings => 1 } ],
['freeze/thaw', { freeze_callbacks => 1 } ],
) {
my ($name, $opts) = @$opt;
$name .= $suffix;
$opts->{use_protocol_v1} = 1 if $proto_version == 1;
$PROTO_VERSION= $proto_version;
setup_tests();
run_roundtrip_tests_internal($name, $opts);
}
}
}
sub run_roundtrip_tests_internal {
my ($ename, $opt, $encode_decode_callbacks) = @_;
my $decoder = Sereal::Decoder->new($opt);
my $encoder = Sereal::Encoder->new($opt);
foreach my $meth (
['functional',
sub {Sereal::Encoder::encode_sereal(shift, $opt)},
sub {Sereal::Decoder::decode_sereal(shift, $opt)}],
['object-oriented',
sub {$encoder->encode(shift)},
sub {$decoder->decode(shift)}],
['header-body',
sub {$encoder->encode(shift, 123456789)}, # header data is abitrary to stand out for debugging
sub {$decoder->decode(shift)}],
['header-only',
sub {$encoder->encode(987654321, shift)}, # body data is abitrary to stand out for debugging
sub {$decoder->decode_only_header(shift)}],
)
{
my ($mname, $enc, $dec) = @$meth;
next if $mname =~ /header/ and $opt->{use_protocol_v1};
foreach my $rt (@RoundtripTests) {
my ($name, $data) = @$rt;
my $encoded;
eval {$encoded = $enc->($data); 1}
or do {
my $err = $@ || 'Zombie error';
diag("Got error while encoding: $err");
};
ok(defined $encoded, "$name ($ename, $mname, encoded defined)")
or do {
debug_checks(\$data, \$encoded, undef);
next;
};
my $decoded;
eval {$decoded = $dec->($encoded); 1}
or do {
my $err = $@ || 'Zombie error';
diag("Got error while decoding: $err");
};
ok( defined($decoded) == defined($data), "$name ($ename, $mname, decoded definedness)")
or do {
debug_checks(\$data, \$encoded, undef);
next;
};
# Second roundtrip
my $encoded2;
eval {$encoded2 = $enc->($decoded); 1}
or do {
my $err = $@ || 'Zombie error';
diag("Got error while encoding the second time: $err");
};
ok(defined $encoded2, "$name ($ename, $mname, encoded2 defined)")
or do {
debug_checks(\$data, \$encoded, \$decoded);
next;
};
my $decoded2;
eval {$decoded2 = $dec->($encoded2); 1}
or do {
my $err = $@ || 'Zombie error';
diag("Got error while encoding the second time: $err");
};
ok(defined($decoded2) == defined($data), "$name ($ename, $mname, decoded2 defined)")
or next;
is_deeply($decoded, $data, "$name ($ename, $mname, decoded vs data)")
or do {
debug_checks(\$data, \$encoded2, \$decoded2);
};
is_deeply($decoded2, $data, "$name ($ename, $mname, decoded2 vs data)")
or do {
debug_checks(\$data, \$encoded2, \$decoded2);
};
is_deeply($decoded, $decoded2, "$name ($ename, $mname, decoded vs decoded2)")
or do {
debug_checks(\$data, \$encoded2, \$decoded2);
};
if (0) {
# It isnt really safe to test this way right now. The exact output
# of two runs of Sereal is not guaranteed to be the same due to the effect of
# refcounts. We could disable ARRAYREF/HASHREF as an option,
# and then skip these tests. We should probably do that just to test
# that we can handle both representations properly at all times.
my $ret;
if ($name=~/complex/) {
SKIP: {
skip "Encoded string length tests for complex hashes and compression depends on hash key ordering", 1 if $opt->{snappy};
$ret = is(length($encoded2), length($encoded),"$name ($ename, $mname, length encoded2 vs length encoded)");
}
} else {
$ret = is_string($encoded2, $encoded, "$name ($ename, $mname, encoded2 vs encoded)");
}
$ret or do {
debug_checks(\$data, \$encoded, \$decoded);
};
}
}
} # end serialization method iteration
}
# dumb data-to-file dumper
sub _write_file {
my ($file, $data) = @_;
open my $fh, ">", $file
or die "Failed to open file '$file' for writing: $!";
binmode($fh);
print $fh $data;
close $fh;
}
# For bootstrapping other language implementations' tests
sub write_test_files {
my $dir = shift;
require File::Path;
File::Path::mkpath($dir);
my $make_data_file_name = sub {File::Spec->catfile($dir, sprintf("test_data_%05u", shift))};
my $make_name_file_name = sub {File::Spec->catfile($dir, sprintf("test_name_%05u", shift))};
setup_tests();
foreach my $testno (1..@BasicTests) {
my $t = $BasicTests[$testno-1];
my $data = ref($t->[1]) eq 'CODE' ? $t->[1]->() : $t->[1];
_write_file($make_data_file_name->($testno), Header($PROTO_VERSION).$data);
_write_file($make_name_file_name->($testno), $t->[2] . "\n");
}
my $encoder = Sereal::Encoder->new;
foreach my $i (0..$#RoundtripTests) {
my $testno = @BasicTests + $i + 1;
my $t = $RoundtripTests[$i];
_write_file($make_data_file_name->($testno), $encoder->encode($t->[1]));
_write_file($make_name_file_name->($testno), $t->[0] . "\n");
}
}
1;