The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Test::Stream::DeepCheck - Tools for comparing deep datastructures

EXPERIMENTAL CODE WARNING

This is an experimental release! Test-Stream, and all its components are still in an experimental phase. This dist has been released to cpan in order to allow testers and early adopters the chance to write experimental new tools with it, or to add experimental support for it into old tools.

PLEASE DO NOT COMPLETELY CONVERT OLD TOOLS YET. This experimental release is very likely to see a lot of code churn. API's may break at any time. Test-Stream should NOT be depended on by any toolchain level tools until the experimental phase is over.

DESCRIPTION

SYNOPSIS

    use Test::Stream::DeepCheck;

    my $reg = qr/aaa/;
    strict_compare(
        {foo => 1, bar => [ 'a' ], baz => { a => 1 }, reg => $reg},
        {foo => 1, bar => [ 'a' ], baz => { a => 1 }, reg => $reg},
        "Match strict"
    );

    relaxed_compare(
        {foo => 1, bar => [ 'a' ], baz => { a => 1, b => 'ignored' }, reg => 'aaa', extra => 'not checked'},
        {foo => 1, bar => [ 'a' ], baz => { a => 1 }, reg => $reg},
        "Match relaxed"
    );

Using the other exports for better debugging:

    strict_compare(
        {foo => 1, bar => [ 'a' ], baz => { a => 1 }, reg => $reg},
        hash {
            field foo => 1;
            field bar => array {
                elem 'a';
            };
            field baz => hash {
                field a => 1;
            };
            field reg => $reg;
        },
        'Missed one'
    );

The benefit of this more verbose form is that each check you list record the line number where you made it. This allows the debugging message to tell you the file and line number of the exact part of the structure that failed.

Here is one that fails

    # This will fail becuase of bad => 'oops' and bad2 => 1
    strict_compare(
        {foo => 1, bar => [ 'a' ], baz => { a => 1, bad => 'oops', bad2 => 1 }, reg => $reg},
        hash {                    # This is line 76
            field foo => 1;
            field bar => array {
                elem 'a';
            };
            field baz => hash {   # This is like 81.
                field a => 1;
            };
            field reg => $reg;
        },
        'Missed one'
    );

The diag from this will be:

    # Failed test 'Missed one'
    # at t/Test-Stream-DeepCheck.t line 86.
    # Path: $_->{'baz'}->{'bad', 'bad2'}
    # Failed Check: Expected no more fields, got 'bad', 'bad2'
    # t/Test-Stream-DeepCheck.t
    # 76 {
    # 81   'baz': {
    # --     'bad', 'bad2'

Line 86 is listed as the main failure, this is due to how perl reports line numbers for function calls where arguments extend across multiple lines. We also list lines 76 and 81 to direct you to the place that actually saw the failure. You might notice that these lines do not suffer the same line number problem as the main function, this is because they take codeblock as argument, we are able to find the correct line number by inspecting the coderef.

EXPORTS

ASSERTIONS

strict_compare($got, $want, $name, $diag_callback)

This function is made to work like is_deeply() from Test::More.

This compares the data structure in $got to the datastructure in $want. $want can be a regular reference to a nested datastructure, or it can be a structure produced using other functions in this library to give you more control and better debugging.

The strict form will alert you if there are any extra (unspecified) keys in your hashes, or extra elements in your arrays. When a regex or reference is encountered in $got the SAME reference should be in $want. All field checks use "$a" eq "$b" to check for a match, unless they are references in which case == is used.

You may also provide a coderef as $diag_callback. This coderef will recieve $got as its first argument, and the keys/indexes used to traverse to the point of failure. Anything returned from the callback will be added as a diagnostics message.

relaxed_compare($got, $want, $name, $diag_callback)

This is the same as strict_compare() except that it is more liberal in what it expects. It will only check hash keys specified in $want extra keys in $got are ignored. Arrays built using array { ... } will ignore extra elements in $got. If $want has a hashref the equivilent path in $got will be checked against the regex. Coderefs in $want will be assumed to be checks that return true or false when called with the value from $got as an argument. Finally, if both sides look like numbers == will be used instead of eq to compare them.

You may also provide a coderef as $diag_callback. This coderef will recieve $got as its first argument, and the keys/indexes used to traverse to the point of failure. Anything returned from the callback will be added as a diagnostics message.

DEFINING STRUCTURES

These can be used to declare datastructures. Using these allows for better debugging than simply providing a vanilla datastructure. This also gives you more control using things like filter { ... }.

$check = array { ... }

Used to build an array check.

