| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Benchmark::Serialize; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 19976 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 4 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 54 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | =head1 NAME | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | Benchmark::Serialize - Benchmarks of serialization modules | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | =head1 VERSION | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | Version 0.08 | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | =cut | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | our $VERSION = '0.08'; | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | use Benchmark::Serialize qw(cmpthese); | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | my $structure = { | 
| 23 |  |  |  |  |  |  | array  => [ 'a' .. 'j' ], | 
| 24 |  |  |  |  |  |  | hash   => { 'a' .. 'z' }, | 
| 25 |  |  |  |  |  |  | string => 'x' x 200, | 
| 26 |  |  |  |  |  |  | }; | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | cmpthese( -5, $structure, qw(:core :json :yaml) ); | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | This module encapsulates some basic benchmarks to help you choose a module | 
| 33 |  |  |  |  |  |  | for serializing data. Note that using this module is only a part of chosing a | 
| 34 |  |  |  |  |  |  | serialization format. Other factors than the benchmarked might be of | 
| 35 |  |  |  |  |  |  | relevance! | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | Included is support for 24 different serialization modules. Also supported | 
| 38 |  |  |  |  |  |  | is the Data::Serializer wrapper providing a unified interface for | 
| 39 |  |  |  |  |  |  | serialization and some extra features. Benchmarking of specialized modules | 
| 40 |  |  |  |  |  |  | made with Protocol Buffers for Perl/XS (protobuf-perlxs) is also available. | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | =head2 Functions | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | This module provides the following functions | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | =over 5 | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | =item cmpthese(COUNT, STRUCTURE, BENCHMARKS ...) | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | Benchmark COUNT interations of a list of modules. A benchmark is either a name | 
| 51 |  |  |  |  |  |  | of a supported module, a tag, or a hash ref containing at least an inflate, a | 
| 52 |  |  |  |  |  |  | deflate, and a name attribute: | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | { | 
| 55 |  |  |  |  |  |  | name    => 'JSON::XS', | 
| 56 |  |  |  |  |  |  | deflate => sub { JSON::XS::encode_json($_[0]) } | 
| 57 |  |  |  |  |  |  | inflate => inflate  => sub { JSON::XS::decode_json($_[0]) } | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | By default Benchmark::Serialize will try to use the name attribute as a module | 
| 61 |  |  |  |  |  |  | to be loaded. This can be overridden by having a packages attribute with an | 
| 62 |  |  |  |  |  |  | arrayref containing modules to be loaded. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =back | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | =head2 Benchmark tags | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | The following tags are supported | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | =over 5 | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | =item :all     - All modules with premade benchmarks | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | =item :default - A default set of serialization modules | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | =item :core    - Serialization modules included in core | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | =item :json    - JSON modules | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | =item :yaml    - YAML modules | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | =item :xml     - XML formats | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | =back | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | =cut | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 1 |  |  | 1 |  | 811 | use Benchmark          qw[timestr]; | 
|  | 1 |  |  |  |  | 7107 |  | 
|  | 1 |  |  |  |  | 5 |  | 
| 89 | 1 |  |  | 1 |  | 865 | use Test::Deep::NoTest; | 
|  | 1 |  |  |  |  | 18763 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 1 |  |  | 1 |  | 868 | use Benchmark::Serialize::Library; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 13 |  | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 1 |  |  | 1 |  | 44 | use Exporter qw(import); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 1883 |  | 
| 94 |  |  |  |  |  |  | our @EXPORT_OK   = qw( cmpthese ); | 
| 95 |  |  |  |  |  |  | our %EXPORT_TAGS = ( all => \@EXPORT_OK ); | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | our $benchmark_deflate  = 1;       # boolean | 
| 98 |  |  |  |  |  |  | our $benchmark_inflate  = 1;       # boolean | 
| 99 |  |  |  |  |  |  | our $benchmark_roundtrip= 1;       # boolean | 
| 100 |  |  |  |  |  |  | our $benchmark_size     = 1;       # boolean | 
| 101 |  |  |  |  |  |  | our $verbose            = 0;       # boolean | 
| 102 |  |  |  |  |  |  | our $output             = 'chart'; # chart or list | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | sub cmpthese { | 
| 105 | 0 |  |  | 0 | 1 |  | my $iterations = shift; | 
| 106 | 0 |  |  |  |  |  | my $structure  = shift; | 
| 107 | 0 |  |  |  |  |  | my @benchmarks = Benchmark::Serialize::Library->load( @_ ); | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 0 |  |  |  |  |  | my $width   = width(map { $_->name } @benchmarks); | 
|  | 0 |  |  |  |  |  |  | 
| 110 | 0 |  |  |  |  |  | my $results = { }; | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 0 |  |  |  |  |  | print "\nModules\n"; | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 0 |  |  |  |  |  | BENCHMARK: | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 0 |  |  |  |  |  | foreach my $benchmark ( sort { $a->name cmp $b->name } @benchmarks ) { | 
| 117 | 0 |  |  |  |  |  | my $name = $benchmark->name; | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 0 |  |  |  |  |  | my ($deflated, $inflated); | 
| 120 |  |  |  |  |  |  | eval { | 
| 121 | 0 |  |  |  |  |  | $deflated = $benchmark->deflate($structure); | 
| 122 | 0 |  |  |  |  |  | $inflated = $benchmark->inflate($deflated); | 
| 123 | 0 |  |  |  |  |  | 1; | 
| 124 | 0 | 0 |  |  |  |  | } or do { | 
| 125 | 0 |  |  |  |  |  | warn "Benchmark $name died with:\n    $@\n"; | 
| 126 | 0 |  |  |  |  |  | next BENCHMARK; | 
| 127 |  |  |  |  |  |  | }; | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 0 |  |  |  |  |  | my ($likeliness, $diag) = likeliness( $inflated, $structure ); | 
| 130 | 0 |  |  |  |  |  | printf( "%-${width}s : %8s %s\n", $benchmark->name, $benchmark->version, $likeliness); | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 0 | 0 | 0 |  |  |  | print Test::Deep::deep_diag($diag), "\n" if defined($diag) and $Benchmark::Serialize::verbose; | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 0 | 0 |  |  |  |  | $results->{deflate}->{$name} = timeit_deflate( $iterations, $structure, $benchmark ) | 
| 135 |  |  |  |  |  |  | if $benchmark_deflate; | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 0 | 0 |  |  |  |  | $results->{inflate}->{$name} = timeit_inflate( $iterations, $structure, $benchmark ) | 
| 138 |  |  |  |  |  |  | if $benchmark_inflate; | 
| 139 |  |  |  |  |  |  |  | 
| 140 | 0 | 0 |  |  |  |  | $results->{roundtrip}->{$name} = timeit_roundtrip( $iterations, $structure, $benchmark ) | 
| 141 |  |  |  |  |  |  | if $benchmark_roundtrip; | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 0 |  |  |  |  |  | $results->{size}->{$name}    = length( $deflated ); | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 | 0 | 0 |  |  |  |  | output( 'Sizes', "size", $output, $results->{size}, $width ) | 
| 147 |  |  |  |  |  |  | if $benchmark_size; | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 0 | 0 |  |  |  |  | output( 'Deflate (perl -> serialized)', "time", $output, $results->{deflate}, $width ) | 
| 150 |  |  |  |  |  |  | if $benchmark_deflate; | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 | 0 |  |  |  |  | output( 'Inflate (serialized -> perl)', "time", $output, $results->{inflate}, $width ) | 
| 153 |  |  |  |  |  |  | if $benchmark_inflate; | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 0 | 0 |  |  |  |  | output( 'Roundtrip', "time", $output, $results->{roundtrip}, $width ) | 
| 156 |  |  |  |  |  |  | if $benchmark_roundtrip; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | sub output { | 
| 160 | 0 |  |  | 0 | 0 |  | my $title  = shift; | 
| 161 | 0 |  |  |  |  |  | my $type   = shift; | 
| 162 | 0 |  |  |  |  |  | my $output = shift; | 
| 163 | 0 |  |  |  |  |  | printf( "\n%s\n", $title ); | 
| 164 | 0 | 0 |  |  |  |  | if ( $type eq "size" ) { | 
|  |  | 0 |  |  |  |  |  | 
| 165 | 0 | 0 |  |  |  |  | ($output eq "list") ? &size_list : &size_chart ; | 
| 166 |  |  |  |  |  |  | } elsif ( $type eq "time" ) { | 
| 167 | 0 | 0 |  |  |  |  | ($output eq "list") ? &time_list : &time_chart ; | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | } else { | 
| 170 | 0 |  |  |  |  |  | warn("Unknown data type: $type"); | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | sub time_chart { | 
| 175 | 0 |  |  | 0 | 0 |  | my $results = shift; | 
| 176 | 0 |  |  |  |  |  | Benchmark::cmpthese($results); | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | sub time_list { | 
| 180 | 0 |  |  | 0 | 0 |  | my $results = shift; | 
| 181 | 0 |  |  |  |  |  | my $width   = shift; | 
| 182 | 0 |  |  |  |  |  | foreach my $title ( sort keys %{ $results } ) { | 
|  | 0 |  |  |  |  |  |  | 
| 183 | 0 |  |  |  |  |  | printf( "%-${width}s %s\n", $title, timestr( $results->{ $title } ) ); | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | sub size_chart { | 
| 188 | 0 |  |  | 0 | 0 |  | my $results = shift; | 
| 189 | 0 |  |  |  |  |  | my @vals    = sort { $a->[1] <=> $b->[1] } map { [ $_, $results->{$_} ] } keys %$results; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 0 |  |  |  |  |  | my @rows    = ( [ | 
| 192 |  |  |  |  |  |  | '', | 
| 193 |  |  |  |  |  |  | 'bytes', | 
| 194 | 0 |  |  |  |  |  | map { $_->[0] } @vals, | 
| 195 |  |  |  |  |  |  | ] ); | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 0 |  |  |  |  |  | my @col_width = map { length ( $_ ) } @{ $rows[0] }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 0 |  |  |  |  |  | for my $row_val ( @vals ) { | 
| 200 | 0 |  |  |  |  |  | my @row; | 
| 201 |  |  |  |  |  |  |  | 
| 202 | 0 |  |  |  |  |  | push @row, $row_val->[0], $row_val->[1]; | 
| 203 | 0 | 0 |  |  |  |  | $col_width[0] = ( length ( $row_val->[0] ) > $col_width[0] ? length( $row_val->[0] ) : $col_width[0] ); | 
| 204 | 0 | 0 |  |  |  |  | $col_width[1] = ( length ( $row_val->[1] ) > $col_width[1] ? length( $row_val->[1] ) : $col_width[1] ); | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | # Columns 2..N = performance ratios | 
| 207 | 0 |  |  |  |  |  | for my $col_num ( 0 .. $#vals ) { | 
| 208 | 0 |  |  |  |  |  | my $col_val = $vals[$col_num]; | 
| 209 | 0 |  |  |  |  |  | my $out; | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 0 | 0 |  |  |  |  | if ( $col_val->[0] eq $row_val->[0] ) { | 
| 212 | 0 |  |  |  |  |  | $out = "--"; | 
| 213 |  |  |  |  |  |  | } else { | 
| 214 | 0 |  |  |  |  |  | $out = sprintf( "%.0f%%", 100*$row_val->[1]/$col_val->[1] - 100 ); | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 | 0 |  |  |  |  |  | push @row, $out; | 
| 218 | 0 | 0 |  |  |  |  | $col_width[$col_num+2] = ( length ( $out ) > $col_width[$col_num+2] ? length ( $out ) : $col_width[$col_num+2]); | 
| 219 |  |  |  |  |  |  | } | 
| 220 | 0 |  |  |  |  |  | push @rows, \@row; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | # Pasted from Benchmark.pm | 
| 224 |  |  |  |  |  |  | # Equalize column widths in the chart as much as possible without | 
| 225 |  |  |  |  |  |  | # exceeding 80 characters.  This does not use or affect cols 0 or 1. | 
| 226 | 0 |  |  |  |  |  | my @sorted_width_refs = | 
| 227 | 0 |  |  |  |  |  | sort { $$a <=> $$b } map { \$_ } @col_width[2..$#col_width]; | 
|  | 0 |  |  |  |  |  |  | 
| 228 | 0 |  |  |  |  |  | my $max_width = ${$sorted_width_refs[-1]}; | 
|  | 0 |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 0 |  |  |  |  |  | my $total = @col_width - 1 ; | 
| 231 | 0 |  |  |  |  |  | for ( @col_width ) { $total += $_ } | 
|  | 0 |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | STRETCHER: | 
| 234 | 0 |  |  |  |  |  | while ( $total < 80 ) { | 
| 235 | 0 |  |  |  |  |  | my $min_width = ${$sorted_width_refs[0]}; | 
|  | 0 |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | last | 
| 237 | 0 | 0 |  |  |  |  | if $min_width == $max_width; | 
| 238 | 0 |  |  |  |  |  | for ( @sorted_width_refs ) { | 
| 239 |  |  |  |  |  |  | last | 
| 240 | 0 | 0 |  |  |  |  | if $$_ > $min_width; | 
| 241 | 0 |  |  |  |  |  | ++$$_; | 
| 242 | 0 |  |  |  |  |  | ++$total; | 
| 243 |  |  |  |  |  |  | last STRETCHER | 
| 244 | 0 | 0 |  |  |  |  | if $total >= 80; | 
| 245 |  |  |  |  |  |  | } | 
| 246 |  |  |  |  |  |  | } | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | # Dump the output | 
| 249 | 0 |  |  |  |  |  | my $format = join( ' ', map { "%${_}s" } @col_width ) . "\n"; | 
|  | 0 |  |  |  |  |  |  | 
| 250 | 0 |  |  |  |  |  | substr( $format, 1, 0 ) = '-'; | 
| 251 | 0 |  |  |  |  |  | for ( @rows ) { | 
| 252 | 0 |  |  |  |  |  | printf $format, @$_; | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | sub size_list { | 
| 257 | 0 |  |  | 0 | 0 |  | my $results = shift; | 
| 258 | 0 |  |  |  |  |  | my $width   = shift; | 
| 259 | 0 |  |  |  |  |  | foreach my $title ( sort keys %{ $results } ) { | 
|  | 0 |  |  |  |  |  |  | 
| 260 | 0 |  |  |  |  |  | printf( "%-${width}s : %d bytes\n", $title, $results->{ $title } ); | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | sub timeit_deflate { | 
| 265 | 0 |  |  | 0 | 0 |  | my ( $iterations, $structure, $benchmark ) = @_; | 
| 266 | 0 |  |  | 0 |  |  | return Benchmark::timethis( $iterations, sub { $benchmark->deflate($structure) }, '', 'none' ); | 
|  | 0 |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | sub timeit_inflate { | 
| 270 | 0 |  |  | 0 | 0 |  | my ( $iterations, $structure, $benchmark ) = @_; | 
| 271 | 0 |  |  |  |  |  | my $deflated = $benchmark->deflate($structure); | 
| 272 | 0 |  |  | 0 |  |  | return Benchmark::timethis( $iterations, sub { $benchmark->inflate($deflated) }, '', 'none' ); | 
|  | 0 |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | sub timeit_roundtrip { | 
| 276 | 0 |  |  | 0 | 0 |  | my ( $iterations, $structure, $benchmark ) = @_; | 
| 277 | 0 |  |  | 0 |  |  | return Benchmark::timethis( $iterations, sub { $benchmark->inflate( $benchmark->deflate( $structure )) }, '', 'none' ); | 
|  | 0 |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | sub width { | 
| 281 | 0 |  |  | 0 | 0 |  | return length( ( sort { length $a <=> length $b } @_ )[-1] ); | 
|  | 0 |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | } | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | sub likeliness { | 
| 285 | 0 |  |  | 0 | 0 |  | my ($got, $expected) = @_; | 
| 286 | 0 |  |  |  |  |  | my ($ok, $diag); | 
| 287 |  |  |  |  |  |  |  | 
| 288 | 0 |  |  |  |  |  | ($ok, $diag) = Test::Deep::cmp_details( $got, $expected ); | 
| 289 | 0 | 0 |  |  |  |  | return ("Identical", undef) if $ok; | 
| 290 |  |  |  |  |  |  |  | 
| 291 | 0 |  |  |  |  |  | ($ok, $diag) = Test::Deep::cmp_details( $got, noclass($expected) ); | 
| 292 | 0 | 0 |  |  |  |  | return ("Changes blessing", undef) if $ok; | 
| 293 |  |  |  |  |  |  |  | 
| 294 | 0 |  |  |  |  |  | ($ok, $diag) = Test::Deep::cmp_details( $got, noclass(superhashof $expected) ); | 
| 295 | 0 | 0 |  |  |  |  | return ("Adds content", undef) if $ok; | 
| 296 |  |  |  |  |  |  |  | 
| 297 | 0 |  |  |  |  |  | ($ok, $diag) = Test::Deep::cmp_details( $got, noclass(subhashof $expected) ); | 
| 298 | 0 | 0 |  |  |  |  | return ("Removes content", undef) if $ok; | 
| 299 |  |  |  |  |  |  |  | 
| 300 | 0 |  |  |  |  |  | return ("Changes content", $diag); | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | =head1 RESULTS | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | See the README file for example results. | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | =head1 AUTHOR | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | Peter Makholm, C<<  >> | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | =head1 BUGS | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | Please report any bugs or feature requests to C | 
| 314 |  |  |  |  |  |  | rt.cpan.org>, or through | 
| 315 |  |  |  |  |  |  | the web interface at | 
| 316 |  |  |  |  |  |  | L.  I will | 
| 317 |  |  |  |  |  |  | be notified, and then you'll | 
| 318 |  |  |  |  |  |  | automatically be notified of progress on your bug as I make changes. | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | This module started out as a script written by Christian Hansen, see | 
| 323 |  |  |  |  |  |  | http://idisk.mac.com/christian.hansen/Public/perl/serialize.pl | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | Copyright 2009 Peter Makholm. | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 330 |  |  |  |  |  |  | under the terms of either: the GNU General Public License as published | 
| 331 |  |  |  |  |  |  | by the Free Software Foundation; or the Artistic License. | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | See http://dev.perl.org/licenses/ for more information. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | =cut | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | 1; |