summaryrefslogtreecommitdiff
path: root/dist/Dumpvalue
diff options
context:
space:
mode:
authorFlorian Ragwitz <rafl@debian.org>2010-12-15 01:00:30 +0100
committerFlorian Ragwitz <rafl@debian.org>2010-12-15 01:14:17 +0100
commit6b217b36163b5443c055a23c59d582cd363bb7b1 (patch)
treea4e0afb0fdcdcb5436927b6ccb9ef249d749e970 /dist/Dumpvalue
parent9cc8ef8a050499e8a7c407263539c9b21d850880 (diff)
downloadperl-6b217b36163b5443c055a23c59d582cd363bb7b1.tar.gz
Dual-life Dumpvalue
Diffstat (limited to 'dist/Dumpvalue')
-rw-r--r--dist/Dumpvalue/lib/Dumpvalue.pm648
-rw-r--r--dist/Dumpvalue/t/Dumpvalue.t302
2 files changed, 950 insertions, 0 deletions
diff --git a/dist/Dumpvalue/lib/Dumpvalue.pm b/dist/Dumpvalue/lib/Dumpvalue.pm
new file mode 100644
index 0000000000..f1ec0fbfb4
--- /dev/null
+++ b/dist/Dumpvalue/lib/Dumpvalue.pm
@@ -0,0 +1,648 @@
+use 5.006_001; # for (defined ref) and $#$v and our
+package Dumpvalue;
+use strict;
+our $VERSION = '1.14';
+our(%address, $stab, @stab, %stab, %subs);
+
+# documentation nits, handle complex data structures better by chromatic
+# translate control chars to ^X - Randal Schwartz
+# Modifications to print types by Peter Gordon v1.0
+
+# Ilya Zakharevich -- patches after 5.001 (and some before ;-)
+
+# Won't dump symbol tables and contents of debugged files by default
+
+# (IZ) changes for objectification:
+# c) quote() renamed to method set_quote();
+# d) unctrlSet() renamed to method set_unctrl();
+# f) Compiles with `use strict', but in two places no strict refs is needed:
+# maybe more problems are waiting...
+
+my %defaults = (
+ globPrint => 0,
+ printUndef => 1,
+ tick => "auto",
+ unctrl => 'quote',
+ subdump => 1,
+ dumpReused => 0,
+ bareStringify => 1,
+ hashDepth => '',
+ arrayDepth => '',
+ dumpDBFiles => '',
+ dumpPackages => '',
+ quoteHighBit => '',
+ usageOnly => '',
+ compactDump => '',
+ veryCompact => '',
+ stopDbSignal => '',
+ );
+
+sub new {
+ my $class = shift;
+ my %opt = (%defaults, @_);
+ bless \%opt, $class;
+}
+
+sub set {
+ my $self = shift;
+ my %opt = @_;
+ @$self{keys %opt} = values %opt;
+}
+
+sub get {
+ my $self = shift;
+ wantarray ? @$self{@_} : $$self{pop @_};
+}
+
+sub dumpValue {
+ my $self = shift;
+ die "usage: \$dumper->dumpValue(value)" unless @_ == 1;
+ local %address;
+ local $^W=0;
+ (print "undef\n"), return unless defined $_[0];
+ (print $self->stringify($_[0]), "\n"), return unless ref $_[0];
+ $self->unwrap($_[0],0);
+}
+
+sub dumpValues {
+ my $self = shift;
+ local %address;
+ local $^W=0;
+ (print "undef\n"), return unless defined $_[0];
+ $self->unwrap(\@_,0);
+}
+
+# This one is good for variable names:
+
+sub unctrl {
+ local($_) = @_;
+
+ return \$_ if ref \$_ eq "GLOB";
+ s/([\001-\037\177])/'^'.pack('c',ord($1)^64)/eg;
+ $_;
+}
+
+sub stringify {
+ my $self = shift;
+ local $_ = shift;
+ my $noticks = shift;
+ my $tick = $self->{tick};
+
+ return 'undef' unless defined $_ or not $self->{printUndef};
+ return $_ . "" if ref \$_ eq 'GLOB';
+ { no strict 'refs';
+ $_ = &{'overload::StrVal'}($_)
+ if $self->{bareStringify} and ref $_
+ and %overload:: and defined &{'overload::StrVal'};
+ }
+
+ if ($tick eq 'auto') {
+ if (/[\000-\011\013-\037\177]/) {
+ $tick = '"';
+ } else {
+ $tick = "'";
+ }
+ }
+ if ($tick eq "'") {
+ s/([\'\\])/\\$1/g;
+ } elsif ($self->{unctrl} eq 'unctrl') {
+ s/([\"\\])/\\$1/g ;
+ s/([\000-\037\177])/'^'.pack('c',ord($1)^64)/eg;
+ s/([\200-\377])/'\\0x'.sprintf('%2X',ord($1))/eg
+ if $self->{quoteHighBit};
+ } elsif ($self->{unctrl} eq 'quote') {
+ s/([\"\\\$\@])/\\$1/g if $tick eq '"';
+ s/\033/\\e/g;
+ s/([\000-\037\177])/'\\c'.chr(ord($1)^64)/eg;
+ }
+ s/([\200-\377])/'\\'.sprintf('%3o',ord($1))/eg if $self->{quoteHighBit};
+ ($noticks || /^\d+(\.\d*)?\Z/)
+ ? $_
+ : $tick . $_ . $tick;
+}
+
+sub DumpElem {
+ my ($self, $v) = (shift, shift);
+ my $short = $self->stringify($v, ref $v);
+ my $shortmore = '';
+ if ($self->{veryCompact} && ref $v
+ && (ref $v eq 'ARRAY' and !grep(ref $_, @$v) )) {
+ my $depth = $#$v;
+ ($shortmore, $depth) = (' ...', $self->{arrayDepth} - 1)
+ if $self->{arrayDepth} and $depth >= $self->{arrayDepth};
+ my @a = map $self->stringify($_), @$v[0..$depth];
+ print "0..$#{$v} @a$shortmore\n";
+ } elsif ($self->{veryCompact} && ref $v
+ && (ref $v eq 'HASH') and !grep(ref $_, values %$v)) {
+ my @a = sort keys %$v;
+ my $depth = $#a;
+ ($shortmore, $depth) = (' ...', $self->{hashDepth} - 1)
+ if $self->{hashDepth} and $depth >= $self->{hashDepth};
+ my @b = map {$self->stringify($_) . " => " . $self->stringify($$v{$_})}
+ @a[0..$depth];
+ local $" = ', ';
+ print "@b$shortmore\n";
+ } else {
+ print "$short\n";
+ $self->unwrap($v,shift);
+ }
+}
+
+sub unwrap {
+ my $self = shift;
+ return if $DB::signal and $self->{stopDbSignal};
+ my ($v) = shift ;
+ my ($s) = shift ; # extra no of spaces
+ my $sp;
+ my (%v,@v,$address,$short,$fileno);
+
+ $sp = " " x $s ;
+ $s += 3 ;
+
+ # Check for reused addresses
+ if (ref $v) {
+ my $val = $v;
+ { no strict 'refs';
+ $val = &{'overload::StrVal'}($v)
+ if %overload:: and defined &{'overload::StrVal'};
+ }
+ ($address) = $val =~ /(0x[0-9a-f]+)\)$/ ;
+ if (!$self->{dumpReused} && defined $address) {
+ $address{$address}++ ;
+ if ( $address{$address} > 1 ) {
+ print "${sp}-> REUSED_ADDRESS\n" ;
+ return ;
+ }
+ }
+ } elsif (ref \$v eq 'GLOB') {
+ $address = "$v" . ""; # To avoid a bug with globs
+ $address{$address}++ ;
+ if ( $address{$address} > 1 ) {
+ print "${sp}*DUMPED_GLOB*\n" ;
+ return ;
+ }
+ }
+
+ if (ref $v eq 'Regexp') {
+ my $re = "$v";
+ $re =~ s,/,\\/,g;
+ print "$sp-> qr/$re/\n";
+ return;
+ }
+
+ if ( UNIVERSAL::isa($v, 'HASH') ) {
+ my @sortKeys = sort keys(%$v) ;
+ my $more;
+ my $tHashDepth = $#sortKeys ;
+ $tHashDepth = $#sortKeys < $self->{hashDepth}-1 ? $#sortKeys : $self->{hashDepth}-1
+ unless $self->{hashDepth} eq '' ;
+ $more = "....\n" if $tHashDepth < $#sortKeys ;
+ my $shortmore = "";
+ $shortmore = ", ..." if $tHashDepth < $#sortKeys ;
+ $#sortKeys = $tHashDepth ;
+ if ($self->{compactDump} && !grep(ref $_, values %{$v})) {
+ $short = $sp;
+ my @keys;
+ for (@sortKeys) {
+ push @keys, $self->stringify($_) . " => " . $self->stringify($v->{$_});
+ }
+ $short .= join ', ', @keys;
+ $short .= $shortmore;
+ (print "$short\n"), return if length $short <= $self->{compactDump};
+ }
+ for my $key (@sortKeys) {
+ return if $DB::signal and $self->{stopDbSignal};
+ my $value = $ {$v}{$key} ;
+ print $sp, $self->stringify($key), " => ";
+ $self->DumpElem($value, $s);
+ }
+ print "$sp empty hash\n" unless @sortKeys;
+ print "$sp$more" if defined $more ;
+ } elsif ( UNIVERSAL::isa($v, 'ARRAY') ) {
+ my $tArrayDepth = $#{$v} ;
+ my $more ;
+ $tArrayDepth = $#$v < $self->{arrayDepth}-1 ? $#$v : $self->{arrayDepth}-1
+ unless $self->{arrayDepth} eq '' ;
+ $more = "....\n" if $tArrayDepth < $#{$v} ;
+ my $shortmore = "";
+ $shortmore = " ..." if $tArrayDepth < $#{$v} ;
+ if ($self->{compactDump} && !grep(ref $_, @{$v})) {
+ if ($#$v >= 0) {
+ $short = $sp . "0..$#{$v} " .
+ join(" ",
+ map {exists $v->[$_] ? $self->stringify($v->[$_]) : "empty"} (0..$tArrayDepth)
+ ) . "$shortmore";
+ } else {
+ $short = $sp . "empty array";
+ }
+ (print "$short\n"), return if length $short <= $self->{compactDump};
+ }
+ for my $num (0 .. $tArrayDepth) {
+ return if $DB::signal and $self->{stopDbSignal};
+ print "$sp$num ";
+ if (exists $v->[$num]) {
+ $self->DumpElem($v->[$num], $s);
+ } else {
+ print "empty slot\n";
+ }
+ }
+ print "$sp empty array\n" unless @$v;
+ print "$sp$more" if defined $more ;
+ } elsif ( UNIVERSAL::isa($v, 'SCALAR') or ref $v eq 'REF' ) {
+ print "$sp-> ";
+ $self->DumpElem($$v, $s);
+ } elsif ( UNIVERSAL::isa($v, 'CODE') ) {
+ print "$sp-> ";
+ $self->dumpsub(0, $v);
+ } elsif ( UNIVERSAL::isa($v, 'GLOB') ) {
+ print "$sp-> ",$self->stringify($$v,1),"\n";
+ if ($self->{globPrint}) {
+ $s += 3;
+ $self->dumpglob('', $s, "{$$v}", $$v, 1);
+ } elsif (defined ($fileno = fileno($v))) {
+ print( (' ' x ($s+3)) . "FileHandle({$$v}) => fileno($fileno)\n" );
+ }
+ } elsif (ref \$v eq 'GLOB') {
+ if ($self->{globPrint}) {
+ $self->dumpglob('', $s, "{$v}", $v, 1);
+ } elsif (defined ($fileno = fileno(\$v))) {
+ print( (' ' x $s) . "FileHandle({$v}) => fileno($fileno)\n" );
+ }
+ }
+}
+
+sub matchvar {
+ $_[0] eq $_[1] or
+ ($_[1] =~ /^([!~])(.)([\x00-\xff]*)/) and
+ ($1 eq '!') ^ (eval {($_[2] . "::" . $_[0]) =~ /$2$3/});
+}
+
+sub compactDump {
+ my $self = shift;
+ $self->{compactDump} = shift if @_;
+ $self->{compactDump} = 6*80-1
+ if $self->{compactDump} and $self->{compactDump} < 2;
+ $self->{compactDump};
+}
+
+sub veryCompact {
+ my $self = shift;
+ $self->{veryCompact} = shift if @_;
+ $self->compactDump(1) if !$self->{compactDump} and $self->{veryCompact};
+ $self->{veryCompact};
+}
+
+sub set_unctrl {
+ my $self = shift;
+ if (@_) {
+ my $in = shift;
+ if ($in eq 'unctrl' or $in eq 'quote') {
+ $self->{unctrl} = $in;
+ } else {
+ print "Unknown value for `unctrl'.\n";
+ }
+ }
+ $self->{unctrl};
+}
+
+sub set_quote {
+ my $self = shift;
+ if (@_ and $_[0] eq '"') {
+ $self->{tick} = '"';
+ $self->{unctrl} = 'quote';
+ } elsif (@_ and $_[0] eq 'auto') {
+ $self->{tick} = 'auto';
+ $self->{unctrl} = 'quote';
+ } elsif (@_) { # Need to set
+ $self->{tick} = "'";
+ $self->{unctrl} = 'unctrl';
+ }
+ $self->{tick};
+}
+
+sub dumpglob {
+ my $self = shift;
+ return if $DB::signal and $self->{stopDbSignal};
+ my ($package, $off, $key, $val, $all) = @_;
+ local(*stab) = $val;
+ my $fileno;
+ if (($key !~ /^_</ or $self->{dumpDBFiles}) and defined $stab) {
+ print( (' ' x $off) . "\$", &unctrl($key), " = " );
+ $self->DumpElem($stab, 3+$off);
+ }
+ if (($key !~ /^_</ or $self->{dumpDBFiles}) and @stab) {
+ print( (' ' x $off) . "\@$key = (\n" );
+ $self->unwrap(\@stab,3+$off) ;
+ print( (' ' x $off) . ")\n" );
+ }
+ if ($key ne "main::" && $key ne "DB::" && %stab
+ && ($self->{dumpPackages} or $key !~ /::$/)
+ && ($key !~ /^_</ or $self->{dumpDBFiles})
+ && !($package eq "Dumpvalue" and $key eq "stab")) {
+ print( (' ' x $off) . "\%$key = (\n" );
+ $self->unwrap(\%stab,3+$off) ;
+ print( (' ' x $off) . ")\n" );
+ }
+ if (defined ($fileno = fileno(*stab))) {
+ print( (' ' x $off) . "FileHandle($key) => fileno($fileno)\n" );
+ }
+ if ($all) {
+ if (defined &stab) {
+ $self->dumpsub($off, $key);
+ }
+ }
+}
+
+sub CvGV_name {
+ my $self = shift;
+ my $in = shift;
+ return if $self->{skipCvGV}; # Backdoor to avoid problems if XS broken...
+ $in = \&$in; # Hard reference...
+ eval {require Devel::Peek; 1} or return;
+ my $gv = Devel::Peek::CvGV($in) or return;
+ *$gv{PACKAGE} . '::' . *$gv{NAME};
+}
+
+sub dumpsub {
+ my $self = shift;
+ my ($off,$sub) = @_;
+ my $ini = $sub;
+ my $s;
+ $sub = $1 if $sub =~ /^\{\*(.*)\}$/;
+ my $subref = defined $1 ? \&$sub : \&$ini;
+ my $place = $DB::sub{$sub} || (($s = $subs{"$subref"}) && $DB::sub{$s})
+ || (($s = $self->CvGV_name($subref)) && $DB::sub{$s})
+ || ($self->{subdump} && ($s = $self->findsubs("$subref"))
+ && $DB::sub{$s});
+ $s = $sub unless defined $s;
+ $place = '???' unless defined $place;
+ print( (' ' x $off) . "&$s in $place\n" );
+}
+
+sub findsubs {
+ my $self = shift;
+ return undef unless %DB::sub;
+ my ($addr, $name, $loc);
+ while (($name, $loc) = each %DB::sub) {
+ $addr = \&$name;
+ $subs{"$addr"} = $name;
+ }
+ $self->{subdump} = 0;
+ $subs{ shift() };
+}
+
+sub dumpvars {
+ my $self = shift;
+ my ($package,@vars) = @_;
+ local(%address,$^W);
+ my ($key,$val);
+ $package .= "::" unless $package =~ /::$/;
+ *stab = *main::;
+
+ while ($package =~ /(\w+?::)/g) {
+ *stab = $ {stab}{$1};
+ }
+ $self->{TotalStrings} = 0;
+ $self->{Strings} = 0;
+ $self->{CompleteTotal} = 0;
+ while (($key,$val) = each(%stab)) {
+ return if $DB::signal and $self->{stopDbSignal};
+ next if @vars && !grep( matchvar($key, $_), @vars );
+ if ($self->{usageOnly}) {
+ $self->globUsage(\$val, $key)
+ if ($package ne 'Dumpvalue' or $key ne 'stab')
+ and ref(\$val) eq 'GLOB';
+ } else {
+ $self->dumpglob($package, 0,$key, $val);
+ }
+ }
+ if ($self->{usageOnly}) {
+ print <<EOP;
+String space: $self->{TotalStrings} bytes in $self->{Strings} strings.
+EOP
+ $self->{CompleteTotal} += $self->{TotalStrings};
+ print <<EOP;
+Grand total = $self->{CompleteTotal} bytes (1 level deep) + overhead.
+EOP
+ }
+}
+
+sub scalarUsage {
+ my $self = shift;
+ my $size;
+ if (UNIVERSAL::isa($_[0], 'ARRAY')) {
+ $size = $self->arrayUsage($_[0]);
+ } elsif (UNIVERSAL::isa($_[0], 'HASH')) {
+ $size = $self->hashUsage($_[0]);
+ } elsif (!ref($_[0])) {
+ $size = length($_[0]);
+ }
+ $self->{TotalStrings} += $size;
+ $self->{Strings}++;
+ $size;
+}
+
+sub arrayUsage { # array ref, name
+ my $self = shift;
+ my $size = 0;
+ map {$size += $self->scalarUsage($_)} @{$_[0]};
+ my $len = @{$_[0]};
+ print "\@$_[1] = $len item", ($len > 1 ? "s" : ""), " (data: $size bytes)\n"
+ if defined $_[1];
+ $self->{CompleteTotal} += $size;
+ $size;
+}
+
+sub hashUsage { # hash ref, name
+ my $self = shift;
+ my @keys = keys %{$_[0]};
+ my @values = values %{$_[0]};
+ my $keys = $self->arrayUsage(\@keys);
+ my $values = $self->arrayUsage(\@values);
+ my $len = @keys;
+ my $total = $keys + $values;
+ print "\%$_[1] = $len item", ($len > 1 ? "s" : ""),
+ " (keys: $keys; values: $values; total: $total bytes)\n"
+ if defined $_[1];
+ $total;
+}
+
+sub globUsage { # glob ref, name
+ my $self = shift;
+ local *stab = *{$_[0]};
+ my $total = 0;
+ $total += $self->scalarUsage($stab) if defined $stab;
+ $total += $self->arrayUsage(\@stab, $_[1]) if @stab;
+ $total += $self->hashUsage(\%stab, $_[1])
+ if %stab and $_[1] ne "main::" and $_[1] ne "DB::";
+ #and !($package eq "Dumpvalue" and $key eq "stab"));
+ $total;
+}
+
+1;
+
+=head1 NAME
+
+Dumpvalue - provides screen dump of Perl data.
+
+=head1 SYNOPSIS
+
+ use Dumpvalue;
+ my $dumper = Dumpvalue->new;
+ $dumper->set(globPrint => 1);
+ $dumper->dumpValue(\*::);
+ $dumper->dumpvars('main');
+ my $dump = $dumper->stringify($some_value);
+
+=head1 DESCRIPTION
+
+=head2 Creation
+
+A new dumper is created by a call
+
+ $d = Dumpvalue->new(option1 => value1, option2 => value2)
+
+Recognized options:
+
+=over 4
+
+=item C<arrayDepth>, C<hashDepth>
+
+Print only first N elements of arrays and hashes. If false, prints all the
+elements.
+
+=item C<compactDump>, C<veryCompact>
+
+Change style of array and hash dump. If true, short array
+may be printed on one line.
+
+=item C<globPrint>
+
+Whether to print contents of globs.
+
+=item C<dumpDBFiles>
+
+Dump arrays holding contents of debugged files.
+
+=item C<dumpPackages>
+
+Dump symbol tables of packages.
+
+=item C<dumpReused>
+
+Dump contents of "reused" addresses.
+
+=item C<tick>, C<quoteHighBit>, C<printUndef>
+
+Change style of string dump. Default value of C<tick> is C<auto>, one
+can enable either double-quotish dump, or single-quotish by setting it
+to C<"> or C<'>. By default, characters with high bit set are printed
+I<as is>. If C<quoteHighBit> is set, they will be quoted.
+
+=item C<usageOnly>
+
+rudimentally per-package memory usage dump. If set,
+C<dumpvars> calculates total size of strings in variables in the package.
+
+=item unctrl
+
+Changes the style of printout of strings. Possible values are
+C<unctrl> and C<quote>.
+
+=item subdump
+
+Whether to try to find the subroutine name given the reference.
+
+=item bareStringify
+
+Whether to write the non-overloaded form of the stringify-overloaded objects.
+
+=item quoteHighBit
+
+Whether to print chars with high bit set in binary or "as is".
+
+=item stopDbSignal
+
+Whether to abort printing if debugger signal flag is raised.
+
+=back
+
+Later in the life of the object the methods may be queries with get()
+method and set() method (which accept multiple arguments).
+
+=head2 Methods
+
+=over 4
+
+=item dumpValue
+
+ $dumper->dumpValue($value);
+ $dumper->dumpValue([$value1, $value2]);
+
+Prints a dump to the currently selected filehandle.
+
+=item dumpValues
+
+ $dumper->dumpValues($value1, $value2);
+
+Same as C<< $dumper->dumpValue([$value1, $value2]); >>.
+
+=item stringify
+
+ my $dump = $dumper->stringify($value [,$noticks] );
+
+Returns the dump of a single scalar without printing. If the second
+argument is true, the return value does not contain enclosing ticks.
+Does not handle data structures.
+
+=item dumpvars
+
+ $dumper->dumpvars('my_package');
+ $dumper->dumpvars('my_package', 'foo', '~bar$', '!......');
+
+The optional arguments are considered as literal strings unless they
+start with C<~> or C<!>, in which case they are interpreted as regular
+expressions (possibly negated).
+
+The second example prints entries with names C<foo>, and also entries
+with names which ends on C<bar>, or are shorter than 5 chars.
+
+=item set_quote
+
+ $d->set_quote('"');
+
+Sets C<tick> and C<unctrl> options to suitable values for printout with the
+given quote char. Possible values are C<auto>, C<'> and C<">.
+
+=item set_unctrl
+
+ $d->set_unctrl('unctrl');
+
+Sets C<unctrl> option with checking for an invalid argument.
+Possible values are C<unctrl> and C<quote>.
+
+=item compactDump
+
+ $d->compactDump(1);
+
+Sets C<compactDump> option. If the value is 1, sets to a reasonable
+big number.
+
+=item veryCompact
+
+ $d->veryCompact(1);
+
+Sets C<compactDump> and C<veryCompact> options simultaneously.
+
+=item set
+
+ $d->set(option1 => value1, option2 => value2);
+
+=item get
+
+ @values = $d->get('option1', 'option2');
+
+=back
+
+=cut
+
diff --git a/dist/Dumpvalue/t/Dumpvalue.t b/dist/Dumpvalue/t/Dumpvalue.t
new file mode 100644
index 0000000000..e661fc51e7
--- /dev/null
+++ b/dist/Dumpvalue/t/Dumpvalue.t
@@ -0,0 +1,302 @@
+#!./perl
+
+BEGIN {
+ if (ord('A') == 193) {
+ print "1..0 # skip: EBCDIC\n";
+ exit 0;
+ }
+ require Config;
+ if (($Config::Config{'extensions'} !~ m!\bList/Util\b!) ){
+ print "1..0 # Skip -- Perl configured without List::Util module\n";
+ exit 0;
+ }
+
+ # `make test` in the CPAN version of this module runs us with -w, but
+ # Dumpvalue.pm relies on all sorts of things that can cause warnings. I
+ # don't think that's worth fixing, so we just turn off all warnings
+ # during testing.
+ $^W = 0;
+}
+
+use vars qw( $foo @bar %baz );
+
+use Test::More tests => 88;
+
+use_ok( 'Dumpvalue' );
+
+my $d;
+ok( $d = Dumpvalue->new(), 'create a new Dumpvalue object' );
+
+$d->set( globPrint => 1, dumpReused => 1 );
+is( $d->{globPrint}, 1, 'set an option correctly' );
+is( $d->get('globPrint'), 1, 'get an option correctly' );
+is( $d->get('globPrint', 'dumpReused'), qw( 1 1 ), 'get multiple options' );
+
+# check to see if unctrl works
+is( ref( Dumpvalue::unctrl(*FOO) ), 'GLOB', 'unctrl should not modify GLOB' );
+is( Dumpvalue::unctrl('donotchange'), 'donotchange', "unctrl shouldn't modify");
+like( Dumpvalue::unctrl("bo\007nd"), qr/bo\^.nd/, 'unctrl should escape' );
+
+# check to see if stringify works
+is( $d->stringify(), 'undef', 'stringify handles undef okay' );
+
+# the default is 1, but we want two single quotes
+$d->{printUndef} = 0;
+is( $d->stringify(), "''", 'stringify skips undef when asked nicely' );
+
+is( $d->stringify(*FOO), *FOO . "", 'stringify stringifies globs alright' );
+
+# check for double-quotes if there's an unprintable character
+$d->{tick} = 'auto';
+like( $d->stringify("hi\005"), qr/^"hi/, 'added double-quotes when necessary' );
+
+# if no unprintable character, escape ticks or backslashes
+is( $d->stringify('hi'), "'hi'", 'used single-quotes when appropriate' );
+
+# if 'unctrl' is set
+$d->{unctrl} = 'unctrl';
+like( $d->stringify('double and whack:\ "'), qr!\\ \"!, 'escaped with unctrl' );
+like( $d->stringify("a\005"), qr/^"a\^/, 'escaped ASCII value in unctrl' );
+like( $d->stringify("b\205"), qr!^'b.'$!, 'no high-bit escape value in unctrl');
+
+$d->{quoteHighBit} = 1;
+like( $d->stringify("b\205"), qr!^'b\\205!, 'high-bit now escaped in unctrl');
+
+# if 'quote' is set
+$d->{unctrl} = 'quote';
+is( $d->stringify('5@ $1'), "'5\@ \$1'", 'quoted $ and @ fine' );
+is( $d->stringify("5@\033\$1"), '"5\@\e\$1"', 'quoted $ and @ and \033 fine' );
+like( $d->stringify("\037"), qr/^"\\c/, 'escaped ASCII value okay' );
+
+# add ticks, if necessary
+is( $d->stringify("no ticks", 1), 'no ticks', 'avoid ticks if asked' );
+
+my $out = tie *OUT, 'TieOut';
+select(OUT);
+
+# test DumpElem, it does its magic with veryCompact set
+$d->{veryCompact} = 1;
+$d->DumpElem([1, 2, 3]);
+is( $out->read, "0..2 1 2 3\n", 'DumpElem worked on array ref');
+$d->DumpElem({ one => 1, two => 2 });
+is( $out->read, "'one' => 1, 'two' => 2\n", 'DumpElem worked on hash ref' );
+$d->DumpElem('hi');
+is( $out->read, "'hi'\n", 'DumpElem worked on simple scalar' );
+$d->{veryCompact} = 0;
+$d->DumpElem([]);
+like( $out->read, qr/ARRAY/, 'DumpElem okay with reference and no veryCompact');
+
+# should compact simple arrays just fine
+$d->{veryCompact} = 1;
+$d->DumpElem([1, 2, 3]);
+is( $out->read, "0..2 1 2 3\n", 'dumped array fine' );
+$d->{arrayDepth} = 2;
+$d->DumpElem([1, 2, 3]);
+is( $out->read, "0..2 1 2 ...\n", 'dumped limited array fine' );
+
+# should compact simple hashes just fine
+$d->DumpElem({ a => 1, b => 2, c => 3 });
+is( $out->read, "'a' => 1, 'b' => 2, 'c' => 3\n", 'dumped hash fine' );
+$d->{hashDepth} = 2;
+$d->DumpElem({ a => 1, b => 2, c => 3 });
+is( $out->read, "'a' => 1, 'b' => 2 ...\n", 'dumped limited hash fine' );
+
+# should just stringify what it is
+$d->{veryCompact} = 0;
+$d->DumpElem([]);
+like( $out->read, qr/ARRAY.+empty array/s, 'stringified empty array ref' );
+$d->DumpElem({});
+like( $out->read, qr/HASH.+empty hash/s, 'stringified empty hash ref' );
+$d->DumpElem(1);
+is( $out->read, "1\n", 'stringified simple scalar' );
+
+# test unwrap
+$DB::signal = $d->{stopDbSignal} = 1;
+is( $d->unwrap(), undef, 'unwrap returns if DB signal is set' );
+undef $DB::signal;
+
+my $foo = 7;
+$d->{dumpReused} = 0;
+$d->unwrap(\$foo);
+is( $out->read, "-> 7\n", 'unwrap worked on scalar' );
+$d->unwrap(\$foo);
+is( $out->read, "-> REUSED_ADDRESS\n", 'unwrap worked on scalar' );
+$d->unwrap({ one => 1 });
+
+# leaving this at zero may cause some subsequent tests to fail
+# if they reuse an address creating an anonymous variable
+$d->{dumpReused} = 1;
+is( $out->read, "'one' => 1\n", 'unwrap worked on hash' );
+$d->unwrap([ 2, 3 ]);
+is( $out->read, "0 2\n1 3\n", 'unwrap worked on array' );
+$d->unwrap(*FOO);
+is( $out->read, '', 'unwrap ignored glob on first try');
+$d->unwrap(*FOO);
+is( $out->read, "*DUMPED_GLOB*\n", 'unwrap worked on glob');
+$d->unwrap(qr/foo(.+)/);
+
+my $modifiers = (qr// =~ /\Q(?^/) ? '^' : '-xism';
+is( $out->read, "-> qr/(?${modifiers}:foo(.+))/\n", 'unwrap worked on Regexp' );
+
+$d->unwrap( sub {} );
+like( $out->read, qr/^-> &CODE/, 'unwrap worked on sub ref' );
+
+# test matchvar
+# test to see if first arg 'eq' second
+ok( Dumpvalue::matchvar(1, 1), 'matchvar matched numbers fine' );
+ok( Dumpvalue::matchvar('hi', 'hi'), 'matchvar matched strings fine' );
+ok( !Dumpvalue::matchvar('hello', 1), 'matchvar caught failed match fine' );
+
+# test compactDump, which doesn't do much
+is( $d->compactDump(3), 3, 'set compactDump to 3' );
+is( $d->compactDump(1), 479, 'compactDump reset to 6*80-1 when less than 2' );
+
+# test veryCompact, which does slightly more, setting compactDump sometimes
+$d->{compactDump} = 0;
+is( $d->veryCompact(1), 1, 'set veryCompact successfully' );
+ok( $d->compactDump(), 'and it set compactDump as well' );
+
+# test set_unctrl
+$d->set_unctrl('impossible value');
+like( $out->read, qr/^Unknown value/, 'set_unctrl caught bad value' );
+is( $d->set_unctrl('quote'), 'quote', 'set quote fine' );
+is( $d->set_unctrl(), 'quote', 'retrieved quote fine' );
+
+# test set_quote
+$d->set_quote('"');
+is( $d->{tick}, '"', 'set_quote set tick right' );
+is( $d->{unctrl}, 'quote', 'set unctrl right too' );
+$d->set_quote('auto');
+is( $d->{tick}, 'auto', 'set_quote set auto right' );
+$d->set_quote('foo');
+is( $d->{tick}, "'", 'default value set to " correctly' );
+
+# test dumpglob
+# should do nothing if debugger signal flag is raised
+$d->{stopDbSignal} = $DB::signal = 1;
+is( $d->dumpglob(*DB::signal), undef, 'returned early with DB signal set' );
+undef $DB::signal;
+
+# test dumping "normal" variables, this is a nasty glob trick
+$foo = 1;
+$d->dumpglob( '', 2, 'foo', local *foo = \$foo );
+is( $out->read, " \$foo = 1\n", 'dumped glob for $foo correctly' );
+@bar = (1, 2);
+
+# the key name is a little different here
+$d->dumpglob( '', 0, 'boo', *bar );
+is( $out->read, "\@boo = (\n 0..1 1 2\n)\n", 'dumped glob for @bar fine' );
+
+%baz = ( one => 1, two => 2 );
+$d->dumpglob( '', 0, 'baz', *baz );
+is( $out->read, "\%baz = (\n 'one' => 1, 'two' => 2\n)\n",
+ 'dumped glob for %baz fine' );
+
+SKIP: {
+ skip( "Couldn't open $0 for reading", 1 ) unless open(FILE, $0);
+ my $fileno = fileno(FILE);
+ $d->dumpglob( '', 0, 'FILE', *FILE );
+ is( $out->read, "FileHandle(FILE) => fileno($fileno)\n",
+ 'dumped filehandle from glob fine' );
+}
+
+$d->dumpglob( '', 0, 'read', *TieOut::read );
+is( $out->read, '', 'no sub dumped without $all set' );
+$d->dumpglob( '', 0, 'read', \&TieOut::read, 1 );
+is( $out->read, "&read in ???\n", 'sub dumped when requested' );
+
+# see if it dumps DB-like values correctly
+$d->{dumpDBFiles} = 1;
+$d->dumpglob( '', 0, '_<foo', *foo );
+is( $out->read, "\$_<foo = 1\n", 'dumped glob for $_<foo correctly (DB)' );
+
+# test CvGV name
+SKIP: {
+ if (" $Config::Config{'extensions'} " !~ m[ Devel/Peek ]) {
+ skip( 'no Devel::Peek', 2 );
+ }
+ use_ok( 'Devel::Peek' );
+ is( $d->CvGV_name(\&TieOut::read), 'TieOut::read', 'CvGV_name found sub' );
+}
+
+# test dumpsub
+$d->dumpsub( '', 'TieOut::read' );
+like( $out->read, qr/&TieOut::read in/, 'dumpsub found sub fine' );
+
+# test findsubs
+is( $d->findsubs(), undef, 'findsubs returns nothing without %DB::sub' );
+$DB::sub{'TieOut::read'} = 'TieOut';
+is( $d->findsubs( \&TieOut::read ), 'TieOut::read', 'findsubs reported sub' );
+
+# now that it's capable of finding the package...
+$d->dumpsub( '', 'TieOut::read' );
+is( $out->read, "&TieOut::read in TieOut\n", 'dumpsub found sub fine again' );
+
+# this should print just a usage message
+$d->{usageOnly} = 1;
+$d->dumpvars( 'Fake', 'veryfake' );
+like( $out->read, qr/^String space:/, 'printed usage message fine' );
+delete $d->{usageOnly};
+
+# this should report @INC and %INC
+$d->dumpvars( 'main', 'INC' );
+like( $out->read, qr/\@INC =/, 'dumped variables from a package' );
+
+# this should report nothing
+$DB::signal = 1;
+$d->dumpvars( 'main', 'INC' );
+is( $out->read, '', 'no dump when $DB::signal is set' );
+undef $DB::signal;
+
+is( $d->scalarUsage('12345'), 5, 'scalarUsage reports length correctly' );
+is( $d->arrayUsage( [1, 2, 3], 'a' ), 3, 'arrayUsage reports correct lengths' );
+is( $out->read, "\@a = 3 items (data: 3 bytes)\n", 'arrayUsage message okay' );
+is( $d->hashUsage({ one => 1 }, 'b'), 4, 'hashUsage reports correct lengths' );
+is( $out->read, "\%b = 1 item (keys: 3; values: 1; total: 4 bytes)\n",
+ 'hashUsage message okay' );
+is( $d->hashUsage({ one => [ 1, 2, 3 ]}, 'c'), 6, 'complex hash okay' );
+is( $out->read, "\%c = 1 item (keys: 3; values: 3; total: 6 bytes)\n",
+ 'hashUsage complex message okay' );
+
+$foo = 'one';
+@foo = ('two');
+%foo = ( three => '123' );
+is( $d->globUsage(\*foo, 'foo'), 14, 'globUsage reports length correctly' );
+like( $out->read, qr/\@foo =.+\%foo =/s, 'globValue message okay' );
+
+# and now, the real show
+$d->dumpValue(undef);
+is( $out->read, "undef\n", 'dumpValue caught undef value okay' );
+$d->dumpValue($foo);
+is( $out->read, "'one'\n", 'dumpValue worked' );
+$d->dumpValue(@foo);
+is( $out->read, "'two'\n", 'dumpValue worked on array' );
+$d->dumpValue(\$foo);
+is( $out->read, "-> 'one'\n", 'dumpValue worked on scalar ref' );
+
+# dumpValues (the rest of these should be caught by unwrap)
+$d->dumpValues(undef);
+is( $out->read, "undef\n", 'dumpValues caught undef value fine' );
+$d->dumpValues(\@foo);
+is( $out->read, "0 0..0 'two'\n", 'dumpValues worked on array ref' );
+$d->dumpValues('one', 'two');
+is( $out->read, "0..1 'one' 'two'\n", 'dumpValues worked on multiple values' );
+
+
+package TieOut;
+use overload '"' => sub { "overloaded!" };
+
+sub TIEHANDLE {
+ my $class = shift;
+ bless(\( my $ref), $class);
+}
+
+sub PRINT {
+ my $self = shift;
+ $$self .= join('', @_);
+}
+
+sub read {
+ my $self = shift;
+ return substr($$self, 0, length($$self), '');
+}