Why not adopt me?
NAME
Test::Stream::Plugin::Explain::Terse - Dump anything in a single line in 80 characters or fewer
VERSION
version 0.001001
SYNOPSIS
use Test::Stream::Bundle::V1;
use Test::Stream::Plugin::Explain::Terse qw( explain_terse );
note "Studying: fn(y) = " . explain_terse(my $got = fn($y));
# test $got
done_testing;
DESCRIPTION
This module aims to provide a simple tool for adding trace-level details about data-structures to the TAP stream to visually keep track of what is being tested.
Its objective is to not be comprehensive, and only be sufficient for a quick visual sanity check, allowing you to visually spot obviously wrong things at a glance without producing too much clutter.
It is expected that if Explain::Terse
produces a data structure that needs compacting for display, that the user will also be performing sub-tests on that data structure, and those sub-tests will trace their own context closer to the actual test.
# Checking: { a_key => ["a value"], b_key => ["b value'], ... }
# Subtest: c_key is expected
# Checking: ["c value"]
ok 1 - c_key's array has value "c value"
1..1
ok 1 - c_key is expected
The idea being the higher up in the data structure you're doing the comparison, the less relevant the individual details are to that comparison, and the actual details only being relevant in child comparisons.
This is obviously also better if you're doing structurally layered comparison, and not simple path-based comparisons, e.g:
# Not intended to be used this way.
note explain_terse(\%hash);
is( $hash{'key'}{'otherkey'}{'finalkey'}, 'expected_value' );
And you want something like:
note explain_terse(\%hash);
ok( exists $hash{'key'}, 'has q[key]')
and subtest "key structure" => sub {
my $structure = $hash{'key'};
note explain_terse($structure);
is( ref $structure, 'HASH', 'is a HASH' )
and ok( exists $structure->{'otherkey'}, 'has q[otherkey]' )
and subtest "otherkey structure" => sub {
my $substructure = $structure->{'otherkey'};
note explain_terse($substructure);
is( ref $substructure, 'HASH', 'is a HASH' )
and ok( exists $structure->{'finalkey'}, 'has final key' )
and subtest "finalkey structure" => sub {
my $final_structure = $substructure->{'finalkey'};
note explain_terse($final_structure);
ok( !ref $final_structure, "finalkey is not a ref")
and ok( defined $final_structure, "finalkey is defined")
and is( $final_structure, 'expected_value', "finalkey is expected_value" );
};
};
};
Though of course you'd not want to write it like that directly in your tests, you'd probably want something more like
with(\%hash)->is_hash->has_key('key', sub {
with($_[0])->is_hash->has_key('otherkey', sub {
with($_[0])->is_hash->has_key('finalkey', sub {
with($_[0])->is_scalar->defined->is_eq("expected_value");
});
});
});
Or
cmp_deeply( \%hash, superhashof({
key => superhashof({
otherkey => superhashof({
finalkey => "expeted_value"
}),
}),
}));
And have Explain::Terse
operating transparently under the hood of these implementations so you can see what is happening.
FUNCTIONS
explain_terse
my $data = explain_terse($structure);
Returns $structure
pretty printed and compacted to be less than 80 characters long.
FUTURE STABILITY
Test::Stream
This module intends to inter-operate with Test::Stream
which this modules author considers still in a heavy state of flux, and so this module cannot be considered even remotely stable until some point after that becoming more stable.
Dumper
internals.
This module presently uses pp
from Data::Dump
as its main formatter bolted into some simple sub-string operations and newline transformations.
It is planned that this module will switch to using Data::Dumper
at some future time, pending on its addition of features like range-list reductions, and other niceties Data::Dump
offers.
Alas, Data::Dump
doesn't support sub
de-parsing, and Data::Dump
doesn't have internals that could be considered a canonical reference implementation Data::Dumper
is.
So as soon as Data::Dumper
has all the features this module wants, it will switch.
But you shouldn't be relying on the output of this module having a fixed string representation anyway, its purely for human consumption.
Controlled Non-Terse Dumping
Two features here could be useful, but I'm still working out how to do it nicely.
It would be nice to stash
diag
traces in a context and then reveal the entire leg of the test prior to the failure, but only on failure, such that when you were just reading a passing TAP series it wasn't burdensome, but when failures occurred you got all the details you needed still.Conditionally
diag
ing in full uncondensed form might eventually be a feature at user request.
And the above two in conjunction could be really handy.
INTEROP WITH TEST::STREAM ECOSYSTEM
IMPORT STYLE
The author of Test::Stream
presently indicates their preferred way of consuming plugins like this one would be as follows:
use Test::Stream -V1, 'Explain::Terse';
The author of this module finds such a style confusing an unclear to new users and finds it seriously impedes automatic prerequisite detection.
use Test::Stream::Bundle::V1, 'Explain::Terse';
This style is less confusing, but not yet perfectly clear.
use Test::Stream::Bundle::V1;
use Test::Stream::Plugin::Explain::Terse qw( explain_terse );
is much more obvious what is happening.
EXPORTER
This module presently uses Test::Stream::Exporter
as its exporter library. This is for inter-operability with the Test::Stream
bundling system which allows for bundles to compose multiple plugins into a single calling class.
This technique requires a bit of indirection, and requires allowing the bundle to clearly communicate the name of the bundles caller to its composed plugins while allowing plugins to augment that callers name-space directly.
But to facilitate this, a specific non-import
interface must exist on the plugin which the Test::Stream
infrastructure can use to permit explicit passing of caller()
data without needing to pull cute tricks like locally redefining caller()
like Sub::Uplevel
, or imposing limitations on the ->import(@ARGS)
syntax, and avoids needing to do strange import tricks like Import::Into
does with eval
.
020: sub import {
021: my $class = shift;
022: my @caller = caller;
023:
024: push @_ => $class->default unless @_;
025:
026: $class->load(\@caller, @_);
027:
028: 1;
029: }
030: sub load {
...
140: if ($mod->can('load_ts_plugin')) {
141: $mod->load_ts_plugin($caller, @$import);
142: }
143: elsif (my $meta = Test::Stream::Exporter::Meta->get($mod)) {
144: Test::Stream::Exporter::export_from($mod, $caller->[0], $import);
145: }
09: default_export import => sub {
10: my $class = shift;
11: my @caller = caller;
12:
13: my $bundle = $class;
14: $bundle =~ s/^Test::Stream::Bundle::/-/;
15:
16: require Test::Stream;
17: Test::Stream->load(\@caller, $bundle, @_);
18: };
AUTHOR
Kent Fredric <kentnl@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2015 by Kent Fredric <kentfredric@gmail.com>.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.