Search code examples
perltestingtap

How can I ignore a TAP result in perl with Test::more?


I want to make use of this library to pretty print the differences of my test Test::Differences; but I do not want to count this in my plan, as this is just used for debugging in my use case.

So I created a minimal example of this issue to reproduce this, I know is not "right" to do the oks like this, but is a good ilustration of the problem.

use strict;
use warnings;
use utf8::all;
use open ':std', ':encoding(UTF-8)';

use Test::Differences;
use Test::Deep::NoTest qw(cmp_details deep_diag);
use Test::More tests => 2;
use JSON;

my $context = 1;
my $extracted_ref = {
    a => '1',
    b => '2',
    c => '3',
    d => '4',
};
my $reference_ref = {
    a => '1',
    b => '3',
    c => '3',
    d => '4',
};

my ($is_compare_ok, $stack) = cmp_details($extracted_ref,$reference_ref);
my $json_reference = JSON->new->canonical->encode($reference_ref);
my $json_extracted = JSON->new->canonical->encode($extracted_ref);

ok(1);
if ($is_compare_ok){
    ok(1);
    return 1;                               
}else{                                  
    ok(0);
    eq_or_diff($reference_ref, $extracted_ref, "Error in '$0'", {context=>$context}); # <- I don't want to count this
    return 0;
}

I would expect to get to tests executed at the end of the script, the two ok which are done during the process. But the function eq_or_diff adds a new test to the TAP session so the script ends with an exection of 3 tests and therefore done_testing() expects 2 but gets 3.

This is a minimal example, but usually I have a main script:

main.t

use Test::More tests => 2;
...
ok(some_function_in_other_file_or_library(), "name_of_the_test");
...

lib.pm

...
sub some_function_in_other_file_or_library{
...
   eq_or_diff(...)
   return $bool;
}
...

I mention this because I tried with substest and I was not able to make it work neither, and I main does not have to now what happens in the lib because otherwise I think I could just use done_testing($planned_tests+1):

lib.pm

use Test::More tests qw/subtest/;
subtest 'An example subtest' => sub {
    plan tests => 1;
 
    eq_or_diff(...)
};

Summary: How can I make something similar to:

do_not_add_to_tap(eq_or_diff(...))
# or 
increased during runtime the planned tests
plan() = plan()+1...

Solution

  • Test::Differences and friends are really doing two things for you, then complicating it by tightly coupling it to the test framework. That's not a big bother because it's really just wrapping things from other modules that you can use directly.

    First, it uses Data::Dumper to output a predictable serialization by setting various parameters, such as SortKeys. Second, it uses Text::Diff to compare the lines of those dumps.

    So, just do that yourself. Here's the first part, which just returns the single string that represents the dump, suitable for string comparisons to see if they are equal:

    sub dump_it {
        my( $arg ) = @_;
        local $Data::Dumper::Deparse   = 1;
        local $Data::Dumper::Indent    = 1;
        local $Data::Dumper::Purity    = 0;
        local $Data::Dumper::Terse     = 1;
        local $Data::Dumper::Deepcopy  = 1;
        local $Data::Dumper::Quotekeys = 0;
        local $Data::Dumper::Useperl   = 1;
        local $Data::Dumper::Sortkeys  = 1;
        Data::Dumper::Dumper($arg);
        }
    

    The second part does a similar thing with Text::Diff. It sets various parameters, which you can season to taste. I made this to pass it two strings, which I turn into lines inside an array reference (since this is all in memory):

    sub my_diff {
        state $rc = require Text::Diff;
        my( $got, $expected, $context ) = @_;
    
        $context //= 3;  # lines around the diff to show
    
        my $diff = Text::Diff::diff
            [ split /^/m, dump_it( $got ) ],
            [ split /^/m, dump_it( $expected ) ],
          { CONTEXT     => $context,
            STYLE       => 'Table',
            FILENAME_A  => 'got',
            FILENAME_B  => 'expected',
            OFFSET_A    => 1,
            OFFSET_B    => 1,
            INDEX_LABEL => "Ln",
          };
        chomp $diff;
        $diff .= "\n";
    
        $diff;
        }
    

    So now the meat of your program is something like this:

    my $is_same = dump_it( $reference_ref ) eq dump_it( $extracted_ref );
    
    pass();
    if ($is_same){
        pass();
    }else{
        fail();
        note( my_diff( $reference_ref, $extracted_ref, 1 ) );
    }