#!/usr/local/bin/perl -w
###############################################################################
# Purpose : Unit test for Hash::Flatten
# Author : John Alden
# Created : Feb 2002
# CVS : $Header: /home/cvs/software/cvsroot/hash_flatten/t/hash_flatten.t,v 1.21 2009/05/09 12:42:02 jamiel Exp $
###############################################################################
# -t : trace
# -T : deep trace into modules
###############################################################################
use strict;
use Test::Assertions qw(test);
use Getopt::Std;
use Log::Trace;
use vars qw($opt_t $opt_T);
getopts("tT");
plan tests;
#Compile the code
chdir($1) if($0 =~ /(.*)(\/|\\)(.*)/);
unshift @INC, "./lib", "../lib";
#Override warn() first, then compile
my $buf;
{
BEGIN {$^W = 0}
*CORE::GLOBAL::warn = sub {$buf = shift()};
require Hash::Flatten;
}
ASSERT($INC{'Hash/Flatten.pm'}, 'loaded');
import Log::Trace qw(print) if ($opt_t);
deep_import Log::Trace qw(print) if ($opt_T);
#############################################################
#
# Nested hashes
#
#############################################################
my $data =
{
'x' => 1,
'y' => {
'a' => 2,
'b' => {
'p' => 3,
'q' => 4
},
}
};
my $flat_data = {
'x' => 1,
'y.a' => 2,
'y.b.p' => 3,
'y.b.q' => 4
};
my $flat = Hash::Flatten::flatten($data);
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'nested hashes';
my $unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat);
ASSERT EQUAL($unflat, $data), 'nested hashes unflattened';
#############################################################
#
# Nested hashes with weird values
#
#############################################################
my $data =
{
'x' => 1,
'0' => {
'1' => 2,
'' => {
'' => 3,
'q' => 4
},
},
'a' => [1,2,3],
'' => [4,5,6],
};
my $flat_data = {
'x' => 1,
'0.1' => 2,
'0..' => 3,
'0..q' => 4,
'a:0' => 1,
'a:1' => 2,
'a:2' => 3,
':0' => 4,
':1' => 5,
':2' => 6,
};
my $flat = Hash::Flatten::flatten($data);
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'nested hashes with weird values';
my $unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat);
ASSERT EQUAL($unflat, $data), 'nested hashes with weird values unflattened';
#############################################################
#
# Mixed hashes/arrays
#
#############################################################
my $foo = 'hello';
$data =
{
'x' => 1,
'ay' => {
'a' => 2,
'b' => {
'p' => 3,
'q' => 4
},
},
's' => \\\$foo,
'y' => [
'a', 2,
{
'baz' => 'bum',
},
]
};
$flat_data = {
'ay*b*p' => 3,
'ay*b*q' => 4,
's' => 'hello',
'ay*a' => 2,
'y%2*baz' => 'bum',
'x' => 1,
'y%0' => 'a',
'y%1' => 2
};
$flat = Hash::Flatten::flatten($data, {'HashDelimiter' => '*', 'ArrayDelimiter' => '%'});
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'heterogeneous structure';
$unflat = Hash::Flatten::unflatten($flat, {'HashDelimiter' => '*', 'ArrayDelimiter' => '%'});
DUMP($unflat);
ASSERT EQUAL($unflat,
{ ### NB we can't compare to $data here because we flatten out scalar refs
'x' => 1,
'y' => [
'a',
2,
{
'baz' => 'bum'
}
],
'ay' => {
'a' => 2,
'b' => {
'p' => 3,
'q' => 4
}
},
's' => 'hello'
}
), 'heterogeneous structure unflattened';
#############################################################
#
# Deeply nested arrays
#
#############################################################
$data =
{
'x' => 1,
'y' => [
[
'a', 'fool', 'is',
],
[
'easily', [ 'parted', 'from' ], 'his'
],
'money',
]
};
$flat_data = {
'y:1:2' => 'his',
'x' => 1,
'y:1:1:0' => 'parted',
'y:1:1:1' => 'from',
'y:2' => 'money',
'y:0:0' => 'a',
'y:1:0' => 'easily',
'y:0:1' => 'fool',
'y:0:2' => 'is'
};
$flat = Hash::Flatten::flatten($data);
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'nested arrays';
$unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat);
ASSERT EQUAL($unflat, $data), 'nested arrays unflattened';
#############################################################
#
# Trivial cases
#
#############################################################
$data = {
};
$flat_data = {
};
$flat = Hash::Flatten::flatten($data);
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'empty hash';
$unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat);
ASSERT EQUAL($unflat, $data), 'empty hash unflattened';
$data = {
'x' => 1,
};
$flat_data = {
'x' => 1,
};
$flat = Hash::Flatten::flatten($data);
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), '1 key';
$unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat);
ASSERT EQUAL($unflat, $data), '1 key unflattened';
#############################################################
#
# Very long delimiters
#
###########################################################
$data =
{
'x' => 1,
'ay' => {
'a' => 2,
'b' => {
'p' => 3,
'q' => 4
},
},
's' => 'hey',
'y' => [
'a', 2, {
'baz' => 'bum',
},
]
};
$flat_data = {
'x' => 1,
's' => 'hey',
'ay*Issa Hash*a' => 2,
'y%This Is an Array!!%%2*Issa Hash*baz' => 'bum',
'ay*Issa Hash*b*Issa Hash*p' => 3,
'y%This Is an Array!!%%0' => 'a',
'ay*Issa Hash*b*Issa Hash*q' => 4,
'y%This Is an Array!!%%1' => 2
};
$flat = Hash::Flatten::flatten($data, {'HashDelimiter' => '*Issa Hash*', 'ArrayDelimiter' => '%This Is an Array!!%%'});
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'long delimiters';
$unflat = Hash::Flatten::unflatten($flat, {'HashDelimiter' => '*Issa Hash*', 'ArrayDelimiter' => '%This Is an Array!!%%'});
DUMP($unflat);
ASSERT EQUAL($unflat, $data), 'long delimiters unflattened';
###########################################################
#
# Scalar refs, blessed refs etc
#
###########################################################
my $scal = 'scalar';
my $again = 'again!';
$data = bless({
'x' => bless({'foo'=>'bar'}, 'Foo::Hash'),
'y' => bless(['f', 'g'], 'Bar::Array'),
'z' => bless(\$scal, 'Qux:Scalar'),
'r' => bless(\\\\\$again, 'Qux Ref'),
'rina' => [\$scal, \\$again],
'gref' => \*FH,
}, 'Template');
DUMP($data);
$flat_data = {
'z' => 'scalar',
'r' => 'again!',
'x.foo' => 'bar',
'y:0' => 'f',
'y:1' => 'g',
'rina:0' => 'scalar',
'rina:1' => 'again!',
'gref' => \*FH,
};
$flat = Hash::Flatten::flatten($data);
DUMP($flat);
ASSERT EQUAL($flat, $flat_data), 'blessed references';
$unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat);
ASSERT EQUAL($unflat, {
'x' => {
'foo' => 'bar'
},
'y' => [
'f',
'g'
],
'r' => 'again!',
'z' => 'scalar',
'rina' => ['scalar', 'again!'],
'gref' => \*FH,
}), 'objects and blessed refs unflattened';
###########################################################
#
# OO Interface and callbacks
#
###########################################################
my $counter = 0;
my $o = new Hash::Flatten({
'OnRefRef' => sub {
my $v = shift;
$counter++;
return $$v; #follow
},
'OnRefScalar' => sub {
my $v = shift;
$counter--;
return $$v; #follow
},
'OnRefGlob' => sub {
my $v = shift;
$counter--;
return "A-GLOB";
}
});
# Test coderef for handling refs
$flat = $o->flatten({a => \\\\\"x"});
DUMP($flat);
ASSERT($counter == 3, "coderef called $counter times");
$flat = $o->flatten({a => \*FH});
DUMP($flat);
ASSERT($counter == 2 && $flat->{a} eq 'A-GLOB', "globref callback");
###########################################################
#
# Escaping
#
###########################################################
my $orig = {
a => ['1.1', '1.2', '2.1'],
'b:c' => {e => '3.1'}
};
$flat = Hash::Flatten::flatten($orig);
DUMP($flat);
$unflat = Hash::Flatten::unflatten($flat);
DUMP($unflat, $orig);
ASSERT(EQUAL($orig, $unflat), "escaping");
$orig = {'a' => {'A[ESC]B' => 'c[ESC]', 'C.D' => 'd:e'}};
$flat = Hash::Flatten::flatten($orig, {EscapeSequence => '[ESC]'});
DUMP($flat);
$unflat = Hash::Flatten::unflatten($flat, {EscapeSequence => '[ESC]'});
DUMP($unflat, $orig);
ASSERT(EQUAL($orig, $unflat), "custom escape seq");
###########################################################
#
# Error checking
#
###########################################################
ASSERT(DIED( sub{ Hash::Flatten::flatten([1,2,3]) } ) && scalar $@ =~ /1st arg must be a hashref/, "type check in flatten");
ASSERT(DIED( sub{ Hash::Flatten::unflatten([1,2,3]) } ) && scalar $@ =~ /1st arg must be a hashref/, "type check in unflatten");
ASSERT(
DIED( sub{ Hash::Flatten::flatten({}, {EscapeSequence => '.'}) })
&& scalar $@ =~ /Hash delimiter cannot contain escape sequence/
, "check hash delim for esc seq");
ASSERT(
DIED( sub{ Hash::Flatten::flatten({}, {EscapeSequence => ':'}) })
&& scalar $@ =~ /Array delimiter cannot contain escape sequence/
, "check array delim for esc seq");
$data = {
'y' => {
'a' => 2,
'b' => 3
},
};
$data->{'y'}->{'c'} = $data;
DUMP($data);
ASSERT( DIED( sub { Hash::Flatten::flatten( $data ) } ), 'recursive data structure detected in hashref');
$data = {
'y' => {
'a' => 2,
'b' => 3
},
};
$data->{y}->{c} = \$data;
DUMP($data);
ASSERT( DIED( sub { Hash::Flatten::flatten( $data ) } ), "recursive data structure detected in ref-ref");
$data = {
'y' => {
'a' => 2,
'b' => 3
},
};
$data->{'y'}->{'c'} = [1];
push @{$data->{'y'}->{'c'}}, $data->{'y'}->{'c'};
DUMP($data);
ASSERT( DIED( sub { Hash::Flatten::flatten( $data ) } ), "recursive data structure detected in arrayref");
ASSERT(
DIED( sub{ Hash::Flatten::flatten({a => \[1,2]}, {OnRefRef => "die"}) })
&& scalar $@ =~ /is a REF/
, "check ref to ref raises exception");
ASSERT(
DIED( sub{ Hash::Flatten::flatten({a => \"x"}, {OnRefScalar => "die"}) })
&& scalar $@ =~ /is a SCALAR/
, "check ref to scalar raises exception");
ASSERT(
DIED( sub{ Hash::Flatten::flatten({a => \*FH}, {OnRefGlob => "die"}) })
&& scalar $@ =~ /is a GLOB/
, "check ref to glob raises exception");
my $rv = Hash::Flatten::flatten({a => \[1,2]}, {OnRefRef => "warn"});
DUMP($rv);
TRACE($buf);
ASSERT(scalar $buf =~ /is a REF and will be followed/ && EQUAL($rv, {
'a:0' => 1,
'a:1' => 2
}), "warn mode works as expected");
$rv = Hash::Flatten::flatten({a=>"m:o.o", "o:i.n:k" => {a=>1}},{EscapeSequence => "#", DisableEscapes => 0});
DUMP($rv);
ASSERT(
EQUAL($rv,{a => 'm:o.o','o#:i#.n#:k.a' => 1}),
"Escapes on, returned escaped hash"
);
$rv = Hash::Flatten::unflatten({a => 'm:o.o','o#:i#.n#:k.a' => 1},{EscapeSequence => "#", DisableEscapes => 0});
DUMP($rv);
ASSERT(
EQUAL($rv,{a=>"m:o.o", "o:i.n:k" => {a=>1}}),
"Escapes on, unescaped hash correctly"
);
$rv = Hash::Flatten::flatten({a=>"m:o.o", "o:i.n:k" => {a=>1}},{EscapeSequence => "#", DisableEscapes => 1});
DUMP($rv);
ASSERT(
EQUAL($rv,{a => 'm:o.o','o:i.n:k.a' => 1}),
"Escapes off, returned nonsense"
);
$rv = Hash::Flatten::unflatten({a => 'm:o.o','o#:i#.n#:k.a' => 1},{EscapeSequence => "#", DisableEscapes => 1});
DUMP($rv);
ASSERT(
EQUAL($rv,{a => 'm:o.o','o#' => [{'n#' => [{a => 1}]}]}),
"Escapes off, didn't unescape hash"
);