Within the array builder codeblock you should use elem() to add elements. You can also use check() to add more finely controlled checks. meta() can be used to add checks against the array itself as opposed to its elements.

For relaxed tests this check will ignore elements after the last element checks you specify. To avoid this behavior use end() to tell the array that there should be no remaining elements.

Note meta() checks are all run at once, in the order they are defined, BEFORE any element checks are run.

$check = hash { ... }

Same as array { ... } except it defines a hash. Instead of elem() you should use field to define key/value pairs. end() can be used to make the hash strictly enforce the number of keys that exist. meta() can still be used to run checks against the array itself instead of its keys and values.

Note meta() checks are all run at once, in the order they are defined, BEFORE any field checks are run. Keys will be checked in the order you specify them.

$check = object { ... }

Same as hash { ... } and array { ... } but the check expects a blessed reference (of any reftype). You should not use field() or elem() on basic objects, instead you use call() to define methods to call, and what you expect them to return.

meta() may still be used to define checks against the object itself instead of its methods.

$check = hash_object { ... }

This is a subclass of both the hash and object checks. This lets you use meta(), field() and call() checks.

Note Checks are run int he following order: Meta, Hash, Object.

$check = array_object { ... }

This is a subclass of both the array and object checks. This lets you use meta(), elem() and call() checks.

Note Checks are run in the following order: Meta, Hash, Object.

DEFINING CHECKS

meta($name, $op)
meta($name, $op, $val)

Add a meta-check to the current build, it may be a hash, array, or object. $name is the name of the meta-check for use in debugging. $op can be any operator known to Test::Stream::DeepCheck::Check.

This function can only be called in void context, and there must be a parent build (array, hash, object, etc).

$check = check($op)
$check = check($op, $val)

This quickly builds an Test::Stream::DeepCheck::Check object for you. This is useful for maing very specific checks of fields or method return values:

    hash_object {
        field foo => check('==', 123.456);
        call  bar => check('eq', 'flubber');
    }
field $name => $check

Add a key/value check to the current hash build. Throws an exception if called in a non-void context. Throws an exception if there is no current build, or if the current build is not a hash.

    hash {
        field foo => 'FOO';
        field bar => check ...;
        field baz => hash { ... };
    }
elem $check

Add a check to the current array build. Throws an exception if called in a non-void context. Throws an exception if called with no current build, or a build that is not an array.

    array {
        elem 1;
        elem 'foo';
        elem hash { ... };
    }
call $method => $check
call "[$method]" => $check
call "{$method}" => $check

This is used to check the return value of a method call on the object being checked. The first argument must be a method name, optionally wrapped with '[...]' or '{...}'. The wrapping serves to help you check methods that return more than one value, you can specify that the return should be wrapped in a hashref or arrayref.

    object {
        call 'foo' => 'FOO';
        call 'foo' => check ...;
        call '[things]' => array { ... };
        call '{lookup}' => hash  { ... };
    }

OTHER

filter { grep { ... } @_ }

This works on hashes and arrays, it can be used to remove items from the datastructure we are validating.

    array {
        # Remove all array elements that do not contain an 'x'
        filter { grep { m/x/ } @_ };
        elem 'fox';
        elem 'rox';
        elem 'box';
        end;
    }

The codeblock recieves all remaining elements as arguments, and should return all the elements that should still be checked.

    hash {
        filter {
            my %all = @_;
            delete $all{x}; # Remove the x field if present
            return %all;
        };
        field foo => 'bar';
        ...
    }
end()

Used to note that there should be not more elements or fields depending on if the build is a hash or an array.

$check = convert($want, $debug, $strict, <$seen>)

This is used to convert a value, such as a string, number, hashref, arrayref, etc, to an Test::Stream::DeepCheck::Check object. This is used internally to convert regular structures to the validation checks.

The first argument is the value to convert. The second argument must be an instance of Test::Stream::DebugInfo. The third argument is a boolean that toggles strict mode on and off. There is a fourth argument that is used internally for recursive data structures.

$check = build_object($class, $coderef, $caller)

This is used to build an object check of the specified class using the provided coderef.

$build = STRUCT()

This can be used to obtain the current build object.

SOURCE

The source code repository for Test::Stream can be found at http://github.com/Test-More/Test-Stream/.

MAINTAINERS

Chad Granum <exodist@cpan.org>

AUTHORS

Chad Granum <exodist@cpan.org>

COPYRIGHT

Copyright 2015 Chad Granum <exodist7@gmail.com>.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See http://www.perl.com/perl/misc/Artistic.